Тип возврата успеха / неудачи для универсального метода

#c# #generics #asp.net-core

#c# #общие #asp.net-ядро

Вопрос:

Я написал универсальные методы для Get, Post и Put. Примером получения универсального метода является :

  public async Task<object> GetAsync<T>(string uri, NamingStrategy namingStrategy)
    {
        using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri))
        {
            return await ProcessAsync<T>(requestMessage, namingStrategy);
        }
    }
  

и ProcessAync является :

 public async Task<object> ProcessAsync<T>(HttpRequestMessage request, NamingStrategy namingStrategy)
    {
        if (!string.IsNullOrEmpty(AuthToken))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AuthToken);
        }
        HttpResponseMessage response = await _client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            _logger.LogInformation("Request Succeeded");
            var dezerializerSettings = new JsonSerializerSettings
            {
                ContractResolver = new DefaultContractResolver
                {
                    NamingStrategy = namingStrategy
                }
            };
            T responseModel = JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync(), dezerializerSettings);
            return responseModel;
        }
        else
        {
            return await GetFailureResponseModel(response);

        }
    }
  

затем я вызываю этот метод get подобным образом в моем SingletonClass

  public async Task<object> GetShops(string category)
    {
        _logger.LogInformation("ClubMatas outgoing request: {RequestName}", nameof(GetShops));
        return await _client.GetAsync<ShopsResponseModel>($"v2/shops?category={WebUtility.UrlEncode(category)}");
    }
  

И этот метод вызывается в моем контроллере следующим образом

  public async Task<ActionResult<object>> GetShops([FromQuery(Name = "category")]string category)
    {
        var response = await _httpClient.GetShops(category);
        return ParseResponse<ShopsResponseModel>(response);
    }
  

и ParseResponse является

  protected ActionResult<object> ParseResponse<T>(object response)
    {
        if (response.GetType() == typeof(T))
        {
            return Ok(response);
        }
        else
        {
            return Error(response);
        }
    }
  

Как показывает цепочка вызовов, я ожидаю другую SuccessModel или FailureModel в моем ответе Api, и из-за этого я должен использовать object в качестве возвращаемого типа. Но у меня такое чувство, что я не должен использовать тип объекта для возврата. К вашему сведению, приведенная выше цепочка работает нормально. Я просто ищу больше рефакторинга или улучшения моего текущего потока. ищете более элегантное решение этой проблемы. Пожалуйста, предложите любое другое решение моей проблемы.

Обновление Я попробовал предложение @ChrisPratt об использовании интерфейса, но это решение не работает или, возможно, я делаю это неправильно. Итак, я создал этот пустой интерфейс

 public interface IResult
{
}
  

И я расширил как свой ShopResponseModel интерфейс, так и FailureResponseModel IResult интерфейс from, а также обновил методы, подобные этому.

  public async Task<IResult> GetShops(string category)
    {
        _logger.LogInformation("ClubMatas outgoing request: {RequestName}", nameof(GetShops));
        return await _client.GetAsync<IResult>($"v2/shops?category={WebUtility.UrlEncode(category)}");
    }
  

и

  public async Task<T> GetAsync<T>(string uri, NamingStrategy namingStrategy)
    {
        using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri))
        {
            return await ProcessAsync<T>(requestMessage, namingStrategy);
        }
    }
  

и я обновил ProcessAsync возвращаемый тип с object на T. Но получаю ошибки.

Комментарии:

1. Почему бы просто не создать исключение на случай, если что-то пойдет не так?

2. Но мне также нужен этот failureResponse на мобильном конце, потому что он содержит пользовательские сообщения, связанные с ошибкой, а также о том, как ее исправить. Так что выбрасывание исключения мне в этом случае не поможет.

3. Универсальный метод не должен использоваться object . Он также должен принимать возвращаемый тип в качестве параметра типа. Что касается использования типов результатов, таких как Rust, F #, все, что вам нужно, это чтобы оба конкретных типа наследовали от одного и того же базового интерфейса / типа. У него даже не должно быть никаких членов

4. @Shabirjan вы не опубликовали определения типов success / failure . GetShops показывает, что вместо object того, чтобы вы должны использовать ShopsResponseModel или что-то, что содержит ShopsResponseModel

5. @Shabirjan вам все равно не нужно object . Самым простым решением было бы использовать общий интерфейс, например IResult , без членов.

Ответ №1:

Но у меня такое чувство, что я не должен использовать тип объекта для возврата.

ДА. Не используйте object для возвращаемых значений. Он практически бесполезен в качестве возвращаемого типа. Что вы должны делать, так это возвращать интерфейс. Например, вы можете сделать что-то вроде:

 public interface IResponseStatusModel
{
    bool Succeeded { get; }
    int StatusCode { get; }
    // etc.
}
  

Затем:

 public class SuccessModel : IResponseStatusModel

public class FailureModel : IResponseStatusModel
  

Затем вы можете вернуть IResponseStatusModel , и на основе интерфейса вы сможете взаимодействовать с любым свойством или методом, определенным в интерфейсе, независимо от того, какую модель вы фактически возвращаете.

Однако у вас действительно не должно быть отдельных классов для success / failure . Создание интерфейса, который в общем случае может позволить вам взаимодействовать с любым из них, в любом случае приведет к стиранию границ между ними. Вместо этого вы должны просто вернуть один тип модели, который имеет свойства, подобные приведенным выше, и, возможно, свойство list для ошибок и тому подобное. Например:

 public class ProcessResult
{
    public ProcessResult(int statusCode) : this(statusCode, null) {}

    public ProcessResult(int statusCode, IEnumerable<string> errors)
    {
        Succeeded = statusCode < 300;
        StatusCode = statusCode;
        Errors = errors;
    }

    public bool Succeeded { get; private set; }
    public int StatusCode { get; private set; }
    public IEnumerable<string> Errors { get; private set; }
}
  

Это очень простой пример. Возможно, вам захочется расширить его и предоставить более надежное решение для определения того, была ли задача выполнена успешно или нет. Общая идея заключается в том, что вы предоставляете как можно больше релевантной информации о результате операции. Затем в вашем коде вы можете просто перейти Succeeded и затем соответствующим образом обработать ситуацию:

 if (result.Succeeded)
{
    // do something on success
}
else
{
    // do something on failure
}
  

Комментарии:

1. Спасибо за подробный ответ. Но у меня есть пара вопросов, во-первых, почему интерфейс имеет параметры? Прямо сейчас в контроллере я возвращаю либо SuccessModel, либо FailureResponseModel. Во-вторых, как я могу добавить этот интерфейс в свой поток общих вызовов?

2. пробовал ваше решение по использованию интерфейса, но получал ошибки. Проверьте обновленный раздел в моем вопросе.