前言 當我們使用DI方式寫了很多的Service後, 可能會發現我們的有些做法並不是最優的. 獲取註入的對象, 大家經常在構造函數中獲取, 這樣也是官方推薦的方式, 但有時不是效率最高的方法. 如果在構造函數中獲取對象,那麼每次對象的初始化都會把構造函數中的對象初始化一遍, 如果某個方法只用到其中一 ...
前言
當我們使用DI方式寫了很多的Service後, 可能會發現我們的有些做法並不是最優的.
獲取註入的對象, 大家經常在構造函數中獲取, 這樣也是官方推薦的方式, 但有時不是效率最高的方法.
如果在構造函數中獲取對象,那麼每次對象的初始化都會把構造函數中的對象初始化一遍, 如果某個方法只用到其中一個註入對象, 那麼其他的註入對象就白註入了
註入方法:
分別為構造函數、方法特性(FromServices)使用註入, 還有今天的主角 手動獲取服務註入
構造函數註入
/// <summary>
/// xxx服務
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class ValueController : ControllerBase
{
private readonly ILogger<ValueController> _logger;
private readonly IFreeSql _freeSql;
private readonly IOptions<ValueOptions> _valueOptions;
private readonly RoadService _roadService;
/// <summary>
/// 構造函數
/// </summary>
/// <returns></returns>
public RollerController(ILogger<ValueController> logger, IFreeSql freeSql, IOptions<ValueOptions> valueOptions, RoadService roadService)
{
_logger = logger;
_freeSql = freeSql;
_baiduOptions = baiduOptions;
_roadService = roadService;
}
/// <summary>
/// 獲取所有項目
/// </summary>
/// <returns></returns>
[HttpGet("project/all")]
public Task<List<ProjectDto>> GetProjects() => _freeSql.Select<Project>().ToListAsync<ProjectDto>();
/// <summary>
/// 獲取工程名下的道路
/// </summary>
/// <param name="projectId"></param>
/// <returns></returns>
[HttpGet("project/{projectId}/road")]
public Task<List<RoadDto>> GetRoads([FromRoute] Guid projectId) => _roadService.GetRoads(projectId);
}
方法特性(FromServices)註入
/// <summary>
/// 上傳數據
/// </summary>
/// <param name="gridDataService"></param>
/// <param name="dataList"></param>
/// <param name="roadId"></param>
/// <returns></returns>
[HttpPost("road/upload-grid-data/{roadId}")]
public async Task<IActionResult> UpdateRoadGridData([FromServices] GridDataService gridDataService, [FromBody] List<BizRoadGridInput> dataList, Guid roadId)
{
var data = await gridDataService.UploadGridData(dataList, roadId);
if (!data.ok) return BadRequest(data.message);
return Ok();
}
如上: 每個方法並不是都用到了構造函數中的服務, 所以我們這裡就有性能損失, 畢竟創建對象也是有代價的, 而且還會伴有GC.
手動獲取服務註入
在不是Controller中就不能使用[FromServices]特性了
為了能在個別的方法中註入對象就要用到手動獲取註冊對象的方式
如下:
在能直接拿到HttpContext時
/// <summary>
/// 處理道路信息
/// </summary>
/// <param name="roadId"></param>
/// <exception cref="ArgumentNullException"></exception>
[HttpPost("road/{roadid}/handle-road")]
public async Task<ActionResult> HandleRoadInfo([FromRoute] Guid roadId)
{
var imageService = HttpContext.GetRequiredService.GetRequiredService<ImageService>();
await imageService.HandleRoadInfo(roadId);
return Ok();
}
在不能直接拿到HttpContext時,需要先獲取IHttpContextAccessor
, 然後再獲取 HttpContext
上下文對象
/// <summary>
/// 圖片服務
/// </summary>
public class ImageService
{
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// 構造函數
/// </summary>
public ImageService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// 處理道路信息
/// </summary>
/// <param name="roadId"></param>
/// <exception cref="ArgumentNullException"></exception>
public async Task HandleRoadInfo(Guid roadId)
{
var roadService = _httpContextAccessor.HttpContext?.RequestServices.GetRequiredService<RoadService>();
var data = await roadService.GetRoadData(roadId);
// 省略其他
}
}
註意這裡面我們使用了GetRequiredService
, 而沒有使用GetService
, 因為使用GetRequiredService
不需要我們自己再去做空值檢查, 如果為空再很快就會失敗.
關於GetService 和 GetRequiredService
GetService()是IServiceProvider上的唯一方法,ISeviceProvider是ASP.NET核心DI抽象中的中央介面。第三方容器還可以實現可選介面ISupportRequiredService,該介面提供GetRequiredService()方法。當請求的類型serviceType可用時,這些方法的行為相同。如果服務不可用(即它沒有註冊),則GetService()返回null,而GetRequiredService()拋出一個InvalidOperationException。
GetRequiredService()相對於GetService()的主要好處是當服務不可用時,它允許第三方容器提供額外的診斷信息。因此,在使用第三方容器時最好使用GetRequiredService()。就個人而言,我會在任何地方使用它,即使我只使用內置的DI容器。
總結
將所有方法都用的service中使用構造函數註入是個優選方案, 將個別方法使用到的service使用手動獲取的方式代碼執行會更有效率
作者:wwmin
出處:https://www.cnblogs.com/cnwwm
聯繫:[email protected] 微信:w_wmin
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利。如有問題或建議,請多多賜教,非常感謝。