vendredi 4 août 2017

Dependency injection in ASP.NET Core [DI ASP.NET Core] Part 2


ASP.NET Core and Injection Dependencies
First new thing with ASP.NET Core, the injection of dependencies is considered directly as a main module of ASP.NET. This means that it is no longer necessary to consider a DependencyResolver only for ASP.NET MVC but rather a set of classes and interfaces present in the core of ASP.NET and usable in any code that works with ASP.NET (MVC, SignalR, etc.).
Another difference in size, the now-present basic mechanism does not play a simple role as class activator as it was before, but it is a full-fledged IoC container. While it does not support all the features offered by the most complex containers available in the .NET world, it will suffice for the needs of most developers.
Note that in basic case, this whole dependency injection mechanism is compatible with .NET Core, and it is well available in the light version of the .NET Framework.
The core of the mechanism revolves around the Microsoft.Framework.DependencyInjection and Microsoft.Framework.DependencyInjection.Abstractions packages. These are almost automatically present in your application as they are a dependency of Microsoft.AspNet.Hosting.
It's simple to use this container. If you opened an ASP.NET Core application, you probably noticed a ConfigureServices method in the Startup class. The IServiceCollection parameter simply represents the IoC container. It is with this collection that you will have to register your services.
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
To register a service in the collection, it should be defined as the life cycle management strategy to be adopted. This principle is generally common to all IOC containers. The following policies are available with the ASP.NET 5 container.
  • Instance. You provide an instance to the container and the container will reuse it all the time. He will never create another.
  • Transient. This is the most common behavior. A new instance is simply created whenever a component invokes the container.
  • Singleton. The container will create the first instance and keep it for as long as the application runs.
  • Scoped. An instance is created and reused for the entire lifetime of a specific scope.
The latter case is particularly interesting. In a Web application, the most commonly used scope is that of the HTTP request. This means that in this mode, it is possible to have a single instance of a service that will be shared throughout the processing time of an HTTP request.
In other words, all your services that are based on a single component can retrieve the same as a singleton during the processing of a query. As soon as the next request is made on your application, a new singleton will be instantiated. Basing on this, here are some examples of how to use the container.
The service is registered in such a way that each request to the container must instantiate the DependencyInjectionServiceclass.
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IDependencyInjectionService
, DependencyInjectionService
>();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
Below is another example where I register my service as a singleton and where I am responsible for providing the instance to use.
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddInstance<IDependencyInjectionService
>(new DependencyInjectionService
());
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
Note that when you write services.AddMvc to make your ASP.NET MVC 6 application functional, you simply save the various components needed to run ASP.NET MVC. Below, an extract of the code of the AddMvc method. Note the cascading of the different methods. Also note that in this version of ASP.NET MVC, everything is really considered as components registered in the IoC container.
public static IServiceCollection AddMvc([NotNull] this IServiceCollection services)
{
    var builder = services.AddMvcCore();

    builder.AddApiExplorer();
    builder.AddAuthorization();
    builder.AddCors();
    builder.AddDataAnnotations();
    builder.AddFormatterMappings();
    builder.AddJsonFormatters();
    builder.AddViews();
    builder.AddRazorViewEngine();

    return services;
}

public static IMvcBuilder AddMvcCore(
    [NotNull] this IServiceCollection services,
    [NotNull] Action<MvcOptions> setupAction)
{
    ConfigureDefaultServices(services);

    AddMvcCoreServices(services);

    services.Configure(setupAction);

    return new MvcBuilder() { Services = services, };
}

internal static void AddMvcCoreServices(IServiceCollection services)
{
    // ...

    //
    // Controller Factory
    //
    // This has a cache, so it needs to be a singleton
    services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>();

    // Will be cached by the DefaultControllerFactory
    services.TryAddTransient<IControllerActivator, DefaultControllerActivator>();
    services.TryAddEnumerable(
        ServiceDescriptor.Transient<IControllerPropertyActivator, DefaultControllerPropertyActivator>());

    // ...
}
Build Your Own Container
he ASP.NET NET Core IoC mechanism is based on a set of interfaces. This level of abstraction is such that it is easily possible to replace the IoC container present by default with another implementation. It is then possible to run the entire ASP.NET stack on the container of your choice (Unity, StructureMap, Autofac, etc.), allowing you to access the functionalities specific to these containers or allowing you to simply respect The standards of your project.
For this scenario to be possible, it is nevertheless necessary to have an implementation of these abstractions adapted to the container of your choice. Generally, it is the developers of a container that provide this kind of implementation.
It should also be noted that it is possible to write the ConfigureServices method of the Startup class in another form. It is this other form that allows the use of a house container. Note that this time the return type has changed since it became an IServiceProvider. This is the abstraction that represents the IoC container.
public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddInstance<IDependencyInjectionService
>(new DependencyInjectionService
());

        throw new NotImplementedException();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
Below is a possible implementation based on Autofac (attention the packets are still in alpha). Note that the Populate method is in a specific package (Autofac.Dnx). It is the one that contains the whole method of linking the classic functioning of Autofac with the layers of abstractions of ASP.NET Core.
public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddInstance<IDependencyInjectionService
>(new DependencyInjectionService
());

        var builder = new ContainerBuilder();

        builder.Populate(services);

        var container = builder.Build();

        return container.Resolve<IServiceProvider>();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }

Conclusion

Integrating injection of dependencies as an ASP.NET core brick is a real good news for developers. It allows beginners to easily learn the concept, without even adding additional libraries. And for developers accustomed to the concept, it allows having a coherent and elegant solution with all levels of abstraction needed to replace the default operation by a specific container.

0 commentaires:

Enregistrer un commentaire