ASP.NET MVC AutoFac Dependency Injection

2024-10-09

筆記 ASP.NET 開發中使用 AutoFac 進行依賴注入的筆記。

logo

說明

  • 透過參數的方式選擇不同的 Service 實作類別進行注入
  • 批次自動註冊 Interface 與實作類別的方式

Scoped

  • InstancePerDependency (transient)
  • SingleInstance
  • InstancePerLifetimeScope
  • InstancePerRequest

快速開始

使用 ASP.NET MVC 5 需要搭配的是 Autofac.Mvc5 套件,直接在 GUI 進行搜尋與安裝即可。

接著建立一個 AutoFac\AutoFacModule.cs 用來定義注入的類別。

using Autofac;
using System.Collections.Generic;

public class AutoFacModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.Register(c => new List<string> {}).As<IList<string>>().SingleInstance();
    }
}

示範將一個 List<string> 註冊為 IList<string> 並設定為 Singleton Pattern,驗證不同的 Request 會共用相同的 Instance。

接著在 Global.asax.cs 中註冊 AutoFacModule

using Autofac;
using Autofac.Integration.Mvc;

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

    var builder = new ContainerBuilder();
    builder.RegisterControllers(typeof(MvcApplication).Assembly);
    builder.RegisterModule(new AutoFacModule());

    var container = builder.Build();
    DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}

以上就完成註冊,接著就可以在 Controller 中,透過建構子注入 IList<string>,並且作為 _stringList 的私有類別成員,在 Action 當中使用。

private readonly IList<string> _stringList;
public HomeController(IList<string> stringList)
{
    _stringList = stringList;
}

public ActionResult Index()
{
    _stringList.Add(new Random().Next(1, 100).ToString());

    ViewBag.StringList = _stringList;
    return View();
}

Instance And Class Dependency Injection

接著示範如何在 ASP.NET MVC 中進行 Service 的注入,透過建立一個 Service 介面與 Service 實作類別,並且透過 DbContext 取得資料。

public interface INorthwindService
{
    IEnumerable<Product> GetProducts();

    IEnumerable<Employee> GetEmployees();
}

public class NorthwindService : INorthwindService
{
    private readonly NorthwindEntities _context;
    public NorthwindService(NorthwindEntities context)
    {
        _context = context;
    }
    
    public IEnumerable<Product> GetProducts()
    {
        return _context.Products.ToList();
    }

    public IEnumerable<Employee> GetEmployees()
    {
        return _context.Employees.ToList();
    }
}

接著在 AutoFacModule 中註冊 IServiceService

protected override void Load(ContainerBuilder builder)
{
    builder.RegisterType<NorthwindEntities>().AsSelf().InstancePerDependency();
    builder.RegisterType<NorthwindService>().As<INorthwindService>().SingleInstance();
}

接著在 Controller 中透過建構子注入 IService,並且在 Action 中使用。

private readonly INorthwindService northwindService;
public HomeController(INorthwindService northwindService)
{
    _service = service;
}

public ActionResult About()
{
    return View(_northwindService.GetEmployees());
}

以上的設定方式如同下圖所示:


因為 Service 是 Singleton Pattern,而 dbContext 是 InstancePerDependency,所以每次 Request 都會建立新的 dbContext 重複向資料庫取得資料。

我們可以加上快取機制,在快取時間內不重複向資料庫取得資料。

public class NorthwindService : INorthwindService
{
    private readonly NorthwindEntities _context;
    private DateTime _lastUpdate;
    private IEnumerable<Employee> _cacheEmployees;

    public NorthwindService(NorthwindEntities context)
    {
        _context = context;
    }

    public IEnumerable<Employee> GetEmployees()
    {
        if (_cacheEmployees == null || DateTime.Now.Subtract(_lastUpdate).TotalSeconds > 10)
        {
            _cacheEmployees = _context.Employees.ToList();
            _lastUpdate = DateTime.Now;
        }
        return _cacheEmployees;
    }
}

批次自動註冊

builder.RegisterAssemblyTypes(typeof(WebApplication).Assembly)
    .Where(t => t.Name.EndsWith("Repository"))
    .AsImplementedInterfaces()
    .InstancePerLifetimeScope();

注入 HttpContext 相關服務

global.asax.cs 中加入 builder.RegisterModule<AutofacWebTypesModule>

var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);

// 註冊 HttpContextBase
builder.RegisterModule<AutofacWebTypesModule>();

builder.RegisterModule(new AutoFacModule());

var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

相較於以往要透過 HttpContext.Current 取得 HttpContext,透過 AutoFac 可以直接透過建構子注入 HttpContextBase

步驟多了,但帶來的好處是單元測試時可以透過 Mock 的方式注入 HttpContextBase,同時也可以控管 HttpContext 的生命週期與解耦 Service 對於 HttpContext 的依賴。

// 以往的方式
HttpContext httpContext = HttpContext.Current;

// AutoFac 注入方式
private readonly HttpContextBase _httpContext;
public HomeController(HttpContextBase httpContext)
{
    _httpContext = httpContext;
}