Подтвердить что ты не робот

Методы асинхронного приложения Mvc

У нас есть SOA для нашего решения. Мы используем .net framework 4.5.1, asp.net mvc 4.6, сервер sql, сервер Windows Server и сервер идентификации thinktecture 3 (для вызовов на основе токенов на основе токенов).

Структура решения выглядит следующим образом:
введите описание изображения здесь

Приложение mvc frontend взаимодействует с нашим приложением webapi через оболочку httpClient. Вот общий код оболочки http-клиента,

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace Cheetah.HttpClientWrapper
{
    public class ResourceServerRestClient : IResourceServerRestClient
    {
        private readonly ITokenProvider _tokenProvider;

        public ResourceServerRestClient(ITokenProvider tokenProvider)
        {
            _tokenProvider = tokenProvider;
        }

        public string BaseAddress { get; set; }

        public Task<T> GetAsync<T>(string uri, string clientId)
        {
            return CheckAndInvokeAsync(async token =>
            {
                using (var client = new HttpClient())
                {
                    ConfigurateHttpClient(client, token, clientId);

                    HttpResponseMessage response = await client.GetAsync(uri);

                    if (response.IsSuccessStatusCode)
                    {
                        return await response.Content.ReadAsAsync<T>();
                    }

                    var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
                    exception.Data.Add("StatusCode", response.StatusCode);
                    throw exception;
                }
            });
        }

        private void ConfigurateHttpClient(HttpClient client, string bearerToken, string resourceServiceClientName)
        {
            if (!string.IsNullOrEmpty(resourceServiceClientName))
            {
                client.DefaultRequestHeaders.Add("CN", resourceServiceClientName);
            }

            if (string.IsNullOrEmpty(BaseAddress))
            {
                throw new Exception("BaseAddress is required!");
            }

            client.BaseAddress = new Uri(BaseAddress);
            client.Timeout = new TimeSpan(0, 0, 0, 10);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
        }

        private async Task<T> CheckAndInvokeAsync<T>(Func<string, Task<T>> method)
        {
            try
            {
                string token = await _tokenProvider.IsTokenNullOrExpired();

                if (!string.IsNullOrEmpty(token))
                {
                    return await method(token);
                }

                var exception = new Exception();
                exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
                throw exception;
            }
            catch (Exception ex)
            {
                if (ex.Data.Contains("StatusCode") && ((HttpStatusCode)ex.Data["StatusCode"]) == HttpStatusCode.Unauthorized)
                {
                    string token = await _tokenProvider.GetTokenAsync();

                    if (!string.IsNullOrEmpty(token))
                    {
                        return await method(token);
                    }
                }

                throw;
            }
        }

        public void ThrowResourceServerException(List<string> messages)
        {
            string message = messages.Aggregate((p, q) => q + " - " + p);

            var exception = new Exception(message);

            exception.Data.Add("ServiceOperationException", message);

            throw exception;
        }
    }
}

Кроме того, иногда эта оболочка http-клиента используется с менеджером NitoAsync (вызовы async-методов как синхронизация), а иногда мы используем этот общий метод непосредственно с ожиданием - ожидание async-задачи вроде:

var result = await _resourceServerRestClient.GetAsync<ServiceOperation<DailyAgendaModel>>("dailyAgenda/" + id);

Итак, вот наша проблема:

Когда мы тестируем наше приложение mvc с помощью jmeter (для выполнения определенного теста нагрузки /10 потоков за 1 секунду), через пару минут приложение mvc перестает работать [исключение отменяется из-за таймаута] (возможно, только 1-2 тайм-аута запросов): HttpResponseMessage response = await client.GetAsync(uri);. Но после этого запроса все запросы будут сбойными, как в строке. Так что приложение mvc висит на 2-15 минут (случайно), но в это время я могу отправлять новые запросы от почтальона на webapi. Они в порядке, я имею в виду, что webapi хорошо реагирует. Через пару минут приложение mvc вернется в нормальное состояние.

Примечание. У нас есть балансировка нагрузки для mvc-ui и webapi. Потому что иногда мы получаем 120 тыс. Запросов в минуту в напряженный день. Но он дает такую ​​же ошибку, если нет балансировки нагрузки перед приложением webapi или mvc. Так что это не проблема LB.

Примечание2: Мы пытались использовать RestSharp для связи mvc-ui и webapi. Мы получили такую ​​же ошибку. Когда reuqest терпит неудачу, все запросы будут сбой в строке. Это похоже на сетевую ошибку, но мы не можем найти для нее доказательства.

Вы можете увидеть какую-либо ошибку в моей обертке httpClient? или лучший вопрос:
В вашем решении, как ваше приложение mvc общается с вашим приложением webapi? Каковы лучшие практики здесь?

Update1. Мы переместили проекты .net 4.5.1 - 4.6.1. Тот же тупик произошел снова. И чем мы временно перемещали все исходные коды слоя: "Бизнес и репозиторий" как уровень DLL. Сейчас нет веб-страниц между бизнесом и уровнем презентации. Мертвый замок решен. Мы по-прежнему ищем, почему коды httpClientWrapper не работают должным образом, когда мы вызываем методы webapi из наших контроллеров web-приложений.

4b9b3361

Ответ 1

лучший вопрос; В вашем решении, как ваше приложение mvc общается с вашим приложением webapi? Каковы лучшие практики здесь?

Лучшая практика заключается в том, чтобы клиент (браузер в вашем случае) напрямую извлекал данные из контроллеров веб-API и для контроллеров MVC отображал только чистые представления HTML, которые включают в себя макет, стили (css), визуальную структуру, скрипты (т.е. javascript) и т.д., а не данные.

Браузер, взаимодействующий с MVC и веб-API

Кредит изображения: Ode to Code. Кстати, автор на этом сайте также не рекомендует ваш подход, хотя он указан как опция.

  • Этот сервер служит хорошим SOC между вашими представлениями и вашими данными, что позволяет вам с легкостью вносить изменения в обе части.
  • Он позволяет клиенту (браузеру) извлекать данные асинхронно, что создает для лучшего удобства пользователя.

Не выполняя этого и добавляя шаг сетевого запроса в стек вызовов, вы создали ненужный дорогой шаг в потоке данных (вызов из контроллера MVC (Controls) в развертывание Web API). Чем больше границ пересекается во время выполнения более медленного выполнения.

Быстрое решение, как вы уже выяснили, - это вызвать вашу библиотеку бизнес-кода непосредственно из вашего проекта MVC. Это позволит избежать дополнительного и ненужного сетевого шага. Нет ничего плохого в этом, и многие другие традиционные сайты служат как представлению (html), так и данным в одном и том же вызове. Это делает более плотно связанную конструкцию, но она лучше, чем у вас.

Лучшим долгосрочным решением является изменение представлений MVC, чтобы они напрямую вызывали развертывание вашего веб-интерфейса. Это можно сделать с помощью фреймворков, таких как Angular, React, Backbone и т.д. Если вызовы метода Web API ограничены и не ожидаются роста, вы также можете использовать JQuery или чистый javascript BUT Я бы не попытался построить сложное приложение на этом, есть причина, почему фреймворки, такие как Angular, стали настолько популярными.

Что касается реальной технической проблемы в этом случае, мы не можем быть уверены, что без дампа памяти, чтобы увидеть, какие ресурсы вызывают тупик. Это может быть так же просто, как убедиться, что ваши MVC Action Methods также возвращают async Task<ActionResult> (а не только ActionResult, которые, как я полагаю, это то, как вы их структурировали сейчас), чтобы они могли вызвать HttpClient, используя фактический async/await узор. Честно говоря, потому что это плохой дизайн, я бы не потратил времени на то, чтобы заставить это работать.

Ответ 2

Я точно не уверен, но я начну с рефакторинга метода GetAsync()

public async Task<T> GetAsync<T>(string uri, string clientId)
    {
        try
        {
         string token = await _tokenProvider.IsTokenNullOrExpired();
         if (!string.IsNullOrEmpty(token))
            {
             using (var client = new HttpClient())
             {
                ConfigurateHttpClient(client, token, clientId);

                HttpResponseMessage response = await client.GetAsync(uri);

                if (response.IsSuccessStatusCode)
                {
                    return await response.Content.ReadAsAsync<T>();
                }

                var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
                exception.Data.Add("StatusCode", response.StatusCode);
                throw exception;
             }
          }
            else
            {
                var exception = new Exception();
                exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
                throw exception;
            }
         }
        catch (Exception ex)
        {
            throw;
        }
    }

Ответ 3

Вы должны поместить . ConfigureAwait (false) в свои внутренние ожидающие утверждения:

HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false);

(...)

return await response.Content.ReadAsAsync<T>().ConfigureAwait(false);

(...)

string token = await _tokenProvider.IsTokenNullOrExpired().ConfigureAwait(false);

(...)
return await method(token).ConfigureAwait(false);;

(...)

string token = await _tokenProvider.GetTokenAsync().ConfigureAwait(false);;

(...)

return await method(token).ConfigureAwait(false);

Таким образом, вы избежите захвата контекста синхронизации до того, как будет выполнено ожидание. В противном случае в этом контексте будет продолжено продолжение, которое может привести к блокировке, если этот используется другими потоками. Это позволит продолжить выполнение в контексте ожидаемой задачи.