Criando API com .NET
Tags: webdev, dotnet, braziliandevs, programming
Published: 2022-11-06 15:41:18

Para aqueles que programam a mais de 15 anos com certeza já em algum momento colocou a mão em um monólito, seja este web ou desktop. Mas com a evolução tecnológica vemos já uma clara divisão de responsabilidade e de entrega de dados por meio de APIs.

Neste post estarei abordando a construção de uma API REST utilizando .NET. Lembro que em .NET ao criar um projeto 'WebApi' o mesmo irá utilizar um template que seguido terá um resultará em uma API funcional.
O projeto criado pode ser encontrado no endereço [https://github.com/rodabarbosa/apitemplate](https://github.com/rodabarbosa/apitemplate).

Para um entendimento, normalmente não digo que o código é ruim. Para poder dizer isto devo conhecer e ter experiência trabalhando com a pessoa que escreveu o código. Prefiro dizer que a pessoa fez o melhor com o conhecimento que tinha no momento.

Pensamento e explicações a parte vamos para a criação da API.

## Requerimentos

Então para criação de nossa API será necessário ter instalado no computador:

* DOTNET 6
* Editor ou IDE de sua preferência. Ex.: VSCode, Visual Studio 2022, Rider, etc.

## Objetivo

Criar uma API para entrega da dados do tempo (Weather Forecast), esta é a mesma base que é entregue junto ao template do 'webapi' do dotnet.

Criar tratamento global de erro onde o objeto de retorno é comum em todos os erros que possam ocorrer dentro da aplicação. Isto irá facilitar um tratamento para consumidor da API.

API deve conter Swagger para documentação. Este item já irá estar configurado e ativo já com origem no template entregue pelo .NET.

Criar uma estrutura para atender a responsabilidade única de cada recurso e facilitar a manutenção quando necessário;

## Criando o projeto

Abra o terminal e vá até o diretório onde será armazenado o projeto.

Uma vez no diretório desejado digite o seguinte comando:

```bash
dotnet new sln -n ApiTemplate
```

Estou criando um arquivo para minha solução e a minha solução irá se chamar **ApiTemplate**.

Então, faça o comando para criar o projeto de webapi:

```bash
dotnet new webapi -n ApiTemplate.WebApi
```

Em seguida vamos criar alguns projeto do tipo biblioteca para armazenar regras e processos.

```bash
dotnet new classlib -n ApiTemplate.Shared
dotnet new classlib -n ApiTemplate.Domain
dotnet new classlib -n ApiTemplate.Infra.Data
```

Para informar que todos estes projetos contemplam a minha solução utilizo o seguinte comando:

```bash
dotnet sln ApiTemplate.sln add (ls -r **/*.csproj)
```

No meu caso estou utilizando o **powershell** do windows, se você estiver utilizando um sistema operacional diferente ou mesmo um terminal diferente recomendo a verificação da documentação no site da [Microsoft](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-sln).

### Alternativas

Caso você esteja utilizando o Visual Studio 2022 basta ir em novo projeto, selecionar o tipo *WebApi*, informar o diretório onde deseja salvar e clicar em criar.

Depois em **Solution Explore** clique com o botão direito e vá em **Add > New Project**.

Ou seja, caso esteja utilizando uma IDE o processo de criação da solução e projeto é bem simples.

## Estrutura do projeto

Como visto na parte anterior criamos alguns projetos do tipo biblioteca para separarmos nosso código. Então temos um projeto com o nome de *Domain*, apesar do nome este não será domínio como especificado/definido na arquitetura DDD, e sim, apenas uma separação logica de nossas entidades.

Temos **Shared** que irá armazenar código comum e de utilização em todos os níveis da solução. Este projeto não deve conter regras, mas isto não uma definição escrita em pedra sempre analise a necessidade e utilize o bom senso.

Também temos **Infra.Data** que é onde iremos armazenar as instruções para banco de dados.

E por fim, *WebApi* que é o nosso projeto de interface de dados.

### Projeto Domain

Neste projeto foi criado um *namespace*, um diretório dentro do projeto, chamado *Entities*. Dentro deste *namespace* criei a classe **WeatherForecast**. Explicando melhor, como disse anteriormente o projeto **WebApi** já vai vir com uma base que é sobre WeatherForecast, então precisei mover a classe existente do projeto **WebApi** para meu *namespace* e ajustar a definição do mesmo dentro do arquivo.

Dentro do projeto também criei um *namespace* chamado **Repository** onde coloquei classes do tipo interface com definições de métodos que irão existir com instruções de banco de dado. Resumidamente, na interface não existe implementação de métodos, e sim, apenas informações que aqueles métodos existirão em uma classe que ainda não sei qual será.

```csharp
public interface IWeatherForecastRepository
{
    IQueryable<WeatherForecast> Get(Expression<Func<WeatherForecast, bool>> predicate = null!);
    WeatherForecast? Get(Guid id);
    Task<WeatherForecast?> GetAsync(Guid id);
    void Add(WeatherForecast weatherForecast);
    Task AddAsync(WeatherForecast weatherForecast);
    void Update(WeatherForecast weatherForecast);
    Task UpdateAsync(WeatherForecast weatherForecast);
    void Delete(Guid id);
    void Delete(WeatherForecast weatherForecast);
    Task DeleteAsync(Guid id);
    Task DeleteAsync(WeatherForecast? weatherForecast);
}
```

### Projeto Shared

Neste projeto estão os códigos que são compartilhados entre os demais projetos e estes códigos estão livre de regras do meu negócio. Com isto em mente, neste projeto foi criado extensões, exceções e modelos de uso comum.

#### Exceções

Para as exceções foi criada cada uma para servir a um determinado proposito. Irei apresentar uma e as demais seguem esta mesma estrutura.

```csharp
public class BadRequestException : Exception
{
    private const string DefaultMessage = "Bad Request";

    public BadRequestException(string? message = DefaultMessage) : this(message, default) { }

    public BadRequestException(Exception? innerException) : this(DefaultMessage, innerException) { }

    public BadRequestException(string? message, Exception? innerException) : base(DefineMessage(message, DefaultMessage), innerException) { }

    protected BadRequestException(SerializationInfo info, StreamingContext context) : base(info, context) { }

    private static string? DefineMessage(string? message, string? fallbackMessage)
    {
        return string.IsNullOrEmpty(message?.Trim()) ? fallbackMessage : message;
    }

    public static void ThrowIf(bool condition, string? message = DefaultMessage, Exception? innerException = null)
    {
        if (condition)
            throw new BadRequestException(message, innerException);
    }
}
```

Em todas as classes de exceções criado sempre adiciono um método estatico com o nome de *ThrowIf* que utilizo para facilitar a leitura do código. O funcionado deste método é basicamente que quando a condição for verdadeira é disparado a exceção com a mensagem informada.

Com esta estrutura também foram criadas as exceções:

* **DbRegisterExistsException:** Utilizando na operação de criar um registro na banco de dados com dados já existentes;
* **DeleteFailureException:** Como o próprio nome já sugere, utilizando quando ocorre um erro na operação de excluir um registro;
* **NotFoundException:** Este tipo é utilizando nos métodos de busca que não é encontrado nenhum resultado. Em atualização e exclusão quando o registro informado não é encontrado.
* **SaveFailureException:** Utilizado para quando ocorrer um erro na operação de salvar um registro em banco de dados;
* **ValidationException:** Utilizado quando existir erro na validação dos dados recebidos em algum método;

Para a classe **ValidationException** existe uma variação na estrutura da classe, pois irei registrar uma lista de chave e valor referente ao(s) erro(s):

```csharp
public class ValidationException : Exception
{
    private const string DefaultMessage = "One or more validation failure have occurred.";

    private ValidationException(string message = DefaultMessage) : this(message, null) { }

    public ValidationException(Exception? innerException) : this(DefaultMessage, innerException) { }

    private ValidationException(string? message, Exception? innerException) : base(DefineMessage(message, DefaultMessage), innerException) { }

    public ValidationException(IEnumerable<ValidationFailure> failures) : this()
    {
        IEnumerable<ValidationFailure> validationFailures = failures.ToList();
        var propertyNames = validationFailures
            .Select(e => e.PropertyName)
            .Distinct();

        foreach (var propertyName in propertyNames)
        {
            var propertyFailures = validationFailures
                .Where(e => e.PropertyName == propertyName)
                .Select(e => e.ErrorMessage)
                .ToArray();

            Failures.Add(propertyName, propertyFailures);
        }
    }

    protected ValidationException(SerializationInfo info, StreamingContext context) : base(info, context) { }

    public IDictionary<string, string[]> Failures { get; } = new Dictionary<string, string[]>();

    private static string? DefineMessage(string? message, string? fallbackMessage)
    {
        return string.IsNullOrEmpty(message?.Trim()) ? fallbackMessage : message;
    }

    public static void ThrowIf(bool condition, IEnumerable<ValidationFailure> failures)
    {
        if (condition)
            throw new ValidationException(failures);
    }

    public static void When(bool condition, string? message = DefaultMessage, Exception? innerException = null)
    {
        if (condition)
            throw new ValidationException(message, innerException);
    }
}
```

#### Extensões

Para extensões foram criados métodos para serialização e deserilização de JSOM, conversão de graus célsius para fahrenheit e vice-versa.

Para a manipulação do JSON a ideia é centralizar os métodos e caso ocorra a necessidade de alterar a biblioteca do mesmo, basta alterar a extensão. Isto facilita futuras manutenções.

```csharp
public static class JsonExtension
{
    private static readonly JsonSerializerOptions _jsonSerializerOptionsDeserialization = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true,
        WriteIndented = false,
        Encoder = JavaScriptEncoder.Default,
        IgnoreReadOnlyFields = true,
        IgnoreReadOnlyProperties = true
    };

    private static readonly JsonSerializerOptions _jsonSerializerOptionsSerialization = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true,
        WriteIndented = false,
        Encoder = JavaScriptEncoder.Default,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    public static string ToJson(this object value)
    {
        return JsonSerializer.Serialize(value, _jsonSerializerOptionsSerialization);
    }

```

Para conversão de graus, retirei o código de conversão de célsius para fahrenheit existente do modelo gerado no template do **WebApi** e foi incluso o método para conversão de fahrenheit para célsius. Estes métodos serão utilizados tanto na entidade quanto no contrato.

```csharp
public static class TemperatureExtension
{
    public static decimal ToFahrenheit(this decimal celsius)
    {
        return celsius * 9 / 5 + 32;
    }

    public static decimal ToCelsius(this decimal fahrenheit)
    {
        return (fahrenheit - 32) * 5 / 9;
    }
}
```

> **Nota:**
>
> A entidade é utilizada junto ao banco de dados e o contrato é utilizando junto as requisições da API. O contrato não necessita ser um reflexo de uma entidade.

#### Modelos

O único modelo foi o utilizado para retorno da API caso ocorra algum problema na execução.

```csharp
public sealed class ErrorModel
{
    [JsonPropertyName("code")] public int Code { get; init; }
    [JsonPropertyName("error")] public object? Error { get; init; }
    [JsonPropertyName("exception")] public string? Exception { get; init; }

#if !DEBUG
    [JsonIgnore]
#endif
    [JsonPropertyName("stacktrace")] public string? StackTrace { get; init; }
}
```

### Projeto Infra.Data

Este projeto é responsável com a comunicação com o banco de dados. Neste projeto estou utilizando o EntityFramework para fazer esta gestão mas pode ser utilizado qualquer outro componente para esta função. Para esta amostra irei apenas armazenar os dados em memoria.

Para instalar o EntityFramework no diretório do projeto *Infra.Data* utilize o seguinte comando:

```bash
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.InMemory
```

Deve ser consultado a provedor do banco de dados para obter qual a biblioteca necessária para se trabalhar com o banco escolhido.

#### Configurado as entidades com o banco

Esta parte é importante para especificar como uma entidade se comunica com uma determinada tabela do banco de dados. Para isto utilizo o *FluentApi* para escrever estas definições.

```csharp
public class WeatherForecastConfiguration : IEntityTypeConfiguration<WeatherForecast>
{
    public void Configure(EntityTypeBuilder<WeatherForecast> builder)
    {
        builder.HasKey(x => x.Id);

        builder.Property(x => x.Id)
            .ValueGeneratedOnAdd();

        builder.Property(x => x.Date)
            .IsRequired();

        builder.Property(x => x.TemperatureCelsius)
            .IsRequired();

        builder.Property(x => x.Summary)
            .IsRequired(false);
    }
}
```

#### Contexto do banco de dados

Como mencionado, esta sendo utilizado uma banco de dados em memoria para elaboração do código apresentado. Abaixo esta o contexto, o contexto é responsável por fazer a ponte entre o código e o banco de dados. Então com a definição feita utilizando o *FluentApi* o contexto irá conseguir transpor os dados armazenados em tabela do banco relacional para as classes entidades da aplicação.

```csharp
public sealed class ApiTemplateContext : DbContext
{
    public ApiTemplateContext(DbContextOptions<ApiTemplateContext> options) : base(options)
    {
        Database.EnsureCreated();
    }

    public DbSet<WeatherForecast>? WeatherForecasts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApiTemplateContext).Assembly);
    }
}
```

#### Implementando os repositórios

A implementação do repositório iremos escrever as instrução das operações que serão executadas no banco de dados. Lembrando que junto ao *namespace* **Domain** já criamos as interfaces com as assinaturas dos métodos.

Como o repositório se comunica com o banco de dados, então, iremos injetar o contexto na classe. Para isto colocamos que o construtor da classe deve ser informado uma instancia do contexto.

```csharp
public sealed class WeatherForecastRepository : IWeatherForecastRepository
{
    private readonly ApiTemplateContext _context;

    public WeatherForecastRepository(ApiTemplateContext context)
    {
        _context = context;
    }

    public IQueryable<WeatherForecast> Get(Expression<Func<WeatherForecast, bool>>? predicate = default)
    {
        return predicate is null
            ? _context.WeatherForecasts!
            : _context.WeatherForecasts!.Where(predicate);
    }

    public WeatherForecast? Get(Guid id)
    {
        return _context.WeatherForecasts!.SingleOrDefault(x => x.Id == id);
    }

    public Task<WeatherForecast?> GetAsync(Guid id)
    {
        return _context.WeatherForecasts!.SingleOrDefaultAsync(x => x.Id == id);
    }

    public void Add(WeatherForecast weatherForecast)
    {
        AddCheckup(weatherForecast);
        _context.SaveChanges();
    }

    public Task AddAsync(WeatherForecast weatherForecast)
    {
        AddCheckup(weatherForecast);
        return _context.SaveChangesAsync();
    }

    public void Update(WeatherForecast weatherForecast)
    {
        _context.WeatherForecasts!.Update(weatherForecast);
        _context.SaveChanges();
    }

    public Task UpdateAsync(WeatherForecast weatherForecast)
    {
        _context.WeatherForecasts!.Update(weatherForecast);
        return _context.SaveChangesAsync();
    }

    public void Delete(Guid id)
    {
        var weather = Get(id);

        DeleteFailureException.ThrowIf(weather is null, $"Weather forecast {id} not found");

        Delete(weather);
    }

    public void Delete(WeatherForecast? weatherForecast)
    {
        DeleteFailureException.ThrowIf(weatherForecast is null, "Weather forecast was not informed.");

        _context.WeatherForecasts!.Remove(weatherForecast!);
        _context.SaveChanges();
    }

    public Task DeleteAsync(Guid id)
    {
        var weather = Get(id);

        DeleteFailureException.ThrowIf(weather is null, $"Weather forecast {id} not found");

        return DeleteAsync(weather);
    }

    public Task DeleteAsync(WeatherForecast? weatherForecast)
    {
        DeleteFailureException.ThrowIf(weatherForecast is null, "Weather forecast was not informed.");

        _context.WeatherForecasts!.Remove(weatherForecast!);
        return _context.SaveChangesAsync();
    }

    private void AddCheckup(WeatherForecast weatherForecast)
    {
        if (weatherForecast.Id == Guid.Empty)
            weatherForecast.Id = Guid.NewGuid();

        _context.WeatherForecasts!.Add(weatherForecast);
    }
}
```

### Projeto Application

Este projeto irá conter as regras necessárias para o funcionamento de nossa aplicação (sistema). Aqui iremos criar os contratos.

> **Contratos:**
>
> São as classes representando o conjunto de dados recebidos recebidos ou enviado ao usuário.

#### Contratos

Dentro do projeto foi criado um *namespace* com o nome de **Contracts** e nele armazenado as classes referente ao mesmo.

```csharp
public class AuthenticateResponseContract
{
    public string Created { get; set; } = string.Empty;
    public string Expires { get; set; } = string.Empty;
    public string AccessToken { get; set; } = string.Empty;
    public string Username { get; set; } = string.Empty;
}
```

```csharp
public class CreateWeatherForecastRequestContract
{
    public DateTime? Date { get; set; }
    public decimal? TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
```

```csharp
public class GetWeatherForecastResponseContract
{
    public Guid Id { get; set; }
    public DateTime Date { get; set; }
    public decimal TemperatureCelsius { get; set; }
    public decimal TemperatureFahrenheit { get => TemperatureCelsius.ToFahrenheit(); }
    public string? Summary { get; set; }
}
```

```csharp
public class UpdateWeatherForecastRequestContract
{
    public Guid? Id { get; set; }
    public DateTime? Date { get; set; }
    public decimal? TemperatureCelsius { get; set; }
    public decimal? TemperatureFahrenheit { get; set; }
    public string? Summary { get; set; }
}
```

#### Validadores

Para os méotodos de autenticação, criar e atualiza novos métodos foi criado validadores. Na validação foi utilizado a biblioteca FluentValidation.

Para instalar not terminal, navege até o diretório do projeto *Application*  e digite o comando:

```bash
dotnet add package FluentValidation
```

Após a instalação da biblioteca pode ser escrito o validador.

```csharp
public class CreateWeatherForecastValidator : AbstractValidator<CreateWeatherForecastRequestContract>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="CreateWeatherForecastValidator"/> class.
    /// </summary>
    public CreateWeatherForecastValidator()
    {
        RuleFor(x => x.Date)
            .NotNull()
            .LessThanOrEqualTo(DateTime.Now.AddMinutes(1));

        RuleFor(x => x.TemperatureCelsius)
            .NotNull()
            .LessThanOrEqualTo(200)
            .GreaterThanOrEqualTo(-200);

        RuleFor(x => x.Summary)
            .Length(0, 250);
    }
}
```

```csharp
public class UpdateWeatherForecastValidator : AbstractValidator<UpdateWeatherForecastRequestContract>
{
    public UpdateWeatherForecastValidator()
    {
        RuleFor(x => x.Id)
            .NotNull()
            .NotEqual(Guid.Empty);

        RuleFor(x => x.Date)
            .NotNull()
            .LessThanOrEqualTo(DateTime.Now);

        RuleFor(x => x.TemperatureCelsius)
            .NotNull()
            .LessThanOrEqualTo(200)
            .GreaterThanOrEqualTo(-200);

        RuleFor(x => x.TemperatureFahrenheit)
            .NotNull();

        RuleFor(x => x)
            .Custom((contract, context) =>
            {
                if (contract.TemperatureCelsius is null)
                    return;

                if (contract.TemperatureFahrenheit is null)
                    return;

                var fahrenheit = contract.TemperatureCelsius?.ToFahrenheit();
                if (fahrenheit != contract.TemperatureFahrenheit)
                    context.AddFailure(nameof(contract.TemperatureFahrenheit), "Temperature Fahrenheit and Celsius does not match.");
            });

        RuleFor(x => x.Summary)
            .Length(0, 250);
    }
}
```

#### Serviço

As classes criadas em serviço tem como objetivo executar uma determinada operação. Bom, sei que parece repetitivo mas pense que esta é a classe chave. Para exemplificar, a nossa aplicação terá uma chamada que será buscar clima por **Id**. Então teremos uma classe no *namespace* que a sua responsabilidade é buscar clima por **Id**.

Gosto de dizer que esta classe só pode ter um único método publico e caso seja necessário criar métodos complementares de auxilio ao objetivo do serviço estes métodos devem ser privados. Esta classe pode utilizar um validador, como busca em banco de dados deverá utilizar o repositório e por assim vai. Na pratica fica mais fácil de entender.

Então segue os serviços gerados:

```csharp
public interface ICreateWeatherForecastService
{
    Task<GetWeatherForecastResponseContract> CreateWeatherForecastAsync(CreateWeatherForecastRequestContract request);
}

public class CreateWeatherForecastService : ICreateWeatherForecastService
{
    private readonly CreateWeatherForecastValidator _validator = new();
    private readonly IWeatherForecastRepository _weatherForecastRepository;

    public CreateWeatherForecastService(IWeatherForecastRepository weatherForecastRepository)
    {
        _weatherForecastRepository = weatherForecastRepository;
    }

    public async Task<GetWeatherForecastResponseContract> CreateWeatherForecastAsync(CreateWeatherForecastRequestContract request)
    {
        Validate(request);

        var weather = await CreateWeatherForecast(request);

        return CreateResponse(weather);
    }

    private void Validate(CreateWeatherForecastRequestContract request)
    {
        var validationResult = _validator.Validate(request);
        ValidationException.ThrowIf(!validationResult.IsValid, validationResult.Errors);
    }

    private async Task<WeatherForecast> CreateWeatherForecast(CreateWeatherForecastRequestContract request)
    {
        var weather = new WeatherForecast
        {
            Id = Guid.NewGuid(),
            Date = request.Date ?? DateTime.Now,
            TemperatureCelsius = request.TemperatureCelsius ?? default,
            Summary = request.Summary
        };

        await _weatherForecastRepository.AddAsync(weather);

        return weather;
    }

    private static GetWeatherForecastResponseContract CreateResponse(WeatherForecast weather)
    {
        return new GetWeatherForecastResponseContract
        {
            Id = weather.Id,
            Date = weather.Date,
            TemperatureCelsius = weather.TemperatureCelsius,
            Summary = weather.Summary
        };
    }
}
```

```csharp
public interface IDeleteWeatherForecastService
{
    Task DeleteWeatherForecastAsync(Guid? weatherForecastId);
}

public class DeleteWeatherForecastService : IDeleteWeatherForecastService
{
    private readonly IWeatherForecastRepository _weatherForecastRepository;

    public DeleteWeatherForecastService(IWeatherForecastRepository weatherForecastRepository)
    {
        _weatherForecastRepository = weatherForecastRepository;
    }

    public Task DeleteWeatherForecastAsync(Guid? weatherForecastId)
    {
        BadRequestException.ThrowIf(weatherForecastId is null, "Weather forecast identification is required.");

        return _weatherForecastRepository.DeleteAsync(weatherForecastId!.Value);
    }
}
```

Para o método de buscar foi criado uma mecanismo para que seja possível efetuar consulta por meio de [query string](https://en.wikipedia.org/wiki/Query_string). Para isto foi necessário a elaboração de uma classe e um enumerador para fosse possível registrar o valor e o tipo de operação.

```csharp
public enum Operation
{
    Equal,
    NotEqual,
    GreaterThan,
    GreaterThanOrEqual,
    LessThan,
    LessThanOrEqual,
    Contains,
    StartsWith,
    EndsWith
}

public sealed class OperationParam<T>
{
    public Operation Operation { get; set; }
    public T Value { get; set; } = default!;
}
```

```csharp
public interface IGetAllWeatherForecastsService
{
    Task<IEnumerable<GetWeatherForecastResponseContract>> GetAllWeatherForecastsAsync(string? param);
}

public class GetAllWeatherForecastsService : IGetAllWeatherForecastsService
{
    private readonly IWeatherForecastRepository _weatherForecastRepository;

    public GetAllWeatherForecastsService(IWeatherForecastRepository weatherForecastRepository)
    {
        _weatherForecastRepository = weatherForecastRepository;
    }

    public async Task<IEnumerable<GetWeatherForecastResponseContract>> GetAllWeatherForecastsAsync(string? param)
    {
        var date = param?.ExtractDateParam();
        var temperatureCelsius = param?.ExtractTemperatureCelsiusParam();
        var temperatureFahrenheit = param?.ExtractTemperatureFahrenheitParam();

        var weathers = _weatherForecastRepository.Get();
        weathers = FilterByDate(weathers, date);
        weathers = FilterByTemperatureCelsius(weathers, temperatureCelsius);
        weathers = FilterByTemperatureFahrenheit(weathers, temperatureFahrenheit);

        return await weathers.Select(x => new GetWeatherForecastResponseContract
            {
                Id = x.Id,
                Date = x.Date,
                TemperatureCelsius = x.TemperatureCelsius,
                Summary = x.Summary
            })
            .ToListAsync();
    }

    private static IQueryable<WeatherForecast> FilterByDate(IQueryable<WeatherForecast> weathers, OperationParam<DateTime>? filter)
    {
        return filter?.Operation switch
        {
            Operation.GreaterThan => weathers.Where(w => w.Date > filter.Value),
            Operation.LessThan => weathers.Where(w => w.Date < filter.Value),
            Operation.Equal => weathers.Where(w => w.Date == filter.Value),
            Operation.NotEqual => weathers.Where(w => w.Date != filter.Value),
            Operation.GreaterThanOrEqual => weathers.Where(w => w.Date >= filter.Value),
            Operation.LessThanOrEqual => weathers.Where(w => w.Date <= filter.Value),
            _ => weathers
        };
    }

    private static IQueryable<WeatherForecast> FilterByTemperatureCelsius(IQueryable<WeatherForecast> weathers, OperationParam<decimal>? filter)
    {
        return filter?.Operation switch
        {
            Operation.GreaterThan => weathers.Where(w => w.TemperatureCelsius > filter.Value),
            Operation.LessThan => weathers.Where(w => w.TemperatureCelsius < filter.Value),
            Operation.Equal => weathers.Where(w => w.TemperatureCelsius == filter.Value),
            Operation.NotEqual => weathers.Where(w => w.TemperatureCelsius != filter.Value),
            Operation.GreaterThanOrEqual => weathers.Where(w => w.TemperatureCelsius >= filter.Value),
            Operation.LessThanOrEqual => weathers.Where(w => w.TemperatureCelsius <= filter.Value),
            _ => weathers
        };
    }

    private static IQueryable<WeatherForecast> FilterByTemperatureFahrenheit(IQueryable<WeatherForecast> weathers, OperationParam<decimal>? filter)
    {
        return filter?.Operation switch
        {
            Operation.GreaterThan => weathers.Where(w => w.TemperatureCelsius.ToFahrenheit() > filter.Value),
            Operation.LessThan => weathers.Where(w => w.TemperatureCelsius.ToFahrenheit() < filter.Value),
            Operation.Equal => weathers.Where(w => w.TemperatureCelsius.ToFahrenheit() == filter.Value),
            Operation.NotEqual => weathers.Where(w => w.TemperatureCelsius.ToFahrenheit() != filter.Value),
            Operation.GreaterThanOrEqual => weathers.Where(w => w.TemperatureCelsius.ToFahrenheit() >= filter.Value),
            Operation.LessThanOrEqual => weathers.Where(w => w.TemperatureCelsius.ToFahrenheit() <= filter.Value),
            _ => weathers
        };
    }
}
```

```csharp
public interface IGetWeatherForecastService
{
    Task<GetWeatherForecastResponseContract> GetWeatherForecastAsync(Guid? weatherForecastId);
}

public class GetWeatherForecastService : IGetWeatherForecastService
{
    private readonly IWeatherForecastRepository _weatherForecastRepository;

    public GetWeatherForecastService(IWeatherForecastRepository weatherForecastRepository)
    {
        _weatherForecastRepository = weatherForecastRepository;
    }

    public async Task<GetWeatherForecastResponseContract> GetWeatherForecastAsync(Guid? weatherForecastId)
    {
        BadRequestException.ThrowIf(weatherForecastId is null, "Weather forecast identification is required.");

        var weather = await _weatherForecastRepository.GetAsync(weatherForecastId!.Value);

        NotFoundException.ThrowIf(weather is null, $"No weather forecast found by id {weatherForecastId}");

        return new GetWeatherForecastResponseContract
        {
            Id = weather!.Id,
            Date = weather.Date,
            TemperatureCelsius = weather.TemperatureCelsius,
            Summary = weather.Summary
        };
    }
}
```

```csharp
public interface IUpdateWeatherForecastService
{
    Task<GetWeatherForecastResponseContract> UpdateWeatherForecastAsync(Guid? weatherForecastId, UpdateWeatherForecastRequestContract request);
}

public class UpdateWeatherForecastService : IUpdateWeatherForecastService
{
    private readonly UpdateWeatherForecastValidator _validator = new();
    private readonly IWeatherForecastRepository _weatherForecastRepository;

    public UpdateWeatherForecastService(IWeatherForecastRepository weatherForecastRepository)
    {
        _weatherForecastRepository = weatherForecastRepository;
    }

    public async Task<GetWeatherForecastResponseContract> UpdateWeatherForecastAsync(Guid? weatherForecastId, UpdateWeatherForecastRequestContract request)
    {
        BadRequestException.ThrowIf(weatherForecastId != request.Id, "Weather forecast identification mismatch.");

        Validate(request);

        var weather = GetDatabaseWeatherForecast(request);

        await UpdateWeatherForecastAsync(weather);

        return CreateRespoonse(weather);
    }

    private void Validate(UpdateWeatherForecastRequestContract request)
    {
        var validationResult = _validator.Validate(request);
        ValidationException.ThrowIf(!validationResult.IsValid, validationResult.Errors);
    }

    private WeatherForecast GetDatabaseWeatherForecast(UpdateWeatherForecastRequestContract request)
    {
        var databaseWeatherForecast = _weatherForecastRepository.Get(request.Id!.Value);

        NotFoundException.ThrowIf(databaseWeatherForecast is null, "Weather forecast not found.");

        databaseWeatherForecast!.Date = request.Date ?? DateTime.Now;
        databaseWeatherForecast.Summary = request.Summary ?? databaseWeatherForecast.Summary;
        databaseWeatherForecast.TemperatureCelsius = request.TemperatureCelsius ?? databaseWeatherForecast.TemperatureCelsius;

        return databaseWeatherForecast;
    }

    private Task UpdateWeatherForecastAsync(WeatherForecast weatherForecast)
    {
        return _weatherForecastRepository.UpdateAsync(weatherForecast);
    }

    private static GetWeatherForecastResponseContract CreateRespoonse(WeatherForecast weatherForecast)
    {
        return new GetWeatherForecastResponseContract
        {
            Id = weatherForecast.Id,
            Date = weatherForecast.Date,
            Summary = weatherForecast.Summary,
            TemperatureCelsius = weatherForecast.TemperatureCelsius
        };
    }
}
```

### Projeto WebApi

Em relação a regras, validações e consultas vimos que foi elaborado todos os métodos onde um projeto somado ao anterior vai agregando valor para que este seja entregue ao usuário requisitando a operação. Tendo isto como premissa, o projeto de **WebApi** é apenas uma casca a ser utilizada como uma interface ao usuário.

É neste projeto que terá os métodos acessíveis ao usuário através da internet. Apesar de que este projeta seja apenas uma casca ele irá requerer algumas configurações para o seu devido funcionamento.

#### Padronização de erro

Então vamos começar pelas alterações feitas no *Program.cs*. Iremos configurar um comportamento na nossa API para todos os erros sejam reescrito para um objeto comum, a classe **ErrorModel** criada no projeto *Shared*.

1. Devemos suprimir o comportamento existente da APi para que possamos incluir o nosso. Para isto incluímos o seguinte comando em **Program.cs**.

```csharp
_ = builder.Services.Configure<ApiBehaviorOptions>(options =>
    {
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
    });
```

2. Devemos criar o nosso padrão de tratamento de erro. Para isto devemos criar uma classe que herda o comportamento de **ExceptionFilterAttribute**. Aqui todas as vezes que nossa aplicação tiver uma exceção será executado o método *OnException*, então tratamos a exceção e enviamos o objeto resposta que desejamos. Observe que é aqui que se torna claro o principal motivo da criação de nossas exceções feitas no projeto **Shared**.


```csharp
[AttributeUsage(AttributeTargets.All)]
public sealed class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    private const string MediaType = "application/json";

    public override void OnException(ExceptionContext context)
    {
        object content = new { message = context.Exception.Message };
        HttpStatusCode code;
        switch (context.Exception.GetType().Name)
        {
            case nameof(BadHttpRequestException):
            case nameof(DbRegisterExistsException):
            case nameof(DeleteFailureException):
            case nameof(SaveFailureException):
                code = HttpStatusCode.BadRequest;
                break;

            case nameof(NotFoundException):
                code = HttpStatusCode.NotFound;
                break;

            case nameof(NotificationFailureException):
                code = HttpStatusCode.InternalServerError;
                break;

            case nameof(ValidationException):
                code = HttpStatusCode.NotAcceptable;
                content = ((ValidationException)context.Exception).Failures;
                break;

            default:
                code = HttpStatusCode.BadRequest;
                content = new
                {
                    message = "Something is wrong. Your Request could not be processed.",
                    exception = context.Exception.Message
                };
                break;
        }

        BuildContext(context, (int)code, content);
    }

    private static void BuildContext(ExceptionContext context, int code, object content)
    {
        context.HttpContext.Response.ContentType = MediaType;
        context.HttpContext.Response.StatusCode = code;
        var response = new ErrorModel
        {
            Code = code,
            Error = content,
            Exception = context.Exception.GetType().Name,
            StackTrace = context.Exception.StackTrace
        };

        context.Result = new ContentResult
        {
            StatusCode = code,
            ContentType = MediaType,
            Content = response.ToJson()
        };
    }
}
```

3. Com o filtro criado agora devemos adiciona-lo ao pipeline de nossa API. Em **Program.cs** Adicione o comando:

```csharp
    _ = builder.Services.Configure<ApiBehaviorOptions>(options =>
    {
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
    });

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add(typeof(CustomExceptionFilterAttribute));
        options.RespectBrowserAcceptHeader = true;
    });
```

Em adicional vamos incluir uma configuração extra para a serialização da resposta da API. Nesta configuração iremos incluir para que os objetos com campo nulos não seja inclusos no JSON de resposta. Assim diminuímos a quantidade de dados enviamos e aumentamos a velocidade da resposta, em grande volume de requisição isto fará muita diferença.

```csharp
    builder.Services.AddControllers(options =>
    {
        options.Filters.Add(typeof(CustomExceptionFilterAttribute));
        options.RespectBrowserAcceptHeader = true;
    })
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        options.JsonSerializerOptions.WriteIndented = false;
    });
```

4. Incluímos o filtro para a padronização mas nem todas as exceções passam pelo filtro. Não irei entrar em detalhes no motivo mas caso tenha interesse recomendo um estudo sobre o fluxo do pipeline do **WebApi**. Voltando a nossa aplicação, para garantir 100% do tratamento também iremos criar um *middleware* para as requisições.

```csharp
public sealed class RequestHandlerMiddleware
{
    private const string MediaType = "application/json";
    private const string UnauthorizedMessage = "Unauthorized access";
    private const string DefaultErrorMessage = "An error occurred while processing your request";
    private readonly RequestDelegate _next;

    public RequestHandlerMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);

            if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
                throw new UnauthorizedAccessException(UnauthorizedMessage);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception? exception)
    {
        var code = exception switch
        {
            BadRequestException => HttpStatusCode.BadRequest,
            NotFoundException => HttpStatusCode.NotFound,
            DeleteFailureException => HttpStatusCode.BadRequest,
            NotificationFailureException => HttpStatusCode.BadRequest,
            SaveFailureException => HttpStatusCode.BadRequest,
            ValidationException => HttpStatusCode.BadRequest,
            _ => HttpStatusCode.InternalServerError
        };

        context.Response.ContentType = MediaType;
        context.Response.StatusCode = (int)code;
        var error = new ErrorModel
        {
            Code = (int)code,
            Error = exception?.Message ?? DefaultErrorMessage,
            Exception = exception?.GetType().Name,
            StackTrace = exception?.StackTrace
        };

        return context.Response.WriteAsync(error.ToJson());
    }
}
```

5. Agora inclusos o *middleware* em nosso fluxo. No **Program.cs** incluimos o comando:

```csharp
var app = builder.Build();

app.UseMiddleware<RequestHandlerMiddleware>();
```

#### Injetando dependências na aplicação

Sem entrar em descrição técnica sobre o que é injeção de dependência e ao mesmo tempo colocando em um formato ultra simplificado, injeção de dependência é você incluir no contexto de sua aplicação classe que poderão ser utilizadas durante a execução de alguma operação sem se preocupar com o que irá acontecer com ela posteriormente e/ou como ela é gerada.

Para maiores informações a respeito leiam (Injeção de dependência no ASP.NET Core)(https://learn.microsoft.com/pt-br/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2) e [Porque devemos utilizar a injeção de dependência no .NET ?](https://dev.to/paulowalravendev/porque-devemos-utilizar-a-injecao-de-dependencia-no-net--3ooj).

Para injetar uma dependência basta incluir o comando em **Program.cs**:

```csharp
    services.AddDbContext<ApiTemplateContext>(o => o.UseInMemoryDatabase("ApiTemplate"));
    services.AddScoped<IWeatherForecastRepository, WeatherForecastRepository>();
    services.AddScoped<ICreateWeatherForecastService, CreateWeatherForecastService>();
    services.AddScoped<IDeleteWeatherForecastService, DeleteWeatherForecastService>();
    services.AddScoped<IGetWeatherForecastService, GetWeatherForecastService>();
    services.AddScoped<IGetAllWeatherForecastsService, GetAllWeatherForecastsService>();
    services.AddScoped<IUpdateWeatherForecastService, UpdateWeatherForecastService>();

    WebApplication app = builder.Build();
```

#### Controller

Resumidamente, controller é onde fica o(s) método(s) com acesso pela internet. Na criação do projeto **WebApi** o template entrega **WeatherForecastControlle.cs** que é criado pelo próprio .NET. Agora iremos modifica-lo para atender tudo que fizemos anteriormente.

```csharp
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    /// <summary>
    ///     List all weather forecasts
    /// </summary>
    /// <remarks>
    ///     For query use: field=[operationType,value]
    ///
    ///     Example: date=[GreaterThan,2019-01-01]
    ///
    ///     OperationType:
    ///     Equal,
    ///     NotEqual,
    ///     GreaterThan,
    ///     GreaterThanOrEqual,
    ///     LessThan,
    ///     LessThanOrEqual
    /// </remarks>
    /// <param name="service"></param>
    /// <param name="param"></param>
    /// <returns></returns>
    [HttpGet]
    [ProducesResponseType(typeof(IEnumerable<GetWeatherForecastResponseContract>), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ErrorModel), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<IEnumerable<GetWeatherForecastResponseContract>>> GetAsync([FromServices] IGetAllWeatherForecastsService service, string? param = null)
    {
        var result = await service.GetAllWeatherForecastsAsync(param);
        if (!result.Any())
            return NoContent();

        return Ok(result);
    }

    /// <summary>
    /// Get a specific weather forecast
    /// </summary>
    /// <param name="service"></param>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpGet("{id}")]
    [ProducesResponseType(typeof(GetWeatherForecastResponseContract), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ErrorModel), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<GetWeatherForecastResponseContract>> GetASync([FromServices] IGetWeatherForecastService service, Guid? id)
    {
        return Ok(await service.GetWeatherForecastAsync(id));
    }

    /// <summary>
    ///   Create a new weather forecast
    /// </summary>
    /// <param name="service"></param>
    /// <param name="request"></param>
    /// <returns></returns>
    [HttpPost]
    [ProducesResponseType(typeof(GetWeatherForecastResponseContract), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ErrorModel), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<GetWeatherForecastResponseContract>> PostAsync([FromServices] ICreateWeatherForecastService service,
        [FromBody] CreateWeatherForecastRequestContract request)
    {
        var result = await service.CreateWeatherForecastAsync(request);
        return Created("weatherforecast", result);
    }

    /// <summary>
    ///  Update a weather forecast
    /// </summary>
    /// <param name="service"></param>
    /// <param name="id"></param>
    /// <param name="request"></param>
    /// <returns></returns>
    [HttpPut("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ErrorModel), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult> PutAsync([FromServices] IUpdateWeatherForecastService service, Guid id, [FromBody] UpdateWeatherForecastRequestContract request)
    {
        _ = await service.UpdateWeatherForecastAsync(id, request);
        return NoContent();
    }

    /// <summary>
    ///  Delete a weather forecast
    /// </summary>
    /// <param name="service"></param>
    /// <param name="id"></param>
    [HttpDelete("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ErrorModel), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult> DeleteAsync([FromServices] IDeleteWeatherForecastService service, Guid? id)
    {
        await service.DeleteWeatherForecastAsync(id);
        return NoContent();
    }
}
```

Na **controller** que apresentei não contém construtor onde passo as instâncias de classes necessárias para os métodos subsequentes. Isto porque cada método tem uma *dependência* diferente e se for passado no construtor ao executar o método **Get** irei instanciar as dependências de todos os métodos.

Então, como já injeto as dependências na aplicação. Então basta informar na declaração do método que para execução deve ser obtido uma dependência contida na aplicação. Para fazer isto na declaração do método adiciono a seguinte informação '[FromService]' seguido pelo nome da classe que necessito e a declaração da variável para o uso.

```csharp
public async Task<ActionResult<IEnumerable<GetWeatherForecastResponseContract>>> GetAsync([FromServices] IGetAllWeatherForecastsService service, string? param = null)
```

Então quando o método for executado apenas aquela dependência irá ser instanciada.

Acima de cada método existe uma TAG XML com ***summary***, o texto que estiver dentro da TAG irá ser apresentado no Swagger como a descrição do método.

A TAG '[ProducesResponseType(StatusCodes.Status204NoContent)]' irá mostrar no swagger que o método pode apresentar o código HTTP 204.

A TAG '[ProducesResponseType(typeof(ErrorModel), StatusCodes.Status400BadRequest)]' irá mostrar no swagger que o método pode apresentar o código HTTP 400 retornando o objeto do tipo **ErrorModel**.

Bom, imagino que conseguiram entender a lógica das TAG vinculadas aos métodos. Claro que um melhor entendimento é sempre aconselhável a leitura da documentação técnica dos componentes utilizados assim como a documentação do próprio .NET.

Chegamos ao fim desta breve longa postagem. Sei que alguns ponto poderiam ser melhor explicado mas deixo o código fonte disponível em meu [github](https://github.com/rodabarbosa/apitemplate) e qualquer duvida deixe um comentário que estarei respondendo assim que possível ou mesmo criando uma nova postagem abordando algo especifico.