myesn

myEsn2E9

hi
github

.NET Coding Patterns

Generic Factory#

This pattern applies to Microsoft.Extensions.* and Microsoft.AspNetCore.*. The idea is to use a generic type as a factory instead of a function. The generic parameter is the type to be instantiated. For example, if we have an IServiceFactory<TService>, we can resolve TService from the DI container or create an instance (if it is not in the container).

public interface IServiceFactory<TService>
{
	TService Service { get; }
}

public class ServiceFactory<TService> : IServiceFactory<TService>
{
	public ServiceFactory(IServiceProvider provider)
	{
		Service = (TService)provider.GetService(typeof(TService)) ?? ActivatorUtilities.CreateInstance<TService>(provider);
	}

	public TService Service { get; }
}

The constructor can access any service and the requested generic type. These open generic services are registered like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient(typeof(IServiceFactory<>), typeof(ServiceFactory<>));
}

Lazy Initialization of Services#

Sometimes, services need to be created after the constructor has executed. The usual practice is to inject IServiceProvider into the constructor of the service that requires delayed instance creation.

public class Service
{
    private readonly IServiceProvider _serviceProvider;
    public Service(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    public IFoo CreateFoo() => _serviceProvider.GetRequiredService<IFoo>();
    public IBar CreateBar() => _serviceProvider.GetRequiredService<IBar>();
}

If the type is known in advance, we can build a custom service to provide lazy types for encapsulating the service locator pattern:

public interface ILazy<T>
{
    T Value { get; }
}

public class LazyFactory<T> : ILazy<T>
{
    private readonly Lazy<T> _lazy;

    public LazyFactory(IServiceProvider service)
    {
        _lazy = new Lazy<T>(() => service.GetRequiredService<T>());
    }

    public T Value => _lazy.Value;
}

public class Service
{
    private readonly ILazy<IFoo> _foo;
    private readonly ILazy<IBar> _bar;
    public Service(ILazy<IFoo> foo, ILazy<IBar> bar)
    {
        _foo = foo;
        _bar = bar;
    }
    
    public IFoo CreateFoo() => _foo.Value;
    public IBar CreateBar() => _bar.Value;
}

Lazy types are instantiated only when accessing the .Value property, which can improve performance.

Open generics are registered like this:

public void ConfigureServices(IServiceCollection services)
{
    // We register this as transient so it handles both singleton and scoped services
    // as the IServiceProvider injected will have the relevant lifetime.
    services.AddTransient(typeof(ILazy<>), typeof(LazyFactory<>));
}

If you want to add specific types when registering services, you can also directly use Lazy<T>:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<Lazy<IFoo>>(sp => new Lazy<IFoo>(() => sp.GetRequiredService<IFoo>()));
    services.AddTransient<Lazy<IBar>>(sp => new Lazy<IBar>(() => sp.GetRequiredService<IBar>()));
}

Types Inheriting Multiple Interfaces#

Suppose there is an implementation type (class) that inherits multiple interfaces (interface) and you want to expose it through the ID container. The built-in IServiceCollection does not support this, but you can use the following pattern.

public class FooAndBar : IFoo, IBar
{
   // Imagine a useful implementation
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<FooAndBar>();
    services.AddSingleton<IFoo>(sp => sp.GetRequiredService<FooAndBar>());
    services.AddSingleton<IBar>(sp => sp.GetRequiredService<FooAndBar>());
}

This way, resolving FooAndBar, IFoo, and IBar will yield the same instance.

Instantiating Types Using IServiceProvider#

Typically, types need to be registered to obtain an instance of that type from the DI container, such as IServiceProvider.GetService. This means the service needs to be registered in the container. If some types are dynamically discovered (like controllers in MVC), they cannot be resolved from the DI container.
Fortunately, Microsoft provides ActivatorUtilities, which is a type factory that resolves an instance of a specified type from IServiceProvider, and if that type is not registered in the DI container, it will directly create an instance of that type.

public class MyDependency
{
    public MyDependency(ILogger logger) {}
}
public class MyDependencyFactory
{
    private readonly IServiceProvider _serviceProvider;
    public MyDependencyFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public MyDependency GetInstance()
    {
        return ActivatorUtilities.CreateInstance<MyDependency>(_serviceProvider);
    }
}

You can build a more optimized version using CreateFactory, which pre-computes the constructor of the specified type and builds a factory method (which returns a delegate).

public class MyDependencyFactory
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ObjectFactory _factory;
    public MyDependencyFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        _factory = ActivatorUtilities.CreateFactory(typeof(MyDependency), Type.EmptyTypes);
    }
    public MyDependency GetInstance()
    {
        return (MyDependency) _factory(_serviceProvider, null);
    }
}

Warning

The DI container will not release instances created with this API.
So remember to manually Dispose() to release resources, otherwise these instances may occupy memory indefinitely, leading to memory leaks.
https://github.com/davidfowl/DotNetCodingPatterns/issues/13
Therefore, it is best to use it like this:

using (var scope = serviceProvider.CreateScope())
{
   var dbContext = scope.ServiceProvider.GetService<DBContext>();
   // ...
}

Caching Singleton in Generic Types#

If you need to cache an instance of a certain type, you can store it in a static field of a generic type.

public class Factory
{
    public T Create<T>() where T : new()
    {
        return Cache<T>.Instance;
    }
    
    private static class Cache<T> where T : new()
    {
        public static readonly T Instance = new();
    }
}

You can use JIT instead of the slower ConcurrentDictionary<Type, T> to cache instances. It can also be used to cache expensive object creation processes:

public class Factory
{
    public T Create<T>() where T : new()
    {
        return Cache<T>.Instance;
    }
    
    private static class Cache<T> where T : new()
    {
        public static readonly T Instance = CallExpensiveMethodToBuildANewInstance();
        
        private static T CallExpensiveMethodToBuildANewInstance()
        {
            // Assume a very complex process is involved to obtain an instance of type T
            return instanceOfT;
        }
    }
}

Accessing Services in DI Container When Using IOptions#

The options pattern applies to Microsoft.Extensions.* and Microsoft.AspNetCore.*. It allows anyone to register callbacks for updating the configuration repository of POCOs. Sometimes we want to access services when configuring options, and there are several ways to do this:

public class LibraryOptions
{
    public int Setting { get; set; }
}

public class MyConfigureOptions : IConfigureOptions<LibraryOptions>
{
    private readonly ISomeService _service;
    public MyConfigureOptions(ISomeService service)
    {
        _service = service;
    }
    
    public void Configure(LibraryOptions options)
    {
        options.Setting = _service.ComputeSetting();
    }
}

Then register:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IConfigureOptions<LibraryOptions>, MyConfigureOptions>();
}

However, this approach is too cumbersome, and Microsoft provides some helpers to simplify this process:

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions<LibraryOptions>()
            .Configure<ISomeService>((options, service) =>
            {
                options.Setting = service.ComputeSetting();
            });
}

Asynchronous Socket Server#

Here is an asynchronous server using modern .NET APIs:

  • Based on Task interface, asynchronous accept/read/write
  • Range syntax for slicing buffers
using var listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 8080));

Console.WriteLine($"Listening on {listenSocket.LocalEndPoint}");

listenSocket.Listen();

while (true)
{
    // Wait for a new connection to arrive
    var connection = await listenSocket.AcceptAsync();

    // We got a new connection spawn a task to so that we can echo the contents of the connection
    _ = Task.Run(async () =>
    {
        var buffer = new byte[4096];
        try
        {
            while (true)
            {
                int read = await connection.ReceiveAsync(buffer, SocketFlags.None);
                if (read == 0)
                {
                    break;
                }
                await connection.SendAsync(buffer[..read], SocketFlags.None);
            }
        }
        finally
        {
            connection.Dispose();
        }
    });
}

Asynchronous Socket Client#

Here is an asynchronous client using modern .NET APIs:

  • Task-based interface for asynchronous connection establishment
  • Creating a NetworkStream via Socket to use CopyToAsync, which is a helper that easily copies content from one Stream to another.
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 8080));

Console.WriteLine("Type into the console to echo the contents");

var ns = new NetworkStream(socket);
var readTask = Console.OpenStandardInput().CopyToAsync(ns);
var writeTask = ns.CopyToAsync(Console.OpenStandardOutput());

// Quit if any of the tasks complete
await Task.WhenAny(readTask, writeTask);

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.