2017年6月1日 星期四

[ASP.NET Core] Dependency Injection service lifetime

 ASP.NET Core   Dependency Injection   Lifetime


Introduction


We can inject built-in framework services or our services in ASP.NET Core by configuring the container's services in Startup.cs:  ConfigureServices method.

See more information on Dependency Injection in As

We will learn the lifetime of the four injection ways,

1.  Transient
New instance is provided to every controller and every service.

2.  Scoped
Service are created once per request.

3.  Singleton
Single instance throughout the application, lazy singleton.

4.  Singleton Instance
Create instance when registered, eager singleton.



Environment


.NET Core 2.0.0 preview 1



Sample


Create custom services

Here we will create several interfaces to identify Transient, Scoped, Singleton, Singleton-instance services.

Service interface

public interface IGuidService
{
    string Title { get; set; }
    Guid Id { get; set; }
}

public interface IGuidServiceTransient : IGuidService
{}
public interface IGuidServiceScoped : IGuidService
{}
public interface IGuidServiceSingleton : IGuidService
{}
public interface IGuidServiceSingletonInstance : IGuidService
{}


Service class

public class GuidService : IGuidService, IGuidServiceTransient, IGuidServiceScoped, IGuidServiceSingleton, IGuidServiceSingletonInstance
{
    public GuidService(string title, Guid? guid = null)
    {
        this.Title = title;

        if (guid != null && !guid.Equals(Guid.Empty))
            this.Id = (Guid)guid;
        else
            this.Id = Guid.NewGuid();

        //Leave a log when the service instance is created
        LogUtility.Logger.Warn($"{title} : completed constructor.");
    }

    public string Title { get; set; }
    public Guid Id { get; set; }

}



Register services

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    //Transient
    services.AddTransient<IGuidServiceTransient>(provider => new GuidService(title: "Transient"));
    //Scoped
    services.AddScoped<IGuidServiceScoped>(provider => new GuidService(title: "Scoped"));
    //Singleton
    services.AddSingleton<IGuidServiceSingleton>(provider => new GuidService(title: "Singleton"));
    //Singleton instance
    services.AddSingleton<IGuidServiceSingletonInstance>(new GuidService("Instance", Guid.Empty));
}




Inject services to see the lifetime

We will log the GUID when receiving a request.

Controller

public class DIdemoController : Controller
    {
        private IGuidServiceTransient _guidServiceTransient = null;
        private IGuidServiceScoped _guidServiceScoped = null;

        private IGuidServiceSingleton _guidServiceSingleton = null;
        private IGuidServiceSingletonInstance _guidServiceSingletonInstance = null;

        public DIdemoController(
            IGuidServiceTransient guidServiceTransient,
            IGuidServiceScoped guidServiceScoped,
            IGuidServiceSingleton guidServiceSingleton,
            IGuidServiceSingletonInstance guidServiceSingletonInstance
             )
        {
            this._guidServiceTransient = guidServiceTransient;
            this._guidServiceScoped = guidServiceScoped;
            this._guidServiceSingleton = guidServiceSingleton;
            this._guidServiceSingletonInstance = guidServiceSingletonInstance;
        }

        public IActionResult Index()
        {
            _logger.Debug($"Action: Index");

            _logger.Info($"Transient : {this._guidServiceTransient.Id}");
            _logger.Info($"Scoped : {this._guidServiceScoped.Id}");
            _logger.Info($"Singleton : {this._guidServiceSingleton.Id}");
            _logger.Info($"Instance : {this._guidServiceSingletonInstance.Id}");
            return View();
        }

}


Result on first and second request



The result shows that the Singleton service was created only ONCE on demand (receiving a request), thus the two requests get the same GUID.
However, Transient and Scoped services were created every time we send a request.
But what the difference between Transient and Scoped ones? We will talk about it later on the next demo.

Notice that we didn’t have any construction log with the Singleton-Instance service, why? The reason is that the Singleton-Instance service was created when we registered it on this line:
services.AddSingleton<IGuidServiceSingletonInstance>(new GuidService("Instance", Guid.Empty));

And thaz why I called it an eager singleton.
However my log utility was initialized after the above code, so we didn’t get any log when it was created :- )


Inject services to tell the difference between Transient and Scoped

Let’s modify the MVC controller to inject multiple services on Transient and Scoped.

Controller

public class DIdemoController : Controller
    {
        private IGuidServiceTransient _guidServiceTransient = null;
        private IGuidServiceScoped _guidServiceScoped = null;

        private IGuidServiceTransient _guidServiceTransientDup = null;
        private IGuidServiceScoped _guidServiceScopedDup = null;

        public DIdemoController(
            IGuidServiceTransient guidServiceTransient,
            IGuidServiceScoped guidServiceScoped,
             IGuidServiceTransient guidServiceTransientDup,
             IGuidServiceScoped guidServiceScopedDup)
        {
            this._guidServiceTransient = guidServiceTransient;
            this._guidServiceScoped = guidServiceScoped;
            this._guidServiceTransientDup = guidServiceTransientDup;
            this._guidServiceScopedDup = guidServiceScopedDup;
        }

        public IActionResult Index()
        {
            _logger.Debug($"Action: Index");

            _logger.Info($"Transient : {this._guidServiceTransient.Id}");
            _logger.Info($"Scoped : {this._guidServiceScoped.Id}");
            _logger.Info($"Transient(2) : {this._guidServiceTransientDup.Id}");
            _logger.Info($"Scoped(2) : {this._guidServiceScopedDup.Id}");
            return View();
        }

}


Result



We found that Scoped service was only created once! And Transient service was created on every injection. So the request got the same GUID on Scoped service.


Summary


How we choose the right way to inject our service? Here are some suggestions.
1.  Use Transient on stateless service.
2.  Use Scoped to ensure the same instance in a request.
3.  Use Singleton to keep or deal with application level information, but it should be a thread-safe service.
4.  Use Singleton-Instance to create instance on Application startup for application level usage.



Reference




沒有留言:

張貼留言