7

Tôi đang cố gắng tìm ra cách sử dụng tốt nhất lớp HttpClient trong ASP.Net Core.Cách tốt nhất để sử dụng HTTPClient trong ASP.Net Core là DI Singleton

Theo tài liệu và một số bài viết, lớp được khởi tạo tốt nhất một lần trong suốt thời gian tồn tại của ứng dụng và được chia sẻ cho nhiều yêu cầu. Rất tiếc, tôi không thể tìm thấy ví dụ về cách thực hiện chính xác điều này trong Core nên tôi đã đưa ra giải pháp sau.

Nhu cầu cụ thể của tôi yêu cầu sử dụng 2 thiết bị đầu cuối khác nhau (tôi có APIServer cho logic nghiệp vụ và máy chủ định hướng API), vì vậy tôi nghĩ là có 2 bộ đơn HttpClient mà tôi có thể sử dụng trong ứng dụng.

Tôi đã cấu hình servicepoints tôi trong appsettings.json như sau:

"ServicePoints": { 
"APIServer": "http://localhost:5001", 
"ImageServer": "http://localhost:5002", 
} 

Tiếp theo, tôi đã tạo ra một HttpClientsFactory rằng sẽ nhanh chóng 2 httpclients của tôi và giữ chúng trong một từ điển tĩnh.

public class HttpClientsFactory : IHttpClientsFactory 
{ 
    public static Dictionary<string, HttpClient> HttpClients { get; set; } 
    private readonly ILogger _logger; 
    private readonly IOptions<ServerOptions> _serverOptionsAccessor; 

    public HttpClientsFactory(ILoggerFactory loggerFactory, IOptions<ServerOptions> serverOptionsAccessor) { 
     _logger = loggerFactory.CreateLogger<HttpClientsFactory>(); 
     _serverOptionsAccessor = serverOptionsAccessor; 
     HttpClients = new Dictionary<string, HttpClient>(); 
     Initialize(); 
    } 

    private void Initialize() 
    { 
     HttpClient client = new HttpClient(); 
     // ADD imageServer 
     var imageServer = _serverOptionsAccessor.Value.ImageServer; 
     client.BaseAddress = new Uri(imageServer); 
     HttpClients.Add("imageServer", client); 

     // ADD apiServer 
     var apiServer = _serverOptionsAccessor.Value.APIServer; 
     client.BaseAddress = new Uri(apiServer); 
     HttpClients.Add("apiServer", client); 
    } 

    public Dictionary<string, HttpClient> Clients() 
    { 
     return HttpClients; 
    } 

    public HttpClient Client(string key) 
    { 
     return Clients()[key]; 
    } 
    } 

Sau đó, tôi đã tạo giao diện mà tôi có thể sử dụng khi xác định DI sau này. Lưu ý rằng lớp HttpClientsFactory thừa kế từ giao diện này.

public interface IHttpClientsFactory 
{ 
    Dictionary<string, HttpClient> Clients(); 
    HttpClient Client(string key); 
} 

Bây giờ tôi đã sẵn sàng để đưa điều này vào thùng chứa Phụ thuộc của tôi như sau trong lớp Khởi động theo phương pháp ConfigureServices.

// Add httpClient service 
     services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>(); 

Tất cả bây giờ được thiết lập để bắt đầu sử dụng điều này trong bộ điều khiển của tôi.
Thứ nhất, tôi nhận sự phụ thuộc. Để làm điều này, tôi đã tạo một thuộc tính lớp riêng để giữ nó, sau đó thêm nó vào chữ ký hàm tạo và kết thúc bằng cách gán đối tượng đến cho thuộc tính lớp cục bộ.

private IHttpClientsFactory _httpClientsFactory; 
public AppUsersAdminController(IHttpClientsFactory httpClientsFactory) 
{ 
    _httpClientsFactory = httpClientsFactory; 
} 

Cuối cùng, giờ đây chúng tôi có thể sử dụng Nhà máy để yêu cầu htppclient và thực hiện cuộc gọi. Bên dưới, ví dụ tôi yêu cầu hình ảnh từ máy chủ hình ảnh bằng cách sử dụng httpclientsfactory:

[HttpGet] 
    public async Task<ActionResult> GetUserPicture(string imgName) 
    { 
     // get imageserver uri 
     var imageServer = _optionsAccessor.Value.ImageServer; 

     // create path to requested image 
     var path = imageServer + "/imageuploads/" + imgName; 

     var client = _httpClientsFactory.Client("imageServer"); 
     byte[] image = await client.GetByteArrayAsync(path); 

     return base.File(image, "image/jpeg"); 
    } 

Xong!

Tôi đã thử nghiệm tính năng này và nó hoạt động tốt trên môi trường phát triển của tôi. Tuy nhiên, tôi không chắc chắn nếu đây là cách tốt nhất để thực hiện điều này. Tôi vẫn còn với các câu hỏi sau:

  1. Chủ đề giải pháp này có an toàn không? (theo tài liệu MS: ‘Mọi thành viên tĩnh công cộng (Được chia sẻ trong Visual Basic) thuộc loại này đều an toàn.’)
  2. Thiết lập này có thể xử lý tải nặng mà không cần mở nhiều kết nối riêng biệt không?
  3. Việc cần làm trong lõi ASP.Net để xử lý sự cố DNS được mô tả trong ‘Singleton HttpClient? Cẩn thận với hành vi nghiêm trọng này và cách khắc phục. ’Tại số http://byterot.blogspot.be/2016/07/singleton-httpclient-dns.html
  4. Bất kỳ cải tiến hoặc đề xuất nào khác?
+0

cách tiếp cận thú vị, tôi có HttpClient phương pháp tĩnh như một dịch vụ nhưng tôi dint nghĩ về một mô hình nhà máy . Tôi đã tự hỏi phải thử điều này dưới một API yêu cầu xác thực hoặc trường hợp API của bạn là một API mở? Bạn sẽ xử lý như thế nào cho các yêu cầu khác nhau yêu cầu các mã thông báo khác nhau. –

+0

@MuqeetKhan câu trả lời của tôi cho câu hỏi của bạn đã hạ cánh lâu hơn một chút rồi dự đoán. Vì vậy, xin vui lòng tìm thấy nó dưới đây với một ví dụ. – Laobu

+0

Bạn không cần hai phiên bản 'HttpClient'. Chỉ cần đăng ký một singleton và sử dụng nó. Vấn đề DNS sẽ vẫn tồn tại. Là một sidenote, các lớp 'Factory' ** tạo ** một cá thể đối tượng. Đọc thêm về mẫu Nhà máy [tại đây] (https://msdn.microsoft.com/en-us/library/ee817667.aspx). – gldraphael

Trả lời

0

Để trả lời câu hỏi từ @MuqeetKhan về việc sử dụng xác thực với yêu cầu httpclient.

Thứ nhất, động lực của tôi để sử dụng DI và nhà máy là cho phép tôi mở rộng ứng dụng của mình dễ dàng với nhiều API khác nhau và dễ dàng truy cập vào mã đó trong suốt mã của tôi. Đó là một mẫu mà tôi hy vọng có thể sử dụng lại nhiều lần.

Trong trường hợp bộ điều khiển 'GetUserPicture' của tôi được giải thích trong câu hỏi ban đầu ở trên, tôi thực sự vì lý do đơn giản đã xóa xác thực. Trung thực Tuy nhiên, tôi vẫn còn nghi ngờ nếu tôi cần nó ở đó để chỉ cần lấy một hình ảnh từ máy chủ hình ảnh. Nhưng dù sao, trong các bộ điều khiển khác, tôi chắc chắn cần nó, vì vậy…

Tôi đã triển khai Identityserver4 làm máy chủ xác thực của mình. Điều này cung cấp cho tôi xác thực trên ASP Identity. Để ủy quyền (sử dụng vai trò trong trường hợp này), tôi đã triển khai IClaimsTransformer trong các dự án API MVC ‘và’ của tôi (bạn có thể đọc thêm về điều này tại đây How to put ASP.net Identity Roles into the Identityserver4 Identity token).

Bây giờ, thời điểm tôi nhập bộ điều khiển của mình, tôi có người dùng được xác thực và được ủy quyền để tôi có thể truy xuất mã thông báo truy cập. Tôi sử dụng mã thông báo này để gọi api của tôi là tất nhiên gọi cùng một ví dụ của máy chủ nhận dạng để xác minh nếu người dùng được xác thực.

Bước cuối cùng là cho phép API của tôi xác minh xem người dùng có được phép gọi bộ điều khiển api được yêu cầu không. Trong đường dẫn yêu cầu của API sử dụng IClaimsTransformer như đã giải thích trước đây, tôi truy xuất ủy quyền của người dùng đang gọi và thêm nó vào các xác nhận quyền sở hữu đến. Lưu ý rằng trong trường hợp cuộc gọi và API MVC, do đó, tôi truy xuất ủy quyền 2 lần; một lần trong đường dẫn yêu cầu MVC và một lần trong đường dẫn yêu cầu API.

Sử dụng thiết lập này Tôi có thể sử dụng HttpClientsFactory của mình với Ủy quyền và xác thực.

Trên phần bảo mật lớn, tôi thiếu là HTTPS tất nhiên. Tôi hy vọng tôi bằng cách nào đó có thể thêm nó vào nhà máy của tôi. Tôi sẽ cập nhật nó một khi tôi đã thực hiện nó.

Như mọi khi, mọi đề xuất đều được hoan nghênh.

Dưới đây là ví dụ tôi tải hình ảnh lên Máy chủ hình ảnh bằng cách sử dụng xác thực (người dùng phải đăng nhập và có quản trị vai trò).

khiển MVC của tôi gọi 'UploadUserPicture':

[Authorize(Roles = "Admin")] 
    [HttpPost] 
    public async Task<ActionResult> UploadUserPicture() 
    { 
     // collect name image server 
     var imageServer = _optionsAccessor.Value.ImageServer; 

     // collect image in Request Form from Slim Image Cropper plugin 
     var json = _httpContextAccessor.HttpContext.Request.Form["slim[]"]; 

     // Collect access token to be able to call API 
     var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token"); 

     // prepare api call to update image on imageserver and update database 
     var client = _httpClientsFactory.Client("imageServer"); 
     client.DefaultRequestHeaders.Accept.Clear(); 
     client.SetBearerToken(accessToken); 
     var content = new FormUrlEncodedContent(new[] 
     { 
      new KeyValuePair<string, string>("image", json[0]) 
     }); 
     HttpResponseMessage response = await client.PostAsync("api/UserPicture/UploadUserPicture", content); 

     if (response.StatusCode != HttpStatusCode.OK) 
     { 
      return StatusCode((int)HttpStatusCode.InternalServerError); 
     } 
     return StatusCode((int)HttpStatusCode.OK); 
    } 

xử lý người sử dụng API tải lên

[Authorize(Roles = "Admin")] 
    [HttpPost] 
    public ActionResult UploadUserPicture(String image) 
    { 
    dynamic jsonDe = JsonConvert.DeserializeObject(image); 

     if (jsonDe == null) 
     { 
      return new StatusCodeResult((int)HttpStatusCode.NotModified); 
     } 

     // create filname for user picture 
     string userId = jsonDe.meta.userid; 
     string userHash = Hashing.GetHashString(userId); 
     string fileName = "User" + userHash + ".jpg"; 

     // create a new version number 
     string pictureVersion = DateTime.Now.ToString("yyyyMMddHHmmss"); 

     // get the image bytes and create a memory stream 
     var imagebase64 = jsonDe.output.image; 
     var cleanBase64 = Regex.Replace(imagebase64.ToString(), @"^data:image/\w+;base64,", ""); 
     var bytes = Convert.FromBase64String(cleanBase64); 
     var memoryStream = new MemoryStream(bytes); 

     // save the image to the folder 
     var fileSavePath = Path.Combine(_env.WebRootPath + ("/imageuploads"), fileName); 
     FileStream file = new FileStream(fileSavePath, FileMode.Create, FileAccess.Write); 
     try 
     { 
      memoryStream.WriteTo(file); 
     } 
     catch (Exception ex) 
     { 
      _logger.LogDebug(LoggingEvents.UPDATE_ITEM, ex, "Could not write file >{fileSavePath}< to server", fileSavePath); 
      return new StatusCodeResult((int)HttpStatusCode.NotModified); 
     } 
     memoryStream.Dispose(); 
     file.Dispose(); 
     memoryStream = null; 
     file = null; 

     // update database with latest filename and version 
     bool isUpdatedInDatabase = UpdateDatabaseUserPicture(userId, fileName, pictureVersion).Result; 

     if (!isUpdatedInDatabase) 
     { 
      return new StatusCodeResult((int)HttpStatusCode.NotModified); 
     } 

     return new StatusCodeResult((int)HttpStatusCode.OK); 
    } 
Các vấn đề liên quan