Explore ASP.Net Core 3.0 series three: ASP.Net Core 3.0 The Service provider validation

Introduction: In this article, I will describe the new ASP.NET Core 3.0 "validate on build" function. This can be used to detect whether your DI service provider configuration errors. Specifically, this function can detect your dependence are not registered in DI container services. First of all, I will show this feature works, and then give some scenes in these scenarios, you may have a misconfigured DI container, but this feature is not recognized as a problem.

 

 

A series exploring ASP.NET Core 3.0: new project file, Program.cs and generic host

Explore ASP.Net Core 3.0 Series II: talk ASP.Net Core 3.0 of Startup.cs

Explore ASP.Net Core 3.0 Series 4: running asynchronous tasks when you start the application in ASP.NET Core 3.0

Explore ASP.Net Core 3.0 series five: introducing IHostLifetime and start interacting clarify Generic Host

Explore ASP.Net Core 3.0 Series Six: ASP.NET Core 3.0 new features start structured log information

First, a simple APP

In this article, I will use the application based on the default dotnet new webapi template. It is composed by a single controller WeatherForecastService, the controller returns the randomly generated data in accordance with some static data.

For a little practice DI container, I will extract some services. First, the controller reconstructed as follows:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly WeatherForecastService _service;
    public WeatherForecastController(WeatherForecastService service)
    {
        _service = service;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        return _service.GetForecasts();
    }
}

Thus, the controller dependent WeatherForecastService. As follows (I have omitted the actual implementation, because it is not important in this article):

public class WeatherForecastService
    {
        private readonly DataService _dataService;
        public WeatherForecastService(DataService dataService)
        {
            _dataService = dataService;
        }

        public IEnumerable<WeatherForecast> GetForecasts()
        {
            var data = _dataService.GetData();

            // use data to create forcasts

            return new List<WeatherForecast>{ new WeatherForecast {

                Date = DateTime.Now,
                TemperatureC = 31,
                Summary="Sweltering",


            } };
        }
    }

 

 

This service is dependent on other DataService, as follows:

public class DataService
{
    public string[] GetData() => new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
}

That's all the services we need, so the rest is to register them DI container.

Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSingleton<WeatherForecastService>();
    services.AddSingleton<DataService>();
}

 

In this example, I have them registered as single cases, but this is not important for this feature. After everything is set up properly, the / WeatherForecast transmission request returns the corresponding data:

 

 

 Everything here looks very good, so let's see what we messed up if DI register would happen.

 

Second, the detection of unregistered dependency on startup

Let's change the code, and then "forget" to register DataService Dependency in DI container:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSingleton<WeatherForecastService>();
    // services.AddSingleton<DataService>();
}

If we use dotnet run run the application again, an exception occurs, a stack trace, and the application can not be started. I've been cut off and format the following results:

 

 

 This error is very clear - "Try not resolve 'TestApp.DataService' type of service is activated 'TestApp.WeatherForecastService'". This is DI verification, it should help reduce the number of errors during the DI applications up and running discovery. It is not as useful to compile-time error, but this is the cost of flexibility provided by the DI container.

If we forget to register WeatherForecastService how to do:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    // services.AddSingleton<WeatherForecastService>();
    services.AddSingleton<DataService>();
}

In this case, the application can start! It is not wonder! Let's see how it is, in the end what pitfalls, understand these traps we can avoid a lot of problems in everyday development.

(1) does not check for constructor dependency controller

Verification reason not to solve this problem is not to create dependency controller controller DefaultControllerActivator get from DI container using DI container, rather than the controller itself. Therefore, DI container on the controller has no knowledge, and therefore unable to check whether its dependencies have been registered.

Fortunately, there is a workaround. You can change the controller activates the device to use AddControllersAsServices on IMvcBuilder () method to add a controller to the DI container:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddControllersAsServices(); // Add the controllers to DI

    // services.AddSingleton<WeatherForecastService>();
    services.AddSingleton<DataService>();
}

 

This will enable ServiceBasedControllerActivator, and a controller registered to DI container as a service. If we now run the application, verify that the missing will be detected when the application starts controller-dependent, with an exception:

 

 

 This seems to be a convenient solution, but I'm not sure what you want to weigh, but it should be good (after all, this is a supported scenario). However, we have not out of the woods, because the only way to not dependency injection constructor injection ......

(2) does not check for dependency [FromServices] injected

How to control [FromBody] and [FromQuery] The attributes incoming request action method to create the parameter using a model of the binding MVC actions. Similarly, [FromServices] Properties can be applied to methods of operation parameters, and these parameters are created by taking the DI container. If you have dependencies required only a single method of operation, this feature is useful. Without service by constructor injection DI container (and thus create a service for each action on the controller), but may be injected into the particular action.

For example, we can rewrite WeatherForecastController using [FromServices] injection, as follows:

[ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<WeatherForecast> Get(
            [FromServices] WeatherForecastService service) // injected using DI
        {
            return service.GetForecasts();
        }
    }

Obviously, there is no reason to do so, but it is very important. Unfortunately, DI verification will not be detected using this service is not registered (regardless of whether you add AddControllersAsServices). The application can be started, but when you try to call the operation will throw an exception.

A simple solution is to avoid, where possible, use [FromServices] property, it should not be difficult to achieve, if you need to use, you can always injected through the constructor.

There is another method for obtaining services from a DI container - to use the service position.

(3) does not check directly from the IServiceProvider service

Let us again so WeatherForecastController. We will be injected directly into the IServiceProvider, rather than direct injection WeatherForecastService, and use the service position anti-pattern to retrieve dependencies.

using Microsoft.Extensions.DependencyInjection;

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly WeatherForecastService _service;
    public WeatherForecastController(IServiceProvider provider)
    {
        _service = provider.GetRequiredService<WeatherForecastService>();
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        return _service.GetForecasts();
    }
}

 

Where you inject IServiceProvider, like this code is usually not a good idea, in addition to such an approach enables developers to be more difficult than reasoning, this also means that DI verification procedures do not understand dependencies. Therefore, the application can start properly.

Unfortunately, you can not always avoid using the IServiceProvider. There is a case where: Do you have a singleton object requires dependency scope. In another situation: You have a singleton object can not have a constructor dependency, such as authentication attribute. Unfortunately, these cases can not be solved.

 

(4) using the factory function does not check registration service

Let us return to the original controller, will WeatherForecastService injected into the constructor, then use AddControllersAsServices () registered controller DI container. However, we will make two changes:

  • Forget registered DataService.
  • Creating WeatherForecastService use the factory functions.

When it comes to the factory function refers to the lambda services provided during registration, which describes how to create the service. E.g:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddControllersAsServices();
    services.AddSingleton<WeatherForecastService>(provider => 
    {
        var dataService = new DataService();
        return new WeatherForecastService(dataService);
    });
    // services.AddSingleton<DataService>(); // not required

}

 

In the example above, we provide a lambda is WeatherForecastService, which describes how to create the service. Within the lambda, we manually constructed DataService and WeatherForecastService. This will not cause any problems in our application because we were able to get WeatherForecastService from DI container using the factory method. We never have to parse DataService directly from the DI container. We only need to WeatherForecastService it, and we are manually constructed it, so there is no problem.

If we use injection IServiceProvider provider in factory functions, the problem occurs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddControllersAsServices();
    services.AddSingleton<WeatherForecastService>(provider => 
    {
        var dataService = provider.GetRequiredService<DataService>();
        return new WeatherForecastService(dataService);
    });
    // services.AddSingleton<DataService>(); // Required!
}

 On DI verification purposes, this plant with a functionally identical functionality, but in fact there is a problem. We are using the IServiceProvider use the Service Locator pattern at run time to resolve DataService. So we have an implicit dependency. This is actually the same as the Trap 3 - Service Provider verification program can not detect cases directly from the service provider access to services. As in previous pitfalls, such code is sometimes necessary, and there is no easy way to solve it. If this is the case, please take extra care to make sure you request dependencies correctly registered. 

(5) does not check for an open generic type

Look at an example, for example, suppose we have a generic ForcastService <T>, it can generate a plurality of types.

public class ForecastService<T> where T: new()
{
    private readonly DataService _dataService;
    public ForecastService(DataService dataService)
    {
        _dataService = dataService;
    }

    public IEnumerable<T> GetForecasts()
    {
        var data = _dataService.GetData();

        // use data to create forcasts

        return new List<T>();
    }
}

In Startup.cs, we registered the generics, but again forget to register DataService:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        AddControllersAsServices();

    // register the open generic
    services.AddSingleton(typeof(ForecastService<>));
    // services.AddSingleton<DataService>(); // should cause an error
}

Service provider to verify completely skip the registration of generics, so it never detected a loss DataService dependencies. No error launch the application, and attempting to initiate a request when an abnormal operation ForecastService <T>.

However, if you use a closed version of this dependency in your application anywhere in (which is likely), then the verification will detect this problem. For example, we can update WeatherForecastController by T WeatherForecast close as generic to the use of generic services:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly ForecastService<WeatherForecast> _service;
    public WeatherForecastController(ForecastService<WeatherForecast> service)
    {
        _service = service;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        return _service.GetForecasts();
    }
}

 

Verification service provider does detect this! Therefore, in practice, the lack of open generic test may not be as important as the factory and service locator function traps. You always need to close a generic to inject it into the service (unless the service itself is an open generic), so we hope you can choose many cases. The exception is if you want to use the service locator IServiceProvider to get open generics, then in any case, and in fact you are returned to the trap 3 and 4!

 

Third, enable the service to verify in other environments

I know this is the last trap, it is worth remembering that, by default, only enabled the service provider to verify the development environment. That is because it has start-up costs, with the scope to verify the same. However, if you have any type of "conditions of service registration", registered in the Development of the service and registration of different services in other environments, you may also wish to enable verification in other environments. You can add to the default master builder in Program.cs in a UseDefaultServiceProvider call to achieve. In the following example, I have enabled ValidateOnBuild in all environments, but retaining only the scope of verification in development:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            // Add a new service provider configuration
            .UseDefaultServiceProvider((context, options) =>
            {
                options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                options.ValidateOnBuild = true;
            });

 

IV Summary

In this article, I described the .NET Core 3.0 features new in ValidateOnBuild. This allows Microsoft.Extensions DI container inspection service configuration errors when you first build a service provider. This can be used to detect problems when the application starts, rather than detecting misconfigured service at runtime. While useful, but in many cases can not be verified, for example, IServiceProvider service locator injected into MVC controllers, as well as generics. You can solve some of these problems, but even if you do not solve these problems, they should keep in mind, and do not rely on your application to resolve 100% of the DI problem!

 

 

 

Translation: Andrew Lock    https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/

 

Author: Guo Zheng

Source: http: //www.cnblogs.com/runningsmallguo/

This article belongs to the author and blog Park total, welcome to reprint, but without the author's consent declared by this section must be retained, and given the original article page link in the apparent position.

Guess you like

Origin www.cnblogs.com/runningsmallguo/p/11617165.html