RSCG – DotnetYang

RSCG – DotnetYang    

name DotnetYang
nuget https://www.nuget.org/packages/DotnetYang/
link https://github.com/westermo/DotnetYang
author Westermo Network Technologies

Generating source code from YANG models

  

This is how you can use DotnetYang .

The code that you start with is


<project sdk="Microsoft.NET.Sdk">

  <propertygroup>
    <outputtype>Exe</outputtype>
    <targetframework>net8.0</targetframework>
    <implicitusings>enable</implicitusings>
    <nullable>enable</nullable>
  </propertygroup>

  
  <itemgroup>
    <additionalfiles include="demo.yang">
  </additionalfiles>

	 <propertygroup>
        <emitcompilergeneratedfiles>true</emitcompilergeneratedfiles>
        <compilergeneratedfilesoutputpath>$(BaseIntermediateOutputPath)\GX</compilergeneratedfilesoutputpath>
    </propertygroup>	
  <itemgroup>
    <packagereference version="0.3.0" include="dotnetYang">
  </packagereference>

</itemgroup>


The code that you will use is


Console.WriteLine("Yang file from https://info.support.huawei.com/info-finder/encyclopedia/en/YANG.html#content4!");
Some.Module.YangNode.DoSomethingInput input = new Some.Module.YangNode.DoSomethingInput
{
    TheBigLeaf = 123
};


module some-module {
    yang-version 1.1;
    namespace "urn:dotnet:yang:andrei";
    prefix sm;
    identity someIdentity;
    identity someOtherIdentity
    {
        base someIdentity;
    }
    rpc doSomething {
        input {
            leaf the-big-leaf
            {
                type uint32;
                default "4";
                description "The value that is the input of the doSomething rpc";
            }
        }
        output {
            leaf response
            {
                type identityref
                {
                    base someIdentity;
                }
                default "someOtherIdentity";
                description "The identity that is the output of the doSomething rpc";
            }
        }
    }
}

   The code that is generated is

using System;
using System.Xml;
using YangSupport;
namespace yangDemo;
///<summary>
///Configuration root object for yangDemo based on provided .yang modules
///</summary>

public class Configuration
{
    public Some.Module.YangNode? SomeModule { get; set; }
    public async Task WriteXMLAsync(XmlWriter writer)
	{
	    await writer.WriteStartElementAsync(null,"root",null);
	    
	    if(SomeModule is not null) await SomeModule.WriteXMLAsync(writer);
	    await writer.WriteEndElementAsync();
	}
    public static async Task<configuration> ParseAsync(XmlReader reader)
	{
	    Some.Module.YangNode? _SomeModule = default!;
	    while(await reader.ReadAsync())
	    {
	       switch(reader.NodeType)
	       {
	           case XmlNodeType.Element:
	               switch(reader.Name)
	               {
	                    case "some-module":
						    _SomeModule = await Some.Module.YangNode.ParseAsync(reader);
						    continue;
	                    case "rpc-error": throw await RpcException.ParseAsync(reader);
	                    default: throw new Exception($"Unexpected element '{reader.Name}' under 'root'");
	               }
	           case XmlNodeType.EndElement when reader.Name == "root":
	               return new Configuration{
	                   SomeModule = _SomeModule,
	               };
	           case XmlNodeType.Whitespace: break;
	           default: throw new Exception($"Unexpected node type '{reader.NodeType}' : '{reader.Name}' under 'root'");
	       }
	    }
	    throw new Exception("Reached end-of-readability without ever returning from Configuration.ParseAsync");
	}
}
public static class IYangServerExtensions
{
   public static async Task Receive(this IYangServer server, global::System.IO.Stream input, global::System.IO.Stream output)
   {
       var initialPosition = output.Position;
       var initialLength = output.Length;
       string? id = null;
       using XmlReader reader = XmlReader.Create(input, SerializationHelper.GetStandardReaderSettings());
       using XmlWriter writer = XmlWriter.Create(output, SerializationHelper.GetStandardWriterSettings());
       try
       {
           await reader.ReadAsync();
           switch(reader.Name)
           {
               case "rpc":
                   id = reader.ParseMessageId();
                   await writer.WriteStartElementAsync(null, "rpc-reply", "urn:ietf:params:xml:ns:netconf:base:1.0");
                   await writer.WriteAttributeStringAsync(null, "message-id", null, id);
                   await reader.ReadAsync();
                   switch(reader.Name)
                   {
                       case "action":
                           await server.ReceiveAction(reader, writer);
                           break;
                       default:
                           await server.ReceiveRPC(reader, writer);
                           break;
                   }
                   await writer.WriteEndElementAsync();
                   await writer.FlushAsync();
                   break;
               case "notification":
                   var eventTime = await reader.ParseEventTime();
                   await reader.ReadAsync();
                   await server.ReceiveNotification(reader, eventTime);
                   break;
           }
       }
       catch(RpcException ex)
       {
           await writer.FlushAsync();
           output.Position = initialPosition;
           output.SetLength(initialLength);
           await ex.SerializeAsync(output,id);
       }
       catch(Exception ex)
       {
           await writer.FlushAsync();
           output.Position = initialPosition;
           output.SetLength(initialLength);
           await output.SerializeRegularExceptionAsync(ex,id);
       }
   }
   public static async Task ReceiveRPC(this IYangServer server, XmlReader reader, XmlWriter writer)
   {
       switch(reader.Name)
       {
           case "doSomething" when reader.NamespaceURI is "urn:dotnet:yang:andrei":
			{
			    var input = await Some.Module.YangNode.DoSomethingInput.ParseAsync(reader);
			    var task = server.OnDoSomething(input);
			    var response = await task;
			    await response.WriteXMLAsync(writer);
			}
			break;
       }
   }
   public static async Task ReceiveAction(this IYangServer server, XmlReader reader, XmlWriter writer)
   {
       await reader.ReadAsync();
       switch(reader.Name)
       {
           
       }
   }
   public static async Task ReceiveNotification(this IYangServer server, XmlReader reader, DateTime eventTime)
   {
       switch(reader.Name)
       {
           
           
       }
   }
}
using System;
using System.Xml;
using System.Text;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using YangSupport;
namespace yangDemo
{
    public partial interface IYangServer
    {
        Task<some.module.yangnode.dosomethingoutput> OnDoSomething(Some.Module.YangNode.DoSomethingInput input);
    }
}
namespace Some.Module{
public class YangNode
{
    public const string ModuleName = "some-module";
    public const string Revision = "";
    public static string[] Features = [];
    //Yang Version 1.1
	public const string Namespace = "urn:dotnet:yang:andrei";
	public static string GetEncodedValue(SomeIdentityIdentity value)
	{
	    switch(value)
	    {
	        case SomeIdentityIdentity.SomeIdentity: return "someIdentity";
			case SomeIdentityIdentity.SomeOtherIdentity: return "someOtherIdentity";
	        default: return value.ToString();
	    }
	}
	public static string GetEncodedValue(SomeIdentityIdentity? value) =&gt; GetEncodedValue(value!.Value!);
	public static SomeIdentityIdentity GetSomeIdentityIdentityValue(string value)
	{
	    switch(value)
	    {
	        case "someIdentity": return SomeIdentityIdentity.SomeIdentity;
			case "someOtherIdentity": return SomeIdentityIdentity.SomeOtherIdentity;
	        default: throw new Exception($"{value} is not a valid value for SomeIdentityIdentity");
	    }
	}
	public enum SomeIdentityIdentity
	{
	    SomeIdentity,
		SomeOtherIdentity
	}
	public static string GetEncodedValue(SomeOtherIdentityIdentity value)
	{
	    switch(value)
	    {
	        case SomeOtherIdentityIdentity.SomeOtherIdentity: return "someOtherIdentity";
	        default: return value.ToString();
	    }
	}
	public static string GetEncodedValue(SomeOtherIdentityIdentity? value) =&gt; GetEncodedValue(value!.Value!);
	public static SomeOtherIdentityIdentity GetSomeOtherIdentityIdentityValue(string value)
	{
	    switch(value)
	    {
	        case "someOtherIdentity": return SomeOtherIdentityIdentity.SomeOtherIdentity;
	        default: throw new Exception($"{value} is not a valid value for SomeOtherIdentityIdentity");
	    }
	}
	public enum SomeOtherIdentityIdentity
	{
	    SomeOtherIdentity
	}
	public static async Task<some.module.yangnode.dosomethingoutput> DoSomething(IChannel channel, int messageID, Some.Module.YangNode.DoSomethingInput input)
	{
	    using XmlWriter writer = XmlWriter.Create(channel.WriteStream, SerializationHelper.GetStandardWriterSettings());
	    await writer.WriteStartElementAsync(null,"rpc","urn:ietf:params:xml:ns:netconf:base:1.0");
	    await writer.WriteAttributeStringAsync(null,"message-id",null,messageID.ToString());
	    await writer.WriteStartElementAsync("","doSomething","urn:dotnet:yang:andrei");
		await input.WriteXMLAsync(writer);
	    await writer.WriteEndElementAsync();
	    await writer.WriteEndElementAsync();
	    await writer.FlushAsync();
	    await channel.Send();
	    using XmlReader reader = XmlReader.Create(channel.ReadStream, SerializationHelper.GetStandardReaderSettings());
	    await reader.ReadAsync();
	    if(reader.NodeType != XmlNodeType.Element || reader.Name != "rpc-reply" || reader.NamespaceURI != "urn:ietf:params:xml:ns:netconf:base:1.0" || reader["message-id"] != messageID.ToString())
	    {
	        throw new Exception($"Expected stream to start with a <rpc-reply> element with message id {messageID} &amp; \"urn:ietf:params:xml:ns:netconf:base:1.0\" but got {reader.NodeType}: {reader.Name} in {reader.NamespaceURI}");
	    }
		var value = await DoSomethingOutput.ParseAsync(reader);
	    return value;
	}
	public class DoSomethingOutput
	{
	    ///<summary>
		///The identity that is the output of the doSomething rpc
		///</summary>
		public SomeIdentityIdentity? Response { get; set; } = SomeIdentityIdentity.SomeOtherIdentity;
	    public static async Task<dosomethingoutput> ParseAsync(XmlReader reader)
	{
	    SomeIdentityIdentity? _Response = default!;
	    while(await reader.ReadAsync())
	    {
	       switch(reader.NodeType)
	       {
	           case XmlNodeType.Element:
	               switch(reader.Name)
	               {
	                    case "response":
						    await reader.ReadAsync();
							if(reader.NodeType != XmlNodeType.Text)
							{
							    throw new Exception($"Expected token in ParseCall for 'response' to be text, but was '{reader.NodeType}'");
							}
							_Response = GetSomeIdentityIdentityValue(await reader.GetValueAsync());
							if(!reader.IsEmptyElement)
							{
							    await reader.ReadAsync();
							    if(reader.NodeType != XmlNodeType.EndElement)
							    {
							        throw new Exception($"Expected token in ParseCall for 'response' to be an element closure, but was '{reader.NodeType}'");
							    }
							}
						    continue;
	                    case "rpc-error": throw await RpcException.ParseAsync(reader);
	                    default: throw new Exception($"Unexpected element '{reader.Name}' under 'rpc-reply'");
	               }
	           case XmlNodeType.EndElement when reader.Name == "rpc-reply":
	               return new DoSomethingOutput{
	                   Response = _Response,
	               };
	           case XmlNodeType.Whitespace: break;
	           default: throw new Exception($"Unexpected node type '{reader.NodeType}' : '{reader.Name}' under 'rpc-reply'");
	       }
	    }
	    throw new Exception("Reached end-of-readability without ever returning from DoSomethingOutput.ParseAsync");
	}
	    public async Task WriteXMLAsync(XmlWriter writer)
	{
	    if(Response != default)
		{
		    await writer.WriteStartElementAsync(null,"response","urn:dotnet:yang:andrei");
		    await writer.WriteStringAsync(YangNode.GetEncodedValue(Response!));
		    await writer.WriteEndElementAsync();
		}
	}
	}
	public class DoSomethingInput
	{
	    ///<summary>
		///The value that is the input of the doSomething rpc
		///</summary>
		public uint? TheBigLeaf { get; set; } = 4;
	    public async Task WriteXMLAsync(XmlWriter writer)
		{
		    if(TheBigLeaf != default)
			{
			    await writer.WriteStartElementAsync(null,"the-big-leaf","urn:dotnet:yang:andrei");
			    await writer.WriteStringAsync(TheBigLeaf!.ToString());
			    await writer.WriteEndElementAsync();
			}
		}
	    public static async Task<dosomethinginput> ParseAsync(XmlReader reader)
		{
		    uint? _TheBigLeaf = default!;
		    while(await reader.ReadAsync())
		    {
		       switch(reader.NodeType)
		       {
		           case XmlNodeType.Element:
		               switch(reader.Name)
		               {
		                    case "the-big-leaf":
							    await reader.ReadAsync();
								if(reader.NodeType != XmlNodeType.Text)
								{
								    throw new Exception($"Expected token in ParseCall for 'the-big-leaf' to be text, but was '{reader.NodeType}'");
								}
								_TheBigLeaf = uint.Parse(await reader.GetValueAsync());
								if(!reader.IsEmptyElement)
								{
								    await reader.ReadAsync();
								    if(reader.NodeType != XmlNodeType.EndElement)
								    {
								        throw new Exception($"Expected token in ParseCall for 'the-big-leaf' to be an element closure, but was '{reader.NodeType}'");
								    }
								}
							    continue;
		                    case "rpc-error": throw await RpcException.ParseAsync(reader);
		                    default: throw new Exception($"Unexpected element '{reader.Name}' under 'doSomething'");
		               }
		           case XmlNodeType.EndElement when reader.Name == "doSomething":
		               return new DoSomethingInput{
		                   TheBigLeaf = _TheBigLeaf,
		               };
		           case XmlNodeType.Whitespace: break;
		           default: throw new Exception($"Unexpected node type '{reader.NodeType}' : '{reader.Name}' under 'doSomething'");
		       }
		    }
		    throw new Exception("Reached end-of-readability without ever returning from DoSomethingInput.ParseAsync");
		}
	}
    public static async Task<some.module.yangnode> ParseAsync(XmlReader reader)
	{
	    while(await reader.ReadAsync())
	    {
	       switch(reader.NodeType)
	       {
	           case XmlNodeType.Element:
	               switch(reader.Name)
	               {
	                    case "rpc-error": throw await RpcException.ParseAsync(reader);
	                    default: throw new Exception($"Unexpected element '{reader.Name}' under 'some-module'");
	               }
	           case XmlNodeType.EndElement when reader.Name == "some-module":
	               return new Some.Module.YangNode{
	               };
	           case XmlNodeType.Whitespace: break;
	           default: throw new Exception($"Unexpected node type '{reader.NodeType}' : '{reader.Name}' under 'some-module'");
	       }
	    }
	    throw new Exception("Reached end-of-readability without ever returning from Some.Module.YangNode.ParseAsync");
	}
    public async Task WriteXMLAsync(XmlWriter writer)
	{
	    await writer.WriteStartElementAsync(null,"some-module","urn:dotnet:yang:andrei");
	    await writer.WriteEndElementAsync();
	}
}
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/DotnetYang

RSCG – depso

RSCG – depso    

name depso
nuget https://www.nuget.org/packages/depso/
link https://github.com/notanaverageman/Depso
author Yusuf Tarık Günaydın

generating DI code

  

This is how you can use depso .

The code that you start with is


<project sdk="Microsoft.NET.Sdk">

  <propertygroup>
    <outputtype>Exe</outputtype>
    <targetframework>net8.0</targetframework>
    <implicitusings>enable</implicitusings>
    <nullable>enable</nullable>
  </propertygroup>
	<itemgroup>
		<packagereference version="1.0.1" include="Depso">
	</packagereference>
	<propertygroup>
		<emitcompilergeneratedfiles>true</emitcompilergeneratedfiles>
		<compilergeneratedfilesoutputpath>$(BaseIntermediateOutputPath)\GX</compilergeneratedfilesoutputpath>
	</propertygroup>
</itemgroup>


The code that you will use is


using InjectDemo;
MyServiceProvider sc = new();
var con = sc.GetService(typeof(Database)) as IDatabase;
ArgumentNullException.ThrowIfNull(con);
con.Open();


[Depso.ServiceProvider]
public partial class MyServiceProvider
{
    private void RegisterServices()
    {
        AddTransient<database database="" ,="">();
        AddTransient<idatabase databasecon="" ,="">();
    }
}


namespace InjectDemo;

partial class Database : IDatabase
{
    private readonly IDatabase con;

    public Database(IDatabase con)
    {
        this.con = con;
    }
    public void Open()
    {
        Console.WriteLine($"open from database");
        con.Open();
    }

}





namespace InjectDemo;

public partial class DatabaseCon:IDatabase
{
    public string? Connection { get; set; }
    public void Open()
    {
        Console.WriteLine("open from database con" );
    }
}



   The code that is generated is

// <auto-generated>

#nullable enable

namespace Depso
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Class)]
    internal sealed class ServiceProviderAttribute : global::System.Attribute
    {
    }
}
// <auto-generated>

#nullable enable

namespace Depso
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Class)]
    internal sealed class ServiceProviderModuleAttribute : global::System.Attribute
    {
    }
}
// <auto-generated>

#nullable enable

public partial class MyServiceProvider
    :
    global::System.IDisposable,
    global::System.IAsyncDisposable,
    global::System.IServiceProvider
{
    private readonly object _sync = new object();

    private global::MyServiceProvider.Scope? _rootScope;
    private global::MyServiceProvider.Scope RootScope =&gt; _rootScope ??= CreateScope(_sync);

    private bool _isDisposed;

    public object? GetService(global::System.Type serviceType)
    {
        if (serviceType == typeof(global::InjectDemo.Database)) return CreateDatabase_0();
        if (serviceType == typeof(global::InjectDemo.IDatabase)) return CreateDatabaseCon_0();
        if (serviceType == typeof(global::System.IServiceProvider)) return this;

        return null;
    }

    private T GetService<t>()
    {
        return (T)GetService(typeof(T))!;
    }

    private global::InjectDemo.Database CreateDatabase_0()
    {
        return new global::InjectDemo.Database(GetService<global::injectdemo.idatabase>());
    }

    private global::InjectDemo.DatabaseCon CreateDatabaseCon_0()
    {
        return new global::InjectDemo.DatabaseCon();
    }

    private global::MyServiceProvider.Scope CreateScope(object? sync)
    {
        ThrowIfDisposed();
        return new global::MyServiceProvider.Scope(this, sync);
    }

    public void Dispose()
    {
        lock (_sync)
        {
            if (_isDisposed)
            {
                return;
            }

            _isDisposed = true;
        }

        if (_rootScope != null) _rootScope.Dispose();
    }

    public async global::System.Threading.Tasks.ValueTask DisposeAsync()
    {
        lock (_sync)
        {
            if (_isDisposed)
            {
                return;
            }

            _isDisposed = true;
        }

        if (_rootScope != null) await _rootScope.DisposeAsync();
    }

    private void ThrowIfDisposed()
    {
        if (_isDisposed)
        {
            throw new global::System.ObjectDisposedException("MyServiceProvider");
        }
    }
}

// <auto-generated>

#nullable enable

public partial class MyServiceProvider
{
    private class RegistrationModifier
    {
        public static readonly global::MyServiceProvider.RegistrationModifier Instance;

        static RegistrationModifier()
        {
            Instance = new global::MyServiceProvider.RegistrationModifier();
        }

        private RegistrationModifier()
        {
        }

        public global::MyServiceProvider.RegistrationModifier AlsoAsSelf()
        {
            return this;
        }

        public global::MyServiceProvider.RegistrationModifier AlsoAs(global::System.Type type)
        {
            return this;
        }

        public global::MyServiceProvider.RegistrationModifier AlsoAs<t>()
        {
            return this;
        }
    }

    private global::MyServiceProvider.RegistrationModifier ImportModule<t>()
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier ImportModule(global::System.Type moduleType)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddSingleton(global::System.Type serviceType)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddSingleton(global::System.Type serviceType, global::System.Type implementationType)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddSingleton<tservice>()
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddSingleton<tservice timplementation="" ,="">() where TImplementation : TService
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddSingleton<tservice>(global::System.Func<global::system.iserviceprovider tservice="" ,=""> factory)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddScoped(global::System.Type serviceType)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddScoped(global::System.Type serviceType, global::System.Type implementationType)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddScoped<tservice>()
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddScoped<tservice timplementation="" ,="">() where TImplementation : TService
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddScoped<tservice>(global::System.Func<global::system.iserviceprovider tservice="" ,=""> factory)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddTransient(global::System.Type serviceType)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddTransient(global::System.Type serviceType, global::System.Type implementationType)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddTransient<tservice>()
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddTransient<tservice timplementation="" ,="">() where TImplementation : TService
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }

    private global::MyServiceProvider.RegistrationModifier AddTransient<tservice>(global::System.Func<global::system.iserviceprovider tservice="" ,=""> factory)
    {
        return global::MyServiceProvider.RegistrationModifier.Instance;
    }
}

// <auto-generated>

#nullable enable

public partial class MyServiceProvider
{
    public class Scope
        :
        global::System.IDisposable,
        global::System.IAsyncDisposable,
        global::System.IServiceProvider
    {
        private readonly object _sync = new object();
        private readonly global::MyServiceProvider _root;

        private bool _isDisposed;

        public Scope(global::MyServiceProvider root, object? sync)
        {
            _root = root;

            if (sync != null)
            {
                _sync = sync;
            }
        }

        public object? GetService(global::System.Type serviceType)
        {
            if (serviceType == typeof(global::InjectDemo.Database)) return _root.CreateDatabase_0();
            if (serviceType == typeof(global::InjectDemo.IDatabase)) return _root.CreateDatabaseCon_0();
            if (serviceType == typeof(global::System.IServiceProvider)) return this;

            return null;
        }

        private T GetService<t>()
        {
            return (T)GetService(typeof(T))!;
        }

        public void Dispose()
        {
            lock (_sync)
            {
                if (_isDisposed)
                {
                    return;
                }

                _isDisposed = true;
            }
        }

        public global::System.Threading.Tasks.ValueTask DisposeAsync()
        {
            lock (_sync)
            {
                if (_isDisposed)
                {
                    return default;
                }

                _isDisposed = true;
            }

            return default;
        }

        private void ThrowIfDisposed()
        {
            if (_isDisposed)
            {
                throw new global::System.ObjectDisposedException("MyServiceProvider.Scope");
            }
        }
    }
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/depso

RSCG – FactoryGenerator

RSCG – FactoryGenerator    

name FactoryGenerator
nuget https://www.nuget.org/packages/FactoryGenerator/
link https://github.com/westermo/FactoryGenerator
author Westermo Network Technologies

generating DI code

  

This is how you can use FactoryGenerator .

The code that you start with is


<project sdk="Microsoft.NET.Sdk">

  <propertygroup>
    <outputtype>Exe</outputtype>
    <targetframework>net8.0</targetframework>
    <implicitusings>enable</implicitusings>
    <nullable>enable</nullable>
  </propertygroup>
	<propertygroup>
		<emitcompilergeneratedfiles>true</emitcompilergeneratedfiles>
		<compilergeneratedfilesoutputpath>$(BaseIntermediateOutputPath)\GX</compilergeneratedfilesoutputpath>
	</propertygroup>
	<itemgroup>
	  <packagereference version="1.0.11" include="FactoryGenerator">
	  <packagereference version="1.0.11" include="FactoryGenerator.Attributes">
	</packagereference>
</packagereference>


The code that you will use is


using InjectDemo;

InjectDemo.Generated.DependencyInjectionContainer sc = new();
var db = sc.Resolve<idatabase>();
db.Open();




using FactoryGenerator.Attributes;

namespace InjectDemo;

[Inject, Scoped]
public partial class Database : IDatabase
{
    private readonly DatabaseCon con;

    public Database(DatabaseCon con)
    {
        this.con = con;
    }
    public void Open()
    {
        Console.WriteLine($"open {con.Connection}");
        this.con.Open();
    }

}





using FactoryGenerator.Attributes;

namespace InjectDemo;

[Inject,Scoped, Self]
public partial class DatabaseCon: IDatabase
{
    public string? Connection { get; set; }
    public void Open()
    {
        Console.WriteLine("open" + Connection);
    }
}



   The code that is generated is


using System;
using System.Collections.Generic;
using FactoryGenerator;
using System.CodeDom.Compiler;
namespace InjectDemo.Generated;
#nullable enable
public partial class DependencyInjectionContainer
{
    
    public DependencyInjectionContainer()
    {
        
        
        m_lookup = new(2)
        {
			{ typeof(InjectDemo.IDatabase),InjectDemo_IDatabase },
			{ typeof(InjectDemo.DatabaseCon),InjectDemo_DatabaseCon },




        };
    }

    
public ILifetimeScope BeginLifetimeScope()
{
    var scope = new LifetimeScope(this);
    resolvedInstances.Add(new WeakReference<idisposable>(scope));
    return scope;
}

}

using System;
using System.Collections.Generic;
using FactoryGenerator;
using System.CodeDom.Compiler;
namespace InjectDemo.Generated;
#nullable enable
public partial class DependencyInjectionContainer
{
    
    internal InjectDemo.DatabaseCon InjectDemo_DatabaseCon()
    {
        if (m_InjectDemo_DatabaseCon != null)
            return m_InjectDemo_DatabaseCon;
    
        lock (m_lock)
        {
            if (m_InjectDemo_DatabaseCon != null)
                return m_InjectDemo_DatabaseCon;
            return m_InjectDemo_DatabaseCon = new InjectDemo.DatabaseCon();
        }
    } 
    internal InjectDemo.DatabaseCon? m_InjectDemo_DatabaseCon;
	
    internal InjectDemo.Database InjectDemo_Database()
    {
        if (m_InjectDemo_Database != null)
            return m_InjectDemo_Database;
    
        lock (m_lock)
        {
            if (m_InjectDemo_Database != null)
                return m_InjectDemo_Database;
            return m_InjectDemo_Database = new InjectDemo.Database(InjectDemo_DatabaseCon());
        }
    } 
    internal InjectDemo.Database? m_InjectDemo_Database;
	internal InjectDemo.IDatabase InjectDemo_IDatabase() =&gt; InjectDemo_Database();
}

using System;
using System.Collections.Generic;
using FactoryGenerator;
using System.CodeDom.Compiler;
namespace InjectDemo.Generated;
#nullable enable
public partial class DependencyInjectionContainer
{
    
}

using System;
using System.Collections.Generic;
using FactoryGenerator;
using System.CodeDom.Compiler;
namespace InjectDemo.Generated;
#nullable enable
[GeneratedCode("FactoryGenerator", "1.0.0")]
#nullable disable
public sealed partial class DependencyInjectionContainer : IContainer
{
    private object m_lock = new();
    private Dictionary<type  ,func><object>> m_lookup;     private readonly List<WeakReference<IDisposable>> resolvedInstances = new();      public T Resolve<T>()     {         return (T)Resolve(typeof(T));     }      public object Resolve(Type type)     {         var instance = m_lookup[type]();         return instance;     }      public void Dispose()     {         foreach (var weakReference in resolvedInstances)         {             if(weakReference.TryGetTarget(out var disposable))             {                 disposable.Dispose();             }         }         resolvedInstances.Clear();     }      public bool TryResolve(Type type, out object resolved)     {         if(m_lookup.TryGetValue(type, out var factory))         {             resolved = factory();             return true;         }         resolved = default;         return false;     }       public bool TryResolve<T>(out T resolved)     {         if(m_lookup.TryGetValue(typeof(T), out var factory))         {             var value = factory();             if(value is T t)             {                 resolved = t;                 return true;             }         }         resolved = default;         return false;     }     public bool IsRegistered(Type type)     {         return m_lookup.ContainsKey(type);     }     public bool IsRegistered<T>() => IsRegistered(typeof(T)); } 
  using System; using System.Collections.Generic; using FactoryGenerator; using System.CodeDom.Compiler; namespace InjectDemo.Generated; #nullable enable public partial class LifetimeScope {          public LifetimeScope(DependencyInjectionContainer fallback)     {         m_fallback = fallback;                  m_lookup = new(2)         { 			{ typeof(InjectDemo.IDatabase),InjectDemo_IDatabase }, 			{ typeof(InjectDemo.DatabaseCon),InjectDemo_DatabaseCon },             };     }        } 
  using System; using System.Collections.Generic; using FactoryGenerator; using System.CodeDom.Compiler; namespace InjectDemo.Generated; #nullable enable public partial class LifetimeScope {          internal InjectDemo.DatabaseCon InjectDemo_DatabaseCon()     {         if (m_InjectDemo_DatabaseCon != null)             return m_InjectDemo_DatabaseCon;              lock (m_lock)         {             if (m_InjectDemo_DatabaseCon != null)                 return m_InjectDemo_DatabaseCon;             return m_InjectDemo_DatabaseCon = new InjectDemo.DatabaseCon();         }     }      internal InjectDemo.DatabaseCon? m_InjectDemo_DatabaseCon; 	     internal InjectDemo.Database InjectDemo_Database()     {         if (m_InjectDemo_Database != null)             return m_InjectDemo_Database;              lock (m_lock)         {             if (m_InjectDemo_Database != null)                 return m_InjectDemo_Database;             return m_InjectDemo_Database = new InjectDemo.Database(InjectDemo_DatabaseCon());         }     }      internal InjectDemo.Database? m_InjectDemo_Database; 	internal InjectDemo.IDatabase InjectDemo_IDatabase() => InjectDemo_Database(); } 
  using System; using System.Collections.Generic; using FactoryGenerator; using System.CodeDom.Compiler; namespace InjectDemo.Generated; #nullable enable public partial class LifetimeScope {      } 
  using System; using System.Collections.Generic; using FactoryGenerator; using System.CodeDom.Compiler; namespace InjectDemo.Generated; #nullable enable [GeneratedCode("FactoryGenerator", "1.0.0")] #nullable disable public sealed partial class LifetimeScope : IContainer {     public ILifetimeScope BeginLifetimeScope()     {         var scope = m_fallback.BeginLifetimeScope();         resolvedInstances.Add(new WeakReference<IDisposable>(scope));         return scope;     }     private object m_lock = new();     private DependencyInjectionContainer m_fallback;     private Dictionary<Type,Func<object>> m_lookup;     private readonly List<WeakReference<IDisposable>> resolvedInstances = new();     public T Resolve<T>()     {         return (T)Resolve(typeof(T));     }      public object Resolve(Type type)     {         var instance = m_lookup[type]();         return instance;     }      public void Dispose()     {         foreach (var weakReference in resolvedInstances)         {             if(weakReference.TryGetTarget(out var disposable))             {                 disposable.Dispose();             }         }         resolvedInstances.Clear();     }      public bool TryResolve(Type type, out object resolved)     {         if(m_lookup.TryGetValue(type, out var factory))         {             resolved = factory();             return true;         }         resolved = default;         return false;     }       public bool TryResolve<T>(out T resolved)     {         if(m_lookup.TryGetValue(typeof(T), out var factory))         {             var value = factory();             if(value is T t)             {                 resolved = t;                 return true;             }         }         resolved = default;         return false;     }     public bool IsRegistered(Type type)     {         return m_lookup.ContainsKey(type);     }     public bool IsRegistered<T>() => IsRegistered(typeof(T)); }  

Code and pdf at https://ignatandrei.github.io/RSCG_Examples/v2/docs/FactoryGenerator

RSCG – TableStorage

RSCG – TableStorage    

name TableStorage
nuget https://www.nuget.org/packages/TableStorage/
link https://github.com/StevenThuriot/TableStorage
author Steven Thuriot

Generate resources for accessing Azure Table Storage

  

This is how you can use TableStorage .

The code that you start with is


<project sdk="Microsoft.NET.Sdk">

  <propertygroup>
    <outputtype>Exe</outputtype>
    <targetframework>net8.0</targetframework>
    <implicitusings>enable</implicitusings>
    <nullable>enable</nullable>
  </propertygroup>

  <itemgroup>
    <packagereference version="12.13.1" include="Azure.Storage.Blobs">
    <packagereference version="12.1.0" include="Azure.Storage.Files.Shares">
    <packagereference version="12.11.1" include="Azure.Storage.Queues">
    <packagereference version="1.5.0" include="Microsoft.Extensions.Azure">
    <packagereference version="8.0.0" include="Microsoft.Extensions.DependencyInjection">
    <packagereference version="4.2.1" include="TableStorage">
  </packagereference>
	<propertygroup>
		<emitcompilergeneratedfiles>true</emitcompilergeneratedfiles>
		<compilergeneratedfilesoutputpath>$(BaseIntermediateOutputPath)\GX</compilergeneratedfilesoutputpath>
	</propertygroup>
</packagereference>


The code that you will use is


using Microsoft.Extensions.DependencyInjection;
using test;
/*Visual Studio version	Azurite executable location
Visual Studio Community 2022	C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\Microsoft\Azure Storage Emulator
Visual Studio Professional 2022	C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\Extensions\Microsoft\Azure Storage Emulator
Visual Studio Enterprise 2022	C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\Azure Storage Emulator
*/

var serviceProvider = new ServiceCollection()
    .AddDatabaseContext("UseDevelopmentStorage=true")
    .BuildServiceProvider();

DatabaseContext db = serviceProvider.GetRequiredService<databasecontext>();

Employee?  e=new ();
e.Name = "Andrei Ignat";
e.PartitionKey = "1";
e.RowKey = Guid.NewGuid().ToString();
await db.Employees.AddEntityAsync(e);

e = await db.Employees.GetEntityAsync(e.PartitionKey, e.RowKey);
Console.WriteLine(e?.Name);  



using TableStorage;
namespace test;
[TableContext]
public partial class DatabaseContext
{
    public TableSet<employee>? Employees { get; set; }
}


[TableSet]
[TableSetProperty(typeof(bool), "Enabled")]
[TableSetProperty(typeof(string), "Name")]
public partial class Employee
{

}


   The code that is generated is


using System;

namespace TableStorage
{
    [AttributeUsage(AttributeTargets.Class)]
    public sealed class TableContextAttribute : Attribute
    {
    }
}
using Microsoft.Extensions.DependencyInjection;
using TableStorage;
using System;

#nullable disable

namespace test
{
    public static class DatabaseContextExtensions
    {
        public static IServiceCollection AddDatabaseContext(this IServiceCollection services, string connectionString, Action<tablestorage.tableoptions> configure = null)
        {
            DatabaseContext.Register(services, connectionString, configure);
            return services;
        }
    }

    partial class DatabaseContext
    {
        private TableStorage.ICreator _creator { get; init; }

        private static class TableSetCache<t>
                where T : class, Azure.Data.Tables.ITableEntity, new()
        {
            private static System.Collections.Concurrent.ConcurrentDictionary<string  , tablestorage.tableset><t  ="">&gt; _unknownTableSets = new System.Collections.Concurrent.ConcurrentDictionary<string  , tablestorage.tableset><t  ="">&gt;();
            public static TableStorage.TableSet<t> GetTableSet(TableStorage.ICreator creator, string tableName)
            {
                return _unknownTableSets.GetOrAdd(tableName, creator.CreateSet<t>);
            }

        }

        public TableSet<t> GetTableSet<t>(string tableName)
            where T : class, Azure.Data.Tables.ITableEntity, new()
        {
            return TableSetCache<t>.GetTableSet(_creator, tableName);
        }

        public static void Register(IServiceCollection services, string connectionString, Action<tablestorage.tableoptions> configure = null)
        {
            services.AddSingleton(s =&gt;
                    {
                        ICreator creator = TableStorage.TableStorageSetup.BuildCreator(connectionString, configure);

                        return new DatabaseContext()
                        {
                            _creator = creator,
                            Employees = creator.CreateSet<test.employee>("Employees", null, null),
                        };
                    });
        }
    }
}


using System;

namespace TableStorage
{
    [AttributeUsage(AttributeTargets.Class)]
    public sealed class TableSetAttribute : Attribute
    {
    }


    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public sealed class TableSetPropertyAttribute : Attribute
    {
        public TableSetPropertyAttribute(Type type, string name)
        {
        }
    }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public sealed class PartitionKeyAttribute : Attribute
    {
        public PartitionKeyAttribute(string name)
        {
        }
    }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public sealed class RowKeyAttribute : Attribute
    {
        public RowKeyAttribute(string name)
        {
        }
    }
}
using Microsoft.Extensions.DependencyInjection;
using TableStorage;
using System.Collections.Generic;
using System;

#nullable disable

namespace test
{
    [System.Diagnostics.DebuggerDisplay(@"Employee \{ {PartitionKey}, {RowKey} \}")]
    partial class Employee : IDictionary<string  , object="">, Azure.Data.Tables.ITableEntity
    {
        public string PartitionKey { get; set; }
        public string RowKey { get; set; }
        public DateTimeOffset? Timestamp { get; set; }
        public Azure.ETag ETag { get; set; }
        [System.Runtime.Serialization.IgnoreDataMember] public bool Enabled { get; set; }
        [System.Runtime.Serialization.IgnoreDataMember] public string Name { get; set; }

        public object this[string key]
        {
            get
            {
                switch (key)
                {
                    case "PartitionKey": return PartitionKey;
                    case "RowKey": return RowKey;
                    case "Timestamp": return Timestamp;
                    case "odata.etag": return ETag.ToString();
                    case "Enabled": return Enabled;
                    case "Name": return Name;
                    default: return null;
                }
            }

            set
            {
                switch (key)
                {
                    case "PartitionKey": PartitionKey = value?.ToString(); break;
                    case "RowKey": RowKey = value?.ToString(); break;
                    case "Timestamp": Timestamp = (System.DateTimeOffset?)value; break;
                    case "odata.etag": ETag = new Azure.ETag(value?.ToString()); break;
                    case "Enabled": Enabled = (bool) value; break;
                    case "Name": Name = (string) value; break;
                }
            }
        }

        public ICollection<string> Keys =&gt; new string[] { "PartitionKey", "RowKey", "Timestamp", "odata.etag", "Enabled", "Name",  };
        public ICollection<object> Values => new object[] { PartitionKey, RowKey, Timestamp, ETag.ToString(), Enabled, Name,  };         public int Count => 6;         public bool IsReadOnly => false;          public void Add(string key, object value)         {             this[key] = value;         }          public void Add(KeyValuePair<string, object> item)         {             this[item.Key] = item.Value;         }          public void Clear()         {             Enabled = default(bool);             Name = default(string);         }          public bool Contains(KeyValuePair<string, object> item)         {             if (TryGetValue(item.Key, out var value))             {                 return value == item.Value;             }              return false;         }          public bool ContainsKey(string key)         {             switch (key)             {                 case "PartitionKey":                 case "RowKey":                 case "Timestamp":                 case "odata.etag":                 case "Enabled":                  case "Name":                      return true;                              default: return false;             }         }          public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)         {             if (array == null)             {                 throw new System.ArgumentNullException("array");             }              if ((uint)arrayIndex > (uint)array.Length)             {                 throw new System.IndexOutOfRangeException();             }              if (array.Length - arrayIndex < Count)             {                 throw new System.ArgumentException();             }              foreach (var item in this)             {                 array&#91;arrayIndex++&#93; = item;             }         }          public IEnumerator<KeyValuePair<string, object>> GetEnumerator()         {             yield return new KeyValuePair<string, object>("PartitionKey", PartitionKey);             yield return new KeyValuePair<string, object>("RowKey", RowKey);             yield return new KeyValuePair<string, object>("Timestamp", Timestamp);             yield return new KeyValuePair<string, object>("odata.etag", ETag.ToString());             yield return new KeyValuePair<string, object>("Enabled", Enabled);             yield return new KeyValuePair<string, object>("Name", Name);         }          public bool Remove(string key)         {             if (ContainsKey(key))              {                 this[key] = null;                 return true;             }              return false;         }          public bool Remove(KeyValuePair<string, object> item)         {             if (Contains(item))              {                 this[item.Key] = null;                 return true;             }              return false;         }          public bool TryGetValue(string key, out object value)         {             switch (key)             {                 case "PartitionKey": value = PartitionKey; return true;                 case "RowKey": value = RowKey; return true;                 case "Timestamp": value = Timestamp; return true;                 case "odata.etag": value = ETag; return true;                 case "Enabled": value = Enabled; return true;                 case "Name": value = Name; return true;                 default: value = null; return false;             }         }          System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()         {             return this.GetEnumerator();         }     } } 

Code and pdf at https://ignatandrei.github.io/RSCG_Examples/v2/docs/TableStorage

aspnetcore-webapi-clean-architecture

Url : https://github.com/nitish-kaushik/aspnetcore-webapi-clean-architecture

Number projects : 4 (tests : 0, no tests : 4 )

Number packages : 84 (Outdated: 1, Deprecated: 0, Major version differs 0 )

Total Commits: 8 ( last commit : this year,2024 )

The commit with max files ( 9 ) is sha 59fba18226956ae339f3aea4b113540ee0cd7327 on 2024 July 09 )

The file with most commits ( 2 ) is aspnetcore-webapi-clean-architecture\MyApp.Api\MyApp.Api\MyApp.Api/MyApp.Api/Controllers/WeatherForecastController.cs

4 Projects

diagram

Generated by https://www.nuget.org/packages/NetPackageAnalyzerConsole

Install from https://nuget.org/packages/netpackageanalyzerconsole

ADR MADR project

Introduction : This is a test for me to write ADR’s accordingly to https://github.com/joelparkerhenderson/architecture-decision-record . I will try those ADR and see the final result

ADR007 Exception Handling

TITLE: ADR 007 – Exception Handling

  • Status: accepted

  • Deciders: [“Andrei Ignat”]

  • Date: [2023-11-05]

Context and Problem Statement

In every program we need to handle the unexpected situations. One of those are configuration errors. Others are network errors, database errors, etc. However, there are many situations where the errors are of our own – like a bug in the code, errors in the database and so on…

Decision Drivers

In order to make a difference between the errors that are of our own and the errors that are not of our own, we need to have a way to handle them differently. That’s why a custom exception is needed.

Decision Outcome

Chosen option: “Use a custom exception” every time when the error is of our own. Also, use a custom exception when the error is not of our own, but we want to handle it differently.

Positive Consequences

Internal errors are handled differently than external errors. And some corrective measures can be applied.

  1. Domain-Specific Errors: When you have errors that are specific to your business logic or domain, custom exceptions can be used to represent these errors.

  2. Complex Error Handling: If your application has complex error handling requirements, custom exceptions can be used to differentiate between different types of errors.

  3. Enhanced Error Information: If you need to include additional information in your exceptions (like error codes or additional context), custom exceptions can be used.

  4. Control Flow: In some cases, custom exceptions can be used as a form of control flow. This is generally discouraged, but there are some scenarios where it can be useful.

  5. Third-Party Library Errors: If you’re using a third-party library that throws generic exceptions, you might want to catch those and throw your own custom exceptions instead. This can make your error handling code more consistent and easier to understand.

Negative Consequences

  1. Loss of Inner Exception: If not handled properly, the inner exception that caused the custom exception might be lost, making it harder to debug the issue – or should be added to help the developer to understand the error.

  2. Increased Development Time: Creating and maintaining custom exceptions can increase development time.

  3. Potential for Misuse: Custom exceptions can be misused or overused, leading to unnecessary complexity.

  4. Learning Curve: Other developers unfamiliar with the custom exceptions might have a learning curve to understand them.

  5. Dependency: If the custom exceptions are part of a library that is used across multiple projects, changes to the exceptions can impact all these projects.

Pros and Cons of the Options

Pros of Using Custom Exceptions
  1. Specificity: Custom exceptions provide more specific error information, making it easier to understand and handle the error.

  2. Control: They allow for more control over error handling, as you can create different exceptions for different error conditions.
  3. Clarity: They can make the code more readable and maintainable, as the exception names can reflect the error conditions they represent.
Cons of Using Custom Exceptions
  1. Complexity: Creating custom exceptions can add complexity to the code, especially if overused.

  2. Maintenance: They require more maintenance. If the error conditions change, the custom exceptions also need to be updated.
  3. Standardization: Using standard exceptions can sometimes be more straightforward and less error-prone, as they are widely recognized and understood by most developers.

ADR Business Case

Introduction : This is a test for me to write ADR’s accordingly to https://github.com/joelparkerhenderson/architecture-decision-record . I will try those ADR and see the final result

ADR006 Deploy

TITLE: ADR 006 – Deploy

  • Title

  • Status
  • Evaluation criteria
  • Candidates to consider
  • Research and analysis of each candidate
    • Does/doesn’t meet criteria and why

    • Cost analysis
    • SWOT analysis
    • Opinions and feedback
  • Recommendation

Where and how to deploy

The deployment could be done automatically or manually.

The deployment could be on a cloud provider or on premise or on a hosted server.

For automatically, the easy solution is a Github Action because the code is on Github.

Status

Accepted

Evaluation criteria

Low cost – 10000

Easy of deployment – 10

Easy of maintenance – 10

Easy of monitoring – 10

Easy of scaling – 10

Easy of rollback – 10

Candidates to consider

Amazon

Azure

Hosted Server

Research and analysis of each candidate

Because of having a sponsored account on Azure, Azure is the first candidate to consider.

Also, Github Actions is integrated with Azure.

Recommendation

Azure

ADR based on

https://github.com/joelparkerhenderson/architecture-decision-record/blob/main/locales/en/templates/decision-record-template-for-business-case/index.md

ADR Jeff Tyree and Art Akerman

Introduction : This is a test for me to write ADR’s accordingly to https://github.com/joelparkerhenderson/architecture-decision-record . I will try those ADR and see the final result

ADR005 Logging

TITLE: ADR 005 – Logging

  • Status: accepted

  • Deciders: Andrei Ignat
  • Date: 2023-10-07

Technical Story: Must add some logging library in order to debug problems

Context and Problem Statement

Every time there is a problem, logging could solve the problems by having more details about what is wrong

So I MUST add logging

Decision Drivers

  • Making one library for logging is not so complicated , however

    • the exporters to other systems are cumbersome and difficult

    • also threading is a problem when writing to a file
  • There are already production battle tested libraries that performs the work well.

Considered Options

Decision Outcome

Choosing NLog because it is simpler to configure from a text file

Positive Consequences

Devops should be happy – the logging file for production could be also put into SourceControl without problems.

Negative Consequences

Do not support yet SAMNE logging for a class. Could be alleviated by having DebuggerDisplayAttribute for the class and logging the function

Pros and Cons of the Options

Serilog
  • Good, because Widely adopted

  • Good, because A lot of documentation
  • Good, because Great community
  • Bad, because A bit harder to learn when coming from log4net
NLOG

Advantages

  • Good, because A lot of documentation

  • Good, because Having been around a long time, there are lots of blog posts
  • Good, because Easy to get started when coming from other logging frameworks
  • Bad, because Structured logging is still a bit behind Serilog
  • Bad, because C#-based API is harder to use than Serilog’s fluent API

Links

ADR based on

https://github.com/joelparkerhenderson/architecture-decision-record/tree/main/locales/en/templates/decision-record-template-by-jeff-tyree-and-art-akerman/index.md

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.