« first day (1579 days earlier)      last day (1452 days later) » 

12:43 PM
Нигде майки еще не выкладывали дорожную карту на следующий год по .NET и Visual Studio? Интересно, что за фичи планируют добавить в следующем году.
 
 
6 hours later…
A K
6:48 PM
Коллеги, а никто не посмотрит что можно было сделать иначе/лучше в следующем рефакторинг?
У меня есть проект Interop в котором находится набор запросов типа такого:
    public class Query : IRequest<int?>
    {
        public Query(int id)
        {
            this.Id = id;
        }

        public int Id { get; }
    }

    public class Handler : BaseHandler, IRequestHandler<Query, int?>
    {
        public Task<int?> Handle(Query request, CancellationToken cancellationToken)
        {
            return this.Call<int?>(Url, request, cancellationToken);
        }

        private const string Url = "/api/SomeEntity/SomeMethod/";
    }
И вся логика по обработке запроса была вынесена в большой класс (который скрывает детали реализации -- что используется HttpClient, что запросы отправляются в json и расшифровываются обратно, что используется JWT токен для авторизации):
    public class BaseHandler
    {
        protected BaseHandler()
        {
            this.Client = new HttpClient();
        }

        private HttpClient Client { get; }
        private const string BaseUrl = "https://server.tld";
        private const string ServiceAccountUserName = "user";
        private const string ServiceAccountUserPassword = "password";

        protected async Task<T> Call<T>(string uri, object request, CancellationToken cancellationToken)
        {
            var jsonString = JsonConvert.SerializeObject(request);
В принципе, меня до какого-то момента всё устраивало: весь код тихо складирован в одном месте, всё под рукой... Однако захотелось мне сделать запрос токена не на каждый запрос, а на некоторое время -- понял, что класс распухнет и решил немного предварительно порефакторить.
Вот что вышло в итоге:
Методы PostWithToken и PostWithoutToken отправились в класс WebTransport, поскольку они практически одинаковые то они стали одним-единственным методом Call:
    public class WebTransport
    {
        public WebTransport(HttpClient client)
        {
            this.Client = client;
        }

        private HttpClient Client { get; }

        public async Task<string> Call(string uri, string data)
        {
            var content  = new StringContent(data, Encoding.UTF8, "application/json");
            var response = await this.Client.PostAsync(uri, content);
            return await response.Content.ReadAsStringAsync();
        }
    }
Соответственно, нам нужно два вида клиентов (для запроса с токеном и без токена), поэтому появилась небольшая фабрика:
    public static class HttpClientFactory
    {
        public static HttpClient Get(string token)
        {
            var client = new HttpClient();

            if (token != null)
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

            return client;
        }
    }
Процесс обработки запроса стал унифицированным, что его стало возможным вынести в отдельный менеджер, который занимается только этим:
    public static class WebProcessor
    {
        public static async Task<T> Call<T>(string url, object request, string token)
        {
            var jsonString = JsonConvert.SerializeObject(request);

            var client = new WebTransport(HttpClientFactory.Get(token));

            var result = await client.Call($"{BaseUrl}{url}", jsonString);

            var deserialized = JsonConvert.DeserializeObject<T>(result);

            return deserialized;
        }

        private const string BaseUrl = "https://server.tld";
Получение токена съехало в отдельный менеджер:
    public static class TokenObtainer
    {
        public static async Task<string> GetToken(string userName, string password)
        {
            var request = new TokenRequest
            {
                UserName = userName,
                Password = password,
            };

            var result = await WebProcessor.Call<TokenAnwer>("/api/token", request, null);

            return result.Token;
        }

        private class TokenRequest
        {
            public string UserName { get; set; }
И базовый обработчик принял более-менее нормальный краткий SingleResponsible вид:
   public class BaseHandler
    {
        protected BaseHandler()
        {
            Token = null;
        }

        private static string Token { get; set; }

        protected async Task<T> Call<T>(string url, object request)
        {
            await this.AssertToken();

            return await WebProcessor.Call<T>(url, request, Token);
        }

        private async Task AssertToken()
        {
            // TODO: Check expired
            if (Token != null)
                return;
Как бы это сделали вы? Что можно кардинально улучшить, где-то минорно подрихтовать?
 
A K
7:16 PM
М, не заметил: не пробрасываю CancellationToken, повтыкал везде последним параметром.
 
 
2 hours later…
9:24 PM
@AK а если бы ты смотрел Егора Бугаенко, знал бы, что тебе поможет декоратор :)
 
A K
@Grundy Мммм... а не ты ли, о лучезарный, рекомендовал мне как-то не смотреть Егора Бугаенко? ))
 
@AK нет, не я :-D я советовал смотреть, там такие перлы бывают :-D
 
A K
@Grundy Можно поконкретнее куда и как его воткнуть сюда и -- зачем?
или хотя бы ткнуть в конкретный ролик, который поможет разобраться?
 
@AK делаешь декоратор для своего класса BaseHandler, в котором переопределяешь метод GetToken - чтобы он брал не каждый раз и иногда из кэша и все
больше никаких изменений :)
 
A K
А!
Понял
 
9:28 PM
но эт я еще остальной код не смотрел :)
 
9:59 PM
@AK а что мешало оставить метод GetClient в классе? зачем делать статические методы?
@AK Base урл должен в клиент передаваться
@AK не вижу проверку ошибок
@AK ну какой же это ассерт-то?
@AK здесь по идее не нужно наследоваться от BaseHandler
потому что по сути он не basehandler - а apiClient какой-нибудь
он только шлет запросы и получает ответы, а обрабатывает ответ уже этот хандлер непосредственный
@AK, В общем я тут согласен с Егором :-D Ты не уважаешь свои объекты :-D
внимание вопрос, почему BaseHandler не стал статическим классом? :-) раз уж ты совсем отказался он обычных классов
 
10:43 PM
@AK, я бы ограничился на чем-то таком: gist.github.com/urffin/dd7248fb801ed428cbd984226fe27a77
 

« first day (1579 days earlier)      last day (1452 days later) »