ASP.Net CoreでHttpClientクラスを最適に使用する方法を理解しようとしています。
ドキュメントといくつかの記事によると、クラスはアプリケーションの存続期間中に一度インスタンス化され、複数のリクエストで共有されるのが最適です。残念ながら、Coreでこれを正しく行う方法の例が見つからなかったため、次の解決策を考え出しました。
私の特定のニーズでは、2つの異なるエンドポイント(ビジネスロジック用のAPIServerとAPI駆動のImageServerがある)を使用する必要があるため、アプリケーションで使用できる2つのHttpClientシングルトンを使用することを考えています。
Appsettings.jsonでサービスポイントを次のように構成しました。
"ServicePoints": {
"APIServer": "http://localhost:5001",
"ImageServer": "http://localhost:5002",
}
次に、2つのhttpclientsをインスタンス化して静的ディクショナリに保持するHttpClientsFactoryを作成しました。
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);
}
これで、ConfigureServicesメソッドの下のStartupクラスで、次のようにDependencyコンテナーにこれを注入する準備ができました。
// Add httpClient service
services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>();
これで、コントローラーでこれを使用するようにセットアップされました。
まず、依存関係を取り入れます。これを行うには、それを保持するプライベートクラスプロパティを作成し、それをコンストラクタシグネチャに追加して、受信オブジェクトをローカルクラスプロパティに割り当てて終了します。
private IHttpClientsFactory _httpClientsFactory;
public AppUsersAdminController(IHttpClientsFactory httpClientsFactory)
{
_httpClientsFactory = httpClientsFactory;
}
最後に、ファクトリを使用して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");
}
できた!
私はこれをテストしましたが、私の開発環境でうまく機能します。ただし、これがこれを実装するための最良の方法かどうかはわかりません。私は次の質問に残ります:
.netコア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-Startup.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と 彼の投稿はこちら に感謝します。
@MuqeetKhanからのhttpclientリクエストでの認証の使用に関する質問への回答。
まず、DIとファクトリを使用する動機は、アプリケーションをさまざまな複数のAPIに簡単に拡張し、コード全体で簡単にアクセスできるようにすることでした。これは、何度も再利用できるようにしたいテンプレートです。
上記の元の質問で説明した「GetUserPicture」コントローラーの場合、私は確かに簡単にするために認証を削除しました。しかし正直なところ、単純にイメージサーバーから画像を取得するために必要なのかどうかはまだわかりません。とにかく、他のコントローラーでは間違いなく必要なので、…
Identityserver4を認証サーバーとして実装しました。これにより、ASP Identity。の上に認証が提供されます。承認(この場合はロールを使用)のために、IClaimsTransformerをMVCの「および」APIプロジェクトに実装しました(この詳細については、こちらを参照してください) at ASP.net IDロールをIdentityserver4 IDトークンに入れる方法 )。
これで、コントローラーに入るとすぐに、アクセストークンを取得できる認証済みユーザーと承認済みユーザーができました。このトークンを使用してapiを呼び出します。もちろん、これはidentityserverの同じインスタンスを呼び出して、ユーザーが認証されているかどうかを確認します。
最後のステップは、ユーザーが要求されたAPIコントローラーを呼び出すことをユーザーに許可されているかどうかをAPIが確認できるようにすることです。前に説明したように、IClaimsTransformerを使用するAPIのリクエストパイプラインで、呼び出し元ユーザーの認証を取得し、それを受信クレームに追加します。 MVC呼び出しとAPIの場合は、このように2回認証を取得することに注意してください。 1つはMVCリクエストパイプラインで、もう1つは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);
}