我试图弄清楚如何在ASP.Net Core中最好地使用HttpClient类。
根据文档和几篇文章,该类最好在应用程序的生命周期中实例化一次,并为多个请求共享。不幸的是,我找不到如何在Core中正确执行此操作的示例,因此我提出了以下解决方案。
我的特殊需求需要使用2个不同的端点(我有一个用于业务逻辑的APIServer和一个API驱动的ImageServer),所以我的想法是拥有2个可以在应用程序中使用的HttpClient单例。
我已经在appsettings.json中配置了我的服务点,如下所示:
"ServicePoints": {
"APIServer": "http://localhost:5001",
"ImageServer": "http://localhost:5002",
}
接下来,我创建了一个HttpClientsFactory,它将实例化我的2个httpclients并将它们保存在静态字典中。
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];
}
}
然后,我创建了以后在定义我的DI时可以使用的界面。请注意,HttpClientsFactory类继承自此接口。
public interface IHttpClientsFactory
{
Dictionary<string, HttpClient> Clients();
HttpClient Client(string key);
}
现在我准备将它注入我的Dependency容器,如下所示,在ConfigureServices方法下的Startup类中。
// Add httpClient service
services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>();
所有现在都设置为在我的控制器中开始使用它 首先,我接受了依赖。为此,我创建了一个私有类属性来保存它,然后将它添加到构造函数签名中,并通过将传入对象分配给本地类属性来完成。
private IHttpClientsFactory _httpClientsFactory;
public AppUsersAdminController(IHttpClientsFactory httpClientsFactory)
{
_httpClientsFactory = httpClientsFactory;
}
最后,我们现在可以使用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");
}
完成!
我已经对此进行了测试,它在我的开发环境中运行良好。但是,我不确定这是否是实现此目的的最佳方式。我仍然提出以下问题:
答案 0 :(得分:1)
回答@MuqeetKhan关于在httpclient请求中使用身份验证的问题。
首先,我使用DI和工厂的动机是允许我轻松地将我的应用程序扩展到不同的API和多个API,并且可以在我的代码中轻松访问它。这是一个模板,我希望能够重复使用多次。
在我上面的原始问题中描述的'GetUserPicture'控制器的情况下,我确实为了简单起见删除了身份验证。但老实说,如果我需要它来简单地从图像服务器中检索图像,我仍然有疑问。无论如何,在其他控制器中我肯定需要它,所以......
我已将Identityserver4作为我的身份验证服务器实现。这为我提供了ASP身份验证。 对于授权(在这种情况下使用角色),我在我的MVC'和'API项目中实现了IClaimsTransformer(您可以在How to put ASP.net Identity Roles into the Identityserver4 Identity token处阅读更多相关信息)。
现在,当我进入我的控制器时,我有一个经过身份验证和授权的用户,我可以为其检索访问令牌。我使用此令牌来调用我的api,这当然是调用identityserver的同一个实例来验证用户是否经过身份验证。
最后一步是允许我的API验证用户是否有权调用所请求的api控制器。在使用IClaimsTransformer的API的请求管道中,如前所述,我检索主叫用户的授权并将其添加到传入的声明中。 请注意,在MVC调用和API的情况下,我因此检索授权2次;一次在MVC请求管道中,一次在API请求管道中。
使用此设置,我可以将HttpClientsFactory与授权和身份验证一起使用。
在我失踪的大安全部分当然是HTTPS。我希望我能以某种方式将它添加到我的工厂。我已经实施了它,我会更新它。
一如既往,欢迎任何建议。
下面我使用身份验证将图像上传到Imageserver的示例(用户必须登录并拥有角色admin)。
我的MVC控制器调用'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);
}
处理用户上传的API
[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);
}
答案 1 :(得分:1)
如果使用.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
}
}
2-在{upup.cs}的ConfigureServices(IServiceCollection services)
中注册客户
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);
});
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();
}
}
您可以使用services.AddHttpClient
感谢Steve Gordon和his post here帮助我在代码中使用它!
答案 2 :(得分:0)
using System.Net.Http;
public class SomeClass
{
private static readonly HttpClient Client;
static SomeClass()
{
var handler = new SocketsHttpHandler
{
// Sets how long a connection can be in the pool to be considered reusable (by default - infinite)
PooledConnectionLifetime = TimeSpan.FromMinutes(1),
};
Client = new HttpClient(handler, disposeHandler: false);
}
...
}