loading...

【UnityContainer】Lifetime 1

masanori_msl profile image Masui Masanori ・6 min read

Intro

When I use DI with ASP.NET Core app, I choose "Microsoft.Extensions.DependencyInjection".

It has three types of lifetime of instance.

  • Transient
  • Scoped
  • Singleton

Dependency injection in ASP.NET Core | Microsoft Docs

Because I can't use "Microsoft.Extensions.DependencyInjection" with ASP.NET Framework, so when I use DI I choose Unity Container.

It also has "Transient", "Scoped", "Singleton".
"Transient"(every time creates new instances) and "Singleton"(creates only one instance in the application) are same as "Microsoft.Extensions.DependencyInjection".

But "Scoped" doesn't seem like "Microsoft.Extensions.DependencyInjection" one.
(creates instances per request)

Which types of lifetime should I choose to set "Scoped" of "Microsoft.Extensions.DependencyInjection"?

Environments

  • .NET Framework ver.4.8
  • Unity.Container ver.5.11.8
  • Unity.Mvc ver.5.11.1
  • NLog.Web ver.4.9.3

Sample projects

ProductsController.cs

using System.Web.Mvc;
using NetFrameworkSample.Lifetimes;
using NetFrameworkSample.Products;
using NLog;

namespace NetFrameworkSample.Controllers
{
    public class ProductsController: Controller
    {
        private readonly Logger _logger;
        private readonly IProductsService _product;
        private readonly ISecondService _secondService;
        public ProductsController(IProductsService product,
            ISecondService secondService)
        {
            _logger = LogManager.GetCurrentClassLogger();
            _logger.Debug("ProductsController Constructor");
            _product = product;
            _secondService = secondService;
        }
        [Route("")]
        public ActionResult GetMessage()
        {
            return View("~/Views/ProductPage.cshtml");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

ProductPage.cshtml

@{
    ViewBag.Title = "ProductPage";
}
<h2>ProductPage</h2>
@Html.ActionLink("Sample", "Index", "Lifetime")
Enter fullscreen mode Exit fullscreen mode

LifetimeController.cs

using NetFrameworkSample.Products;
using NLog;
using System.Web.Mvc;

namespace NetFrameworkSample.Lifetimes
{
    public class LifetimeController : Controller
    {
        private readonly Logger _logger;
        private readonly IProductsService _product;
        private readonly ISecondService _secondService;
        public LifetimeController(IProductsService product,
            ISecondService secondService)
        {
            _logger = LogManager.GetCurrentClassLogger();
            _logger.Debug("LifetimeController Constructor");
            _product = product;
            _secondService = secondService;
        }
        [Route("Lifetime")]
        public string Index()
        {
            return View("~/Views/Lifetime/Index.cshtml");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Index.cshtml

@{
    ViewBag.Title = "LifetimeSamplePage";
}
<h2>LifetimeSample</h2>
@Html.ActionLink("Sample", "GetMessage", "Products")
Enter fullscreen mode Exit fullscreen mode

IProductsService.cs

namespace NetFrameworkSample.Products
{
    public interface IProductsService
    { }
}
Enter fullscreen mode Exit fullscreen mode

ProductsService.cs

using NetFrameworkSample.Lifetimes;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace NetFrameworkSample.Products
{
    public class ProductsService: IProductsService
    {
        private readonly Logger _logger;
        private readonly IThirdService _thirdService;
        public ProductsService(IThirdService thirdService)
        {
            _logger = LogManager.GetCurrentClassLogger();
            _logger.Debug("ProductsService Constructor");
            _thirdService = thirdService;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

ISecondService.cs

namespace NetFrameworkSample.Lifetimes
{
    public interface ISecondService
    { }
}
Enter fullscreen mode Exit fullscreen mode

SecondService.cs

using NLog;

namespace NetFrameworkSample.Lifetimes
{
    public class SecondService: ISecondService
    {
        private readonly Logger _logger;
        private readonly IThirdService _thirdService;
        public SecondService(IThirdService thirdService)
        {
            _logger = LogManager.GetCurrentClassLogger();
            _logger.Debug("SecondService Constructor");
            _thirdService = thirdService;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

IThirdService.cs

namespace NetFrameworkSample.Lifetimes
{
    public interface IThirdService
    { }
}
Enter fullscreen mode Exit fullscreen mode

ThirdService.cs

using NLog;

namespace NetFrameworkSample.Lifetimes
{
    public class ThirdService: IThirdService
    {
        private readonly Logger _logger;
        public ThirdService()
        {
            _logger = LogManager.GetCurrentClassLogger();
            _logger.Debug("ThirdService Constructor");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

UnityMvcActivator.cs

using System.Linq;
using System.Web.Mvc;
using Unity.AspNet.Mvc;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(NetFrameworkSample.UnityMvcActivator), nameof(NetFrameworkSample.UnityMvcActivator.Start))]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(NetFrameworkSample.UnityMvcActivator), nameof(NetFrameworkSample.UnityMvcActivator.Shutdown))]

namespace NetFrameworkSample
{
    public static class UnityMvcActivator
    {
        public static void Start() 
        {
            FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
            FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(UnityConfig.Container));
            DependencyResolver.SetResolver(new UnityDependencyResolver(UnityConfig.Container));
        }
        public static void Shutdown()
        {
            UnityConfig.Container.Dispose();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Transient, Scoped, Singleton

I try changing lifetime and accessing "ProductsController" and "LifetimeController".
After that, I check logs to know when and how many times are instances created.

Transient

UnityConfig.cs

using System;
using Unity;
using NetFrameworkSample.Products;
using NetFrameworkSample.Lifetimes;

namespace NetFrameworkSample
{
    public static class UnityConfig
    {
        private static Lazy<IUnityContainer> container =
          new Lazy<IUnityContainer>(() =>
          {
              var container = new UnityContainer();
              RegisterTypes(container);
              container.RegisterType<IProductsService, ProductsService>(TypeLifetime.Transient);
              container.RegisterType<ISecondService, SecondService>(TypeLifetime.Transient);
              container.RegisterType<IThirdService, ThirdService>(TypeLifetime.Transient);
              return container;
          });
        public static IUnityContainer Container => container.Value;
        public static void RegisterTypes(IUnityContainer container)
        { }
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

--- Transient ---
--- Access "ProductsController" ---
2020-10-18 19:00:57.2576||DEBUG|NetFrameworkSample.Lifetimes.ThirdService|ThirdService Constructor |url: http://localhost/|action: 
2020-10-18 19:00:57.3281||DEBUG|NetFrameworkSample.Products.ProductsService|ProductsService Constructor |url: http://localhost/|action: 
2020-10-18 19:00:57.3281||DEBUG|NetFrameworkSample.Lifetimes.ThirdService|ThirdService Constructor |url: http://localhost/|action: 
2020-10-18 19:00:57.3281||DEBUG|NetFrameworkSample.Lifetimes.SecondService|SecondService Constructor |url: http://localhost/|action: 
2020-10-18 19:00:57.3281||DEBUG|NetFrameworkSample.Controllers.ProductsController|ProductsController Constructor |url: http://localhost/|action: 

--- Access "LifetimeController" ---
2020-10-18 19:03:57.6918||DEBUG|NetFrameworkSample.Lifetimes.ThirdService|ThirdService Constructor |url: http://localhost/Lifetime|action: 
2020-10-18 19:03:57.6918||DEBUG|NetFrameworkSample.Products.ProductsService|ProductsService Constructor |url: http://localhost/Lifetime|action: 
2020-10-18 19:03:57.6918||DEBUG|NetFrameworkSample.Lifetimes.ThirdService|ThirdService Constructor |url: http://localhost/Lifetime|action: 
2020-10-18 19:03:57.6918||DEBUG|NetFrameworkSample.Lifetimes.SecondService|SecondService Constructor |url: http://localhost/Lifetime|action: 
2020-10-18 19:03:57.6918||DEBUG|NetFrameworkSample.Lifetimes.LifetimeController|LifetimeController Constructor |url: http://localhost/Lifetime|action: 
Enter fullscreen mode Exit fullscreen mode

All instances were created each time.

Singleton

UnityConfig.cs

using System;
using Unity;
using NetFrameworkSample.Products;
using NetFrameworkSample.Lifetimes;

namespace NetFrameworkSample
{
    public static class UnityConfig
    {
        private static Lazy<IUnityContainer> container =
          new Lazy<IUnityContainer>(() =>
          {
              var container = new UnityContainer();
              RegisterTypes(container);
              container.RegisterType<IProductsService, ProductsService>(TypeLifetime.Singleton);
              container.RegisterType<ISecondService, SecondService>(TypeLifetime.Singleton);
              container.RegisterType<IThirdService, ThirdService>(TypeLifetime.Singleton);
              return container;
          });
        public static IUnityContainer Container => container.Value;
        public static void RegisterTypes(IUnityContainer container)
        { }
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

--- Singleton ---
--- Access "ProductsController" ---
2020-10-18 18:58:16.1776||DEBUG|NetFrameworkSample.Lifetimes.ThirdService|ThirdService Constructor |url: http://localhost/|action: 
2020-10-18 18:58:16.2536||DEBUG|NetFrameworkSample.Products.ProductsService|ProductsService Constructor |url: http://localhost/|action: 
2020-10-18 18:58:16.2536||DEBUG|NetFrameworkSample.Lifetimes.SecondService|SecondService Constructor |url: http://localhost/|action: 
2020-10-18 18:58:16.2536||DEBUG|NetFrameworkSample.Controllers.ProductsController|ProductsController Constructor |url: http://localhost/|action: 

--- Access "LifetimeController" ---
2020-10-18 18:59:39.8770||DEBUG|NetFrameworkSample.Lifetimes.LifetimeController|LifetimeController Constructor |url: http://localhost/Lifetime|action: 
Enter fullscreen mode Exit fullscreen mode

Exclude controller classes, all instances are created only one time.

Scoped

UnityConfig.cs

using System;
using Unity;
using NetFrameworkSample.Products;
using NetFrameworkSample.Lifetimes;

namespace NetFrameworkSample
{
    public static class UnityConfig
    {
        private static Lazy<IUnityContainer> container =
          new Lazy<IUnityContainer>(() =>
          {
              var container = new UnityContainer();
              RegisterTypes(container);
              container.RegisterType<IProductsService, ProductsService>(TypeLifetime.Scoped);
              container.RegisterType<ISecondService, SecondService>(TypeLifetime.Scoped);
              container.RegisterType<IThirdService, ThirdService>(TypeLifetime.Scoped);
              return container;
          });
        public static IUnityContainer Container => container.Value;
        public static void RegisterTypes(IUnityContainer container)
        { }
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

--- Scoped ---
--- Access "ProductsController" ---
2020-10-18 19:05:05.4487||DEBUG|NetFrameworkSample.Lifetimes.ThirdService|ThirdService Constructor |url: http://localhost/|action: 
2020-10-18 19:05:05.5336||DEBUG|NetFrameworkSample.Products.ProductsService|ProductsService Constructor |url: http://localhost/|action: 
2020-10-18 19:05:05.5336||DEBUG|NetFrameworkSample.Lifetimes.SecondService|SecondService Constructor |url: http://localhost/|action: 
2020-10-18 19:05:05.5336||DEBUG|NetFrameworkSample.Controllers.ProductsController|ProductsController Constructor |url: http://localhost/|action: 

--- Access "LifetimeController" ---
2020-10-18 19:05:28.1375||DEBUG|NetFrameworkSample.Lifetimes.LifetimeController|LifetimeController Constructor |url: http://localhost/Lifetime|action: 
Enter fullscreen mode Exit fullscreen mode

According to the results, the lifetime of "Scoped" is same as "Singleton".

What is "Scoped"?

According to the documents, when the lifetime is set as "Scoped", "Unity returns a unique value for each scope".

Now, I want to know, what is the scope?

According to the "Remarks", "this lifetime creates local singleton for each level of the hierarchy".
And by default, the application has only one container.

So in this sample, "Scoped" lifetime is as same as "Singleton".

How to create instance per request?

I also tried "ContainerControlled", "External", "Hierarchical", "PerContainer", "PerContainerTransient", "PerResolve", and "PerThread".
According to the result, I think I shall choose "PerResolve" to create instance per request.

UnityConfig.cs

using System;
using Unity;
using NetFrameworkSample.Products;
using NetFrameworkSample.Lifetimes;

namespace NetFrameworkSample
{
    public static class UnityConfig
    {
        private static Lazy<IUnityContainer> container =
          new Lazy<IUnityContainer>(() =>
          {
              var container = new UnityContainer();
              RegisterTypes(container);
              container.RegisterType<IProductsService, ProductsService>(TypeLifetime.PerResolve);
              container.RegisterType<ISecondService, SecondService>(TypeLifetime.PerResolve);
              container.RegisterType<IThirdService, ThirdService>(TypeLifetime.PerResolve);
              return container;
          });
        public static IUnityContainer Container => container.Value;
        public static void RegisterTypes(IUnityContainer container)
        { }
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

--- PerResolve ---
--- Access "ProductsController" ---
2020-10-19 00:13:47.1347||DEBUG|NetFrameworkSample.Lifetimes.ThirdService|ThirdService Constructor |url: http://localhost/|action: 
2020-10-19 00:13:47.2177||DEBUG|NetFrameworkSample.Products.ProductsService|ProductsService Constructor |url: http://localhost/|action: 
2020-10-19 00:13:47.2177||DEBUG|NetFrameworkSample.Lifetimes.SecondService|SecondService Constructor |url: http://localhost/|action: 
2020-10-19 00:13:47.2177||DEBUG|NetFrameworkSample.Controllers.ProductsController|ProductsController Constructor |url: http://localhost/|action: 

--- Access "LifetimeController" ---
2020-10-19 00:14:09.9652||DEBUG|NetFrameworkSample.Lifetimes.ThirdService|ThirdService Constructor |url: http://localhost/Lifetime|action: 
2020-10-19 00:14:09.9672||DEBUG|NetFrameworkSample.Products.ProductsService|ProductsService Constructor |url: http://localhost/Lifetime|action: 
2020-10-19 00:14:09.9672||DEBUG|NetFrameworkSample.Lifetimes.SecondService|SecondService Constructor |url: http://localhost/Lifetime|action: 
2020-10-19 00:14:09.9672||DEBUG|NetFrameworkSample.Lifetimes.LifetimeController|LifetimeController Constructor |url: http://localhost/Lifetime|action: 
Enter fullscreen mode Exit fullscreen mode

But I haven't understand how to use child containers.
So there may be any other way what I shall choose.
Next time, I will try.

Discussion

pic
Editor guide