Создание HttpClient в старых ASP.NET приложение MVC без запуска
у меня есть старая версия ASP.NET MVC приложение, которое не имеет Startup.cs
. Я хотел реализовать чистый способ иметь HttpClient
что я буду использовать для моих вызовов API третьим лицам.
вот что я сделал до сих пор на основе некоторых идей/рекомендаций, которые я получил по этому вопросу. Проблема в том, что когда я делаю вызов API, он никуда не идет. Я положил его в try catch
но я даже не получаю исключение. Поставщик API говорит мне, что они не видят поиска параметр.
во-первых, я создал этот HttpClientAccessor
для отложенной загрузки.
public static class HttpClientAccessor
{
public static Func<HttpClient> ValueFactory = () =>
{
var client = new HttpClient();
client.BaseAddress = new Uri("https://apiUrl.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client;
};
private static Lazy<HttpClient> client = new Lazy<HttpClient>(ValueFactory);
public static HttpClient HttpClient
{
get { return client.Value; }
}
}
затем я создал собственный клиент API, чтобы я мог иметь функции вызова API в одном месте, которое выглядит так:
public class MyApiClient
{
public async Task GetSomeData()
{
var client = HttpClientAccessor.HttpClient;
try
{
var result = await client.GetStringAsync("somedata/search?text=test");
var output = JObject.Parse(result);
}
catch(Exception e)
{
var error = e.Message;
}
}
}
тогда в моем ASP.NET действие контроллера, я делаю это:
public class MyController : Controller
{
private static readonly MyApiClient _apiClient = new MyApiClient ();
public ActionResult ApiTest()
{
var data = _apiClient.GetSomeData().Wait();
}
}
есть идеи, Где моя ошибка?
обновление: Этот простой подход отлично работает:
public class MyController : Controller
{
private static readonly HttpClient _client = new HttpClient();
public ActionResult ApiTest()
{
_client.BaseAddress = new Uri("https://apiUrl.com");
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
_client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
var response = _client.GetStringAsync("somedata/search?text=test").Result;
}
}
4 ответов
как уже упоминалось, инъекция зависимостей не используется, поэтому технически нет необходимости в корне композиции, где эти вещи были бы инициализированы.
если нет необходимости фактически инициализировать клиента при запуске, вы можете рассмотреть возможность использования ленивого одноэлементного подхода.
пример
public static class HttpClientAccessor {
public static Func<HttpClient> ValueFactory = () => {
var client = new HttpClient();
client.BaseAddress = new Uri("https://apiUrl.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client;
};
private static Lazy<HttpClient> client = new Lazy<HttpClient>(ValueFactory);
public static HttpClient HttpClient {
get { return client.Value; }
}
}
делегат фабрики Lazy<HttpClient>
может быть сделано более сложным, если дополнительные настройки необходимы на клиенте.
и где когда клиент нужен, вы звоните в службу
var client = HttpClientAccessor.HttpClient;
var response = await client.GetStringAsync("{url}");
клиент будет инициализирован при первом использовании, и вы получите один и тот же экземпляр при последующих вызовах для экземпляра.
как используется в контроллере, вы смешиваете асинхронные вызовы с блокировкой вызовов line .Wait()
или .Result
. Это может привести к тупиковым ситуациям, и их следует избегать.
public class MyController : Controller {
private static readonly MyApiClient _apiClient = new MyApiClient ();
public async Task<ActionResult> ApiTest() {
var data = await _apiClient.GetSomeData();
//...
}
}
код должен быть асинхронным до конца.
ссылка Async / Await-лучшие практики в Асинхронное Программирование
метод Application_Start () является правильным местом. Но я должен спросить: почему вам нужно создать экземпляр HttpClient, когда "приложение запускается"? В общем, HttpClient-это некоторый "ресурс", и вы можете просто создать его, когда захотите его использовать. А также нет необходимости устанавливать его как "синглтон". Просто оберните его в блок using. (Может быть, вы хотите сделать оболочку API как синглтон?)
public class APICaller
{
//make the APICaller singleton in some way here
//...
// the api calling method:
public string CallAPI(string someParameter)
{
var response = "";
using (var client = new HttpClient())
{
//calling the API
}
return response;
}
}
основная проблема-неправильный асинхронный код.
вы используете Task.Wait()
который наряду с асинхронным MyApiClient.GetSomeData()
вызывает взаимоблокировку ASP.NET контекст запроса. Это очень распространенная проблема, см. пример async / await, который вызывает взаимоблокировку на StackOverflow. Код Task.Result
вызов свойства работает, потому что HttpClient.GetStringAsync()
вероятно, принимает превентивные меры против тупиков. См.Task.ConfigureAwait()
страница на MSDN и Лучшая практика для вызова ConfigureAwait для всего кода на стороне сервера обсуждение StackOverflow.
существует несколько вариантов написания синглтона с помощью C#. См.реализует паттерн Singleton в C# статья Джона Скита для подробного обзора.
Как вы упомянули, Вы можете просто использовать статический член класса контроллера. HttpClient нужно настроить только один раз; поэтому сделайте это в статическом конструкторе контроллера. Кроме того, убедитесь, что вы используете асинхронные/await для асинхронных методов, особенно с длительными http-запросами. IOC и уровень абстракции будут иметь смысл в зависимости от ваших потребностей.
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace TestApi
{
public class MyController : Controller
{
private const string ApiUrlString = "https://apiUrl.com";
private static readonly Uri ApiUri = new Uri(ApiUrlString);
private static readonly HttpClient RestClient;
static MyController()
{
this.RestClient = new HttpClient{
BaseAddress = ApiUri
}
this.RestClient.DefaultRequestHeaders.Accept.Clear();
this.RestClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
RestClient.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
RestClient.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
}
public async Task<IActionResult> ApiTest()
{
return this.Ok(await this.RestClient.GetStringAsync("somedata/search?text=test"));
}
}
}