ASP.NET Core默認容器實現Controller的屬性注入


  • 僅針對Controller的屬性注入;
  • 使用默認容器,不依賴第三方庫;

故事背景

  閑來無事給項目做優化,發現大多數Controller里面都會用到Logger和AutoMapper,每個Controller都構造函數注入,感覺重復勞動太多了,ASP.NET Core默認容器也並不支持屬性注入。寫到基類里面通過構造注入ServiceProvider進行獲取?這樣每次都要傳遞ServiceProvider,也並不方便。嗯。。。有沒有辦法使用默認容器實現Controller的屬性注入呢。本着有問題先搜一搜的態度,找到了一篇相關文章

  文中使用替換IControllerActivator,並將Controller手動添加到DI容器(可以直接使用AddControllersAsServices拓展方法)的方式實現了屬性注入。也就是說必須使用從DI獲取Controller的方式,才能使用這種屬性注入的方式。不過之前看過一篇文章(找不到了。。。)里面的回復,大意為ASP.NET Core默認不使用DI獲取Controller,是因為DI容器構建完成后就不能變更了,但是Controller是可能有動態加載的需求的。嗯。。。本着最大兼容性的考慮,看看有沒有不干涉Controller創建方式也能實現這個功能的辦法。

想了個邏輯

  首先確認目的:在不改變ControllerActivator功能的前提下,為其生成的Controller設置屬性。具體的邏輯就有點繞了:我們需要實現一個設置Controller屬性的IControllerActivator(一個靜態代理類),並替換掉DI容器中的IControllerActivator聲明,但仍然需要DI容器構建之前已聲明的ControllerActivator(因為我們不知道具體如何構建),並且在我們實現的IControllerActivator中訪問(作為被代理的對象)。

針對每個邏輯:

  • 實現一個設置Controller屬性的IControllerActivator靜態代理:這個很好寫;
  • 用靜態代理類替換掉DI容器中的IControllerActivator聲明:這個也很簡單;
  • DI容器保留之前已聲明的ControllerActivator:這個可以直接將IControllerActivator聲明中的實現類型作為服務添加到ServiceCollection中;
  • 在靜態代理類中訪問上一步添加到ServiceCollectionIControllerActivator具體實現:這個問題有點小麻煩。因為靜態代理類和原始的IControllerActivator實現類都需要使用DI容器構建,那么靜態代理類只能通過構造函數注入IControllerActivator實現類,但是我們沒辦法在寫代碼的時候就確定要注入的類型,也不能直接依賴IControllerActivator(自己依賴自己了)。想來想去。。。嗯。。。我們可以使用泛型實現代理類的邏輯,並在添加服務描述時動態生成一個泛型類型。

寫代碼

首先需要一個ASP.NET Core項目。

關鍵代碼

嗯。。屬性注入的關鍵點,只要有ServiceProvider,其它的就好說了。

  • 那就先寫一個IServiceProviderSetter放在這里
using System;

public interface IServiceProviderSetter
{
    void SetServiceProvider(IServiceProvider serviceProvider);
}
  • 實現泛型靜態代理
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;

public class ControllerActivatorProxy<TControllerActivator> : IControllerActivator where TControllerActivator : IControllerActivator
{
    private readonly TControllerActivator _originalControllerActivator;

    public ControllerActivatorProxy(TControllerActivator originalControllerActivator)
    {
        //引用原始ControllerActivator
        _originalControllerActivator = originalControllerActivator;
    }

    public object Create(ControllerContext context)
    {
        //使用原始ControllerActivator創建controller
        var controller = _originalControllerActivator.Create(context);

        if (controller is IServiceProviderSetter serviceProviderSetter)
        {
            //具體要干什么由controller內部決定
            serviceProviderSetter.SetServiceProvider(context.HttpContext.RequestServices);
        }

        return controller;
    }

    public void Release(ControllerContext context, object controller)
    {
        _originalControllerActivator.Release(context, controller);
    }
}
  • 替換服務描述,保留之前已聲明的ControllerActivator。為了方便,寫個拓展方法吧。
using System.Linq;

using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

public static class AutowiredControllerServiceProviderExtensions
{
    public static void AutowiredControllerServiceProvider(this IMvcBuilder mvcBuilder)
    {
        var services = mvcBuilder.Services;

        //查找原始服務描述
        var serviceDescriptor = services.Where(m => m.ServiceType == typeof(IControllerActivator)).First();

        var existedServiceType = serviceDescriptor.ImplementationType;

        //動態創建代理類型
        var controllerActivatorType = typeof(ControllerActivatorProxy<>).MakeGenericType(existedServiceType);

        //將原始實現直接添加到services中
        services.Add(ServiceDescriptor.Describe(existedServiceType, existedServiceType, serviceDescriptor.Lifetime));

        //替換IControllerActivator服務為使用動態構建的代理類型
        services.Replace(ServiceDescriptor.Describe(typeof(IControllerActivator), controllerActivatorType, serviceDescriptor.Lifetime));
    }
}

嗯。。這好像有點有趣的地方。。

關鍵代碼寫完了,接下來是使用

  • 建立一個Controller基類並應用屬性
using System;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

[ApiController]
[Route("api/[controller]")]
public class TestBaseController : ControllerBase, IServiceProviderSetter
{
    //logger只是個示例,僅在使用時才創建,實際還可以直接獲取,或者使用Lazy<T>等各種操作。其它屬性同理。
    private ILogger _logger;
    private IServiceProvider _serviceProvider;

    protected ILogger Logger
    {
        get
        {
            if (_logger != null)
            {
                return _logger;
            }
            _logger = _serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(GetType());
            return _logger;
        }
    }

    [NonAction]
    public void SetServiceProvider(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
}
  • Startup中配置使用
public void ConfigureServices(IServiceCollection services)
{
    //使用默認Controller生成方式
    services.AddControllers().AutowiredControllerServiceProvider();
    //使用DI生成Controller方式
    //services.AddControllers().AddControllersAsServices().AutowiredControllerServiceProvider();
}
  • 示例Controller,這里就直接使用默認生成的Controller示范了
using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

public class WeatherForecastController : TestBaseController
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        Logger.LogInformation("Start");
        var rng = new Random();
        var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
        Logger.LogInformation("End");

        return result;
    }
}

好了,可以看效果了

僅僅是個思路,僅供參考。代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM