Создание 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"));
        }
    }
}