歡迎光臨
每天分享高質量文章

ASP.NET Core如何在ActionFilterAttribute裡做依賴註入

在ASP.NET Core裡,我們可以使用建構式註入很方便地對Controller,ViewComponent等部件做依賴註入。但是如何給過濾器ActionFilterAttribute也用上建構式註入呢?

問題

我的部落格系統裡有個用來刪除訂閱檔案快取的ActionFilter,想要在發生異常的時候記錄日誌。我的部落格用的日誌元件是NLog,因此不使用依賴註入的話,就直接使用LogManager.GetCurrentClassLogger()獲得一個Logger的實體。整個過濾器的程式碼如下:

public class DeleteSubscriptionCache : ActionFilterAttribute

{

    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

    public override void OnActionExecuted(ActionExecutedContext context)

    {

        base.OnActionExecuted(context);

        DeleteSubscriptionFiles();

    }

    private void DeleteSubscriptionFiles()

    {

        try

        {

            // …

        }

        catch (Exception e)

        {

            Logger.Error(e, “Error Delete Subscription Files”);

        }

    }

}

然後在Action上去使用,和經典的ASP.NET MVC一樣

[Authorize]

[HttpPost, ValidateAntiForgeryToken, DeleteSubscriptionCache]

[Route(“manage/edit”)]

public IActionResult Edit(PostEditModel model)

這當然可以沒有問題的執行,但寫程式碼最重要的就是逼格,這個程式碼耦合了NLog,而我的部落格系統裡其他地方早就在用ASP.NET Core的ILogger介面了。如果哪天日誌元件不再用NLog了,那麼這個地方的程式碼就得改,而使用ILogger介面的程式碼就不需要動。雖然這種情況是絕對不會發生的,但是寫程式碼一定要有追求,盡可能過度設計,才能不被人鄙視,然後才能面試造航母,工作擰螺絲。因此我決定把日誌元件用依賴註入的方式安排一下。

改造過濾器

方法和在Controller中使用依賴註入完全一樣,我們使用建構式註入ILogger型別。於是程式碼變成了這樣:

public class DeleteSubscriptionCache : ActionFilterAttribute

{

    protected readonly ILogger Logger;

    public DeleteSubscriptionCache(ILogger logger)

    {

        Logger = logger;

    }

    public override void OnActionExecuted(ActionExecutedContext context)

    {

        base.OnActionExecuted(context);

        DeleteSubscriptionFiles();

    }

    private void DeleteSubscriptionFiles()

    {

        try

        {

            // …

        }

        catch (Exception e)

        {

            Logger.LogError(e, “Error Delete Subscription Files”);

        }

    }

}

但是問題來了,這樣的話我們是沒法在Action上無腦使用了,因為建構式要求傳參。如果要自己new一個的話,裝逼就失敗了。我們來看看正確的解決方法~

ServiceFilter

其實ASP.NET Core裡,我們可以使用ServiceFilter來完成這個需求。它也是一種Attribute,可以作用在Action上。位於Microsoft.AspNetCore.Mvc.Core程式集裡,定義如下:

// A filter that finds another filter in an System.IServiceProvider.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]

public class ServiceFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter

{

        public ServiceFilterAttribute(Type type);

        public int Order { get; set; }

        public Type ServiceType { get; }

        public bool IsReusable { get; set; }

        public IFilterMetadata CreateInstance(IServiceProvider serviceProvider);

}

ServiceFilter允許我們解析一個已經新增到IoC容器裡的服務,因此我們需要把DeleteSubscriptionCache註冊一下:

services.AddScoped();

然後就能直接使用了:

[Authorize]

[HttpPost, ValidateAntiForgeryToken]

[ServiceFilter(typeof(DeleteSubscriptionCache))]

[Route(“manage/edit”)]

public IActionResult Edit(PostEditModel model)

執行時發現ILogger已經能被實體化了,完美!

參考資料:

https://stackoverflow.com/questions/36109052/inject-service-into-action-filter/36109690

https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2

贊(0)

分享創造快樂