起因
這兩天,我忽然有點懷念 Asp.NET MVC 5 之前的時代,原因是我看到專案裡面有這麼一段程式碼(其實不止一段,幾乎每個 Controller 都是)
[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 之前,我們可以把上面的程式碼簡化為下面的形式:
[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 檢視原始碼
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/2a89caed05a1bc9f06d32e15d984cd21598ab6fb
這哥們的 Commit Message 很簡潔:限制 FromServices 僅作用於 parameters 。高手過招,人狠話不多,刀刀致命!從此,廣大 .NETCore 開發者告別了屬性注入。經過我不懈努力的搜尋後,發現其實在 Pranav K 提交程式碼兩天後,他居然自己開了一個 Issue,你說氣人不?
關於廢除 FromServices 的討論
https://github.com/aspnet/Mvc/issues/3578
在這個貼子裡面,許多開發者表達了自己的不滿,我還看到了有人像我一樣,表達了自己想要一個簡潔的建構函式的這樣樸素的請求;但是,對於屬性注入可能導致濫用的問題也產生了激烈的討論,還有屬性注入要求成員必須標記為 public 這些硬性要求,不得不說,這個帖子成功的引起了人們的注意,但是很明顯,作者不打算修改 FromServices 支援屬性注入。
自己動手,豐衣足食
沒關係,官方沒有自帶的話,我們自己動手做一個也是一樣的效果,在此之前,我們還應該關注另外一種從 service 中獲取例項的方式,就是常見的通過 HttpContext 請求上下文獲取服務例項的方式:
var obj = HttpContext.RequestServices.GetService(typeof(Type));
上面的這種方式,其實是反模式的,官方也建議儘量避免使用,說完了廢話,就自動動手擼一個屬性注入特性類:PropertyFromServiceAttribute
[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
** 如果你喜歡這篇文章,請給我點贊,讓更多同學可以看到,筆芯~