[C#][MVC]Per Request Singleton - 使用Unity

在寫MVC網站時

如何讓物件在每次的請求都不一樣呢?(例如:Entity Framework的DbEntities)

本篇會使用Unity所提供的方法

首先你可能會問 :  直接套Singleton Pattern不就好了?

是的,一開始我也是這麼想的

不過測試之後發現不行(不然就不會發文了orz)

為什麼不行呢

目前大部分MVC網站都是架在IIS上時(dotnet core除外)

IIS在收到Request一段時間後執行緒才會自殺(有講錯請指教)

所以實際上你在這段時間內用Singleton拿到的物件會是同一個

如果你使用Singleton Pattern或Unity所提供的RegisterSingleton去註冊物件時

然後連續發送兩次請求

你會發現第一次跟第二次拿到的其實是同一個物件

 

關於Unity

我也只是入門而已

可以參考Kevin大的文章

或者請益G神

 

 

那麼在實作一開始

請先建立一個空白的MVC專案

並加入一個DateTimeRecorder類別

我們會利用觀察這個物件被建立的時間

來判斷是否有達到PerRequestSingleton的效果

DateTimeRecorder.cs

public class DateTimeRecorder
{
    private DateTime date;

    public DateTimeRecorder()
    {
        date = DateTime.Now;
    }

    public string GetRecordTime() => date.ToLongTimeString();
}

 

接著使用Nuget安裝Unity.Mvc5

它會順便幫你把Unity一起安裝

 

[實作一 - 使用Unity內建的Singleton做測試]

裝好之後

在專案目錄App_Start資料夾底下

它會自動幫你加入一個UnityConfig.cs

接著將DateTimeRecorder註冊為Singleton

public static class UnityConfig
{
    public static void RegisterComponents()
    {
	var container = new UnityContainer();

        // register all your components with the container here
        // it is NOT necessary to register your controllers

        // e.g. container.RegisterType<ITestService, TestService>();
        container.RegisterSingleton<DateTimeRecorder>();

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }
}

 

好了之後記得修改Global.asax的Application_Start

UnityConfig註冊上去

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    UnityConfig.RegisterComponents();
 }


然後請在HomeController.cs宣告一個建構式注入

接著把時間回傳到Index上

public class HomeController : Controller
{
    private readonly DateTimeRecorder _recorder;

    public HomeController(DateTimeRecorder recorder)
    {
        this._recorder = recorder;
    }

    public ActionResult Index()
    {
        var recordTime = _recorder.GetRecordTime();

        return View(recordTime as object);
    }
}

這裡的recordTime還要轉成object

是因為它會預設吃到其他簽章

記得要改一下Index.cshtml

@model string

@{
    ViewBag.Title = "Home Page";
}

<h2>DateTimeRecorder建立時間: @Model </h2>

<h2>現在時間: @(DateTime.Now.ToLongTimeString())</h2>

 

好的,接著開始第一次Request

按下F5發送第二次Request

比較了一下兩次DateTimeRecorder被建立的時間

發現是一樣的結果(在IIS執行緒自殺之前應該都是這個時間)

由此可得知使用Singleton並不能達到我們要的PerRequestSingleton

 

 

[實作二 - 使用Unity.Mvc內建的PerRequestLifetimeManager做測試]

其實Unity內建有提供對應的LifetimeManager

但如果你跟我一樣一開始就裝Unity.Mvc5(想說比較新嘛)

那就會很悲劇的找不到

因為這個方法只活在Unity.Mvc裡(不知道為什麼Unity.Mvc5沒有)

所以請記得使用Nuget再次安裝Unity.Mvc

好了之後一樣修改一下UnityConfig.cs

public static class UnityConfig
{
    public static void RegisterComponents()
    {
	var container = new UnityContainer();

        // register all your components with the container here
        // it is NOT necessary to register your controllers

        // e.g. container.RegisterType<ITestService, TestService>();
        //container.RegisterSingleton<DateTimeRecorder>();
        container.RegisterType<DateTimeRecorder>(new PerRequestLifetimeManager());

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }
}

 

好了之後一樣執行測試

接著一樣重新整理

可以發現DateTimeRecorder的建立時間會隨著每次Request有所不同

這樣一來就達到PerRequestSingleton的效果囉!

下篇再補充如何在不使用Unity的情況達到一樣的效果