Lao*_*obu 10 c# dotnet-httpclient asp.net-core
我试图弄清楚如何最好地使用ASP.Net Core中的HttpClient类.
根据文档和几篇文章,该类最好在应用程序的生命周期中实例化一次,并为多个请求共享.不幸的是,我找不到如何在Core中正确执行此操作的示例,因此我提出了以下解决方案.
我的特殊需求需要使用2个不同的端点(我有一个用于业务逻辑的APIServer和一个API驱动的ImageServer),所以我的想法是拥有2个可以在应用程序中使用的HttpClient单例.
我在appsettings.json中配置了我的服务点,如下所示:
"ServicePoints": {
"APIServer": "http://localhost:5001",
"ImageServer": "http://localhost:5002",
}
Run Code Online (Sandbox Code Playgroud)
接下来,我创建了一个HttpClientsFactory,它将实例化我的2个httpclients并将它们保存在静态Dictionary中.
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];
}
}
Run Code Online (Sandbox Code Playgroud)
然后,我创建了以后在定义我的DI时可以使用的界面.请注意,HttpClientsFactory类继承自此接口.
public interface IHttpClientsFactory
{
Dictionary<string, HttpClient> Clients();
HttpClient Client(string key);
}
Run Code Online (Sandbox Code Playgroud)
现在我准备将它注入我的Dependency容器,如下所示,在ConfigureServices方法下的Startup类中.
// Add httpClient service
services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>();
Run Code Online (Sandbox Code Playgroud)
所有现在都设置为在我的控制器中开始使用它.
首先,我接受了依赖.为此,我创建了一个私有类属性来保存它,然后将它添加到构造函数签名中,并通过将传入对象分配给本地类属性来完成.
private IHttpClientsFactory _httpClientsFactory;
public AppUsersAdminController(IHttpClientsFactory httpClientsFactory)
{
_httpClientsFactory = httpClientsFactory;
}
Run Code Online (Sandbox Code Playgroud)
最后,我们现在可以使用Factory来请求htppclient并执行调用.下面是我使用httpclientsfactory从imageserver请求图像的示例:
[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");
}
Run Code Online (Sandbox Code Playgroud)
完成!
我已经测试了它,它在我的开发环境中运行良好.但是,我不确定这是否是实现此目的的最佳方式.我仍然提出以下问题:
gar*_*thb 28
如果使用 .net core 2.1 或更高版本,最好的方法是使用新的HttpClientFactory. 我猜微软意识到人们遇到的所有问题,所以他们为我们做了艰苦的工作。有关如何设置,请参见下文。
注意:添加对Microsoft.Extensions.Http.
1 - 添加一个使用 HttpClient 的类
public interface ISomeApiClient
{
Task<HttpResponseMessage> GetSomethingAsync(string query);
}
public class SomeApiClient : ISomeApiClient
{
private readonly HttpClient _client;
public SomeApiClient (HttpClient client)
{
_client = client;
}
public async Task<SomeModel> GetSomethingAsync(string query)
{
var response = await _client.GetAsync($"?querystring={query}");
if (response.IsSuccessStatusCode)
{
var model = await response.Content.ReadAsJsonAsync<SomeModel>();
return model;
}
// Handle Error
}
}
Run Code Online (Sandbox Code Playgroud)
2 -ConfigureServices(IServiceCollection services)在 Startup.cs 中注册您的客户
var someApiSettings = Configuration.GetSection("SomeApiSettings").Get<SomeApiSettings>(); //Settings stored in app.config (base url, api key to add to header for all requests)
services.AddHttpClient<ISomeApiClient, SomeApiClient>("SomeApi",
client =>
{
client.BaseAddress = new Uri(someApiSettings.BaseAddress);
client.DefaultRequestHeaders.Add("api-key", someApiSettings.ApiKey);
});
Run Code Online (Sandbox Code Playgroud)
3 - 在您的代码中使用客户端
public class MyController
{
private readonly ISomeApiClient _client;
public MyController(ISomeApiClient client)
{
_client = client;
}
[HttpGet]
public async Task<IActionResult> GetAsync(string query)
{
var response = await _client.GetSomethingAsync(query);
// Do something with response
return Ok();
}
}
Run Code Online (Sandbox Code Playgroud)
您可以添加任意数量的客户端并根据需要在启动时注册 services.AddHttpClient
感谢 Steve Gordon 和他在这里的帖子帮助我在我的代码中使用它!
回答 @MuqeetKhan 有关使用 httpclient 请求进行身份验证的问题。
\n\n首先,我使用 DI 和工厂的动机是让我能够轻松地将我的应用程序扩展到不同的多个 API\xe2\x80\x99,并且可以在整个代码中轻松访问它。它\xe2\x80\x99是我希望能够多次重复使用的模板。
\n\n对于上面原始问题中描述的 \xe2\x80\x98GetUserPicture\xe2\x80\x99 控制器,我确实出于简单原因删除了身份验证。但老实说,我仍然怀疑我是否需要它来简单地从图像服务器检索图像。无论如何,在其他控制器中我肯定需要它,所以\xe2\x80\xa6
\n\n我\xe2\x80\x99已经实现了Identityserver4作为我的身份验证服务器。这为我提供了基于 ASP Identity 的身份验证。\n为了授权(在本例中使用角色),我在 MVC \xe2\x80\x98and\xe2\x80\x99 API 项目中实现了 IClaimsTransformer(您可以在此处阅读如何将ASP.net Identity Roles 放入Identityserver4 身份令牌)。
\n\n现在,当我进入控制器时,我就有了一个经过身份验证和授权的用户,我可以检索访问令牌。我使用此令牌来调用我的 api,这当然会调用同一 IDServer 实例来验证用户是否已通过身份验证。
\n\n最后一步是允许我的 API 验证用户是否有权调用所请求的 api 控制器。如前所述,在使用 IClaimsTransformer 的 API 请求管道中,我检索调用用户的授权并将其添加到传入的声明中。\n请注意,在 MVC 调用和 API 的情况下,我因此检索授权 2 次;一次在 MVC 请求管道中,一次在 API 请求管道中。
\n\n使用此设置,我可以使用具有授权和身份验证功能的 HttpClientsFactory。
\n\n当然,在重要的安全部分,我缺少的是 HTTPS。我希望我能以某种方式将它添加到我的工厂。一旦实施,我将更新它。
\n\n一如既往,欢迎提出任何建议。
\n\n下面是我使用身份验证将图像上传到图像服务器的示例(用户必须登录并具有管理员角色)。
\n\n我的 MVC 控制器调用 \xe2\x80\x98UploadUserPicture\xe2\x80\x99:
\n\n [Authorize(Roles = "Admin")]\n [HttpPost]\n public async Task<ActionResult> UploadUserPicture()\n {\n // collect name image server\n var imageServer = _optionsAccessor.Value.ImageServer;\n\n // collect image in Request Form from Slim Image Cropper plugin\n var json = _httpContextAccessor.HttpContext.Request.Form["slim[]"];\n\n // Collect access token to be able to call API\n var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");\n\n // prepare api call to update image on imageserver and update database\n var client = _httpClientsFactory.Client("imageServer");\n client.DefaultRequestHeaders.Accept.Clear();\n client.SetBearerToken(accessToken);\n var content = new FormUrlEncodedContent(new[]\n {\n new KeyValuePair<string, string>("image", json[0])\n });\n HttpResponseMessage response = await client.PostAsync("api/UserPicture/UploadUserPicture", content);\n\n if (response.StatusCode != HttpStatusCode.OK)\n {\n return StatusCode((int)HttpStatusCode.InternalServerError);\n }\n return StatusCode((int)HttpStatusCode.OK);\n }\nRun Code Online (Sandbox Code Playgroud)\n\nAPI处理用户上传
\n\n [Authorize(Roles = "Admin")]\n [HttpPost]\n public ActionResult UploadUserPicture(String image)\n {\n dynamic jsonDe = JsonConvert.DeserializeObject(image);\n\n if (jsonDe == null)\n {\n return new StatusCodeResult((int)HttpStatusCode.NotModified);\n }\n\n // create filname for user picture\n string userId = jsonDe.meta.userid;\n string userHash = Hashing.GetHashString(userId);\n string fileName = "User" + userHash + ".jpg";\n\n // create a new version number\n string pictureVersion = DateTime.Now.ToString("yyyyMMddHHmmss");\n\n // get the image bytes and create a memory stream\n var imagebase64 = jsonDe.output.image;\n var cleanBase64 = Regex.Replace(imagebase64.ToString(), @"^data:image/\\w+;base64,", "");\n var bytes = Convert.FromBase64String(cleanBase64);\n var memoryStream = new MemoryStream(bytes);\n\n // save the image to the folder\n var fileSavePath = Path.Combine(_env.WebRootPath + ("/imageuploads"), fileName);\n FileStream file = new FileStream(fileSavePath, FileMode.Create, FileAccess.Write);\n try\n {\n memoryStream.WriteTo(file);\n }\n catch (Exception ex)\n {\n _logger.LogDebug(LoggingEvents.UPDATE_ITEM, ex, "Could not write file >{fileSavePath}< to server", fileSavePath);\n return new StatusCodeResult((int)HttpStatusCode.NotModified);\n }\n memoryStream.Dispose();\n file.Dispose();\n memoryStream = null;\n file = null;\n\n // update database with latest filename and version\n bool isUpdatedInDatabase = UpdateDatabaseUserPicture(userId, fileName, pictureVersion).Result;\n\n if (!isUpdatedInDatabase)\n {\n return new StatusCodeResult((int)HttpStatusCode.NotModified);\n }\n\n return new StatusCodeResult((int)HttpStatusCode.OK);\n }\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
7254 次 |
| 最近记录: |