Asp.NETCore讓FromServices回來

原由

這兩天,我突然有點懷念 Asp.NET MVC 5 以前的時代,緣由是我看到項目裏面有這麼一段代碼(其實不止一段,幾乎每一個 Controller 都是)git

[Route("home")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        private readonly IConfiguration configuration;
        private readonly IHostingEnvironment environment;
        private readonly CarService carService;
        private readonly PostServices postServices;
        private readonly TokenService tokenService;
        private readonly TopicService topicService;
        private readonly UserService userService;

        public HomeController(IConfiguration configuration,
                              IHostingEnvironment environment,
                              CarService carService,
                              PostServices postServices,
                              TokenService tokenService,
                              TopicService topicService,
                              UserService userService)
        {
            this.configuration = configuration;
            this.environment = environment;
            this.carService = carService;
            this.postServices = postServices;
            this.tokenService = tokenService;
            this.topicService = topicService;
            this.userService = userService;
        }

        [HttpGet("index")]
        public ActionResult<string> Index()
        {
            return "Hello world!";
        }
    }

在構造函數裏面聲明瞭一堆依賴注入的實例,外面還得聲明相應的接收字段,使用代碼克隆掃描,零零散散的充斥在各個 Controller 的構造函數中。在 Asp.NET MVC 5 以前,咱們能夠把上面的代碼簡化爲下面的形式:github

[Route("home")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [FromServices] public IConfiguration Configuration { get; set; }
        [FromServices] public IHostingEnvironment Environment { get; set; }
        [FromServices] public CarService CarService { get; set; }
        [FromServices] public PostServices PostServices { get; set; }
        [FromServices] public TokenService TokenService { get; set; }
        [FromServices] public TopicService TopicService { get; set; }
        [FromServices] public UserService UserService { get; set; }

        public HomeController()
        {
        }

        [HttpGet("index")]
        public ActionResult<string> Index()
        {
            return "Hello world!";
        }
    }

可是,在 .NETCore 中,上面的這斷代碼是會報錯的,緣由就是特性:FromServicesAttribute 只能應用於 AttributeTargets.Parameter,導航到 FromServicesAttribute 查看源碼app

namespace Microsoft.AspNetCore.Mvc
{
    /// <summary>
    /// Specifies that an action parameter should be bound using the request services.
    /// </summary>
    /// <example>
    /// In this example an implementation of IProductModelRequestService is registered as a service.
    /// Then in the GetProduct action, the parameter is bound to an instance of IProductModelRequestService
    /// which is resolved from the request services.
    ///
    /// <code>
    /// [HttpGet]
    /// public ProductModel GetProduct([FromServices] IProductModelRequestService productModelRequest)
    /// {
    ///     return productModelRequest.Value;
    /// }
    /// </code>
    /// </example>
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
    public class FromServicesAttribute : Attribute, IBindingSourceMetadata
    {
        /// <inheritdoc />
        public BindingSource BindingSource => BindingSource.Services;
    }
}

那麼問題來了,AttributeUsage 是何時移除了 AttributeTargets.Property 呢?答案是:2015年11月17日,是一個叫作 Pranav K 的哥們革了 FromServiceAttribute 的命,下面是他的代碼提交記錄函數

Limit [FromServices] to apply only to parameters
https://github.com/aspnet/Mvc/commit/2a89caed05a1bc9f06d32e15d984cd21598ab6fbpost

這哥們的 Commit Message 很簡潔:限制 FromServices 僅做用於 parameters 。高手過招,人狠話很少,刀刀致命!今後,廣大 .NETCore 開發者告別了屬性注入。通過我不懈努力的搜索後,發現其實在 Pranav K 提交代碼兩天後,他竟然本身開了一個 Issue,你說氣人不?this

關於廢除 FromServices 的討論
https://github.com/aspnet/Mvc/issues/3578spa

在這個貼子裏面,許多開發者表達了本身的不滿,我還看到了有人像我同樣,表達了本身想要一個簡潔的構造函數的這樣樸素的請求;可是,對於屬性注入可能致使濫用的問題也產生了激烈的討論,還有屬性注入要求成員必須標記爲 public 這些硬性要求,不得不說,這個帖子成功的引發了人們的注意,可是很明顯,做者不打算修改 FromServices 支持屬性注入。code

本身動手,豐衣足食

不要緊,官方沒有自帶的話,咱們本身動手作一個也是同樣的效果,在此以前,咱們還應該關注另一種從 service 中獲取實例的方式,就是常見的經過 HttpContext 請求上下文獲取服務實例的方式:對象

var obj = HttpContext.RequestServices.GetService(typeof(Type));

上面的這種方式,實際上是反模式的,官方也建議儘可能避免使用,說完了廢話,就自動動手擼一個屬性注入特性類:PropertyFromServiceAttributetoken

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class PropertyFromServiceAttribute : Attribute, IBindingSourceMetadata
{
    public BindingSource BindingSource => BindingSource.Services;
}

沒有多餘的代碼,就是標記爲 AttributeTargets.Property 便可

應用到類成員
[Route("home")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [PropertyFromService] public IConfiguration Configuration { get; set; }
        [PropertyFromService] public IHostingEnvironment Environment { get; set; }
        [PropertyFromService] public CarService CarService { get; set; }
        [PropertyFromService] public PostServices PostServices { get; set; }
        [PropertyFromService] public TokenService TokenService { get; set; }
        [PropertyFromService] public TopicService TopicService { get; set; }
        [PropertyFromService] public UserService UserService { get; set; }

        public HomeController()
        {

        }

        [HttpGet("index")]
        public ActionResult<string> Index()
        {
            return "Hello world!";
        }
    }

請大聲的回答,上面的代碼是否是很是的乾淨整潔!可是,像上面這樣使用屬性注入有一個小問題,在對象未初始化以前,該屬性爲 null,意味着在類的構造函數中,該成員變量不可用,不過沒關係,這點小問題徹底可用經過在構造函數中注入解決;更重要的是,並不是每一個實例都須要在構造函數中使用,是吧。

示例代碼

託管在 Github 上了 https://github.com/lianggx/Examples/tree/master/Ron.DI

** 若是你喜歡這篇文章,請給我點贊,讓更多同窗能夠看到,筆芯~

相關文章
相關標籤/搜索