Ir para o conteúdo
Como criar uma API Web baseada em função com o ASP.NET Core

Como criar uma API Web baseada em função com o ASP.NET Core

Aqui está outra ótima postagem de blog de instruções. Ele mostrará como usar o ASP.NET Core para criar uma API Web baseada em função e a interface do usuário do Swagger para visualizar e interagir com pontos de extremidade.

20min de leitura

Quando se trata de criar uma API Web baseada em função com ASP.NET Core, a abordagem de código em primeiro lugar pode ser um método poderoso e eficiente. Usando-o, podemos definir nossos modelos de dados e relacionamentos em código e, em seguida, gerar o esquema de banco de dados correspondente automaticamente. A que isso leva? Ciclos de desenvolvimento mais rápidos e maior flexibilidade, com certeza. Por quê? Porque as alterações no modelo de dados podem ser feitas de forma rápida e fácil, sem a necessidade de modificar o esquema do banco de dados diretamente. Você pode ler mais sobre as abordagens Design First e Code First no swagger.io.

Neste tutorial, abordaremos as etapas para criar uma API Web baseada em função usando o ASP.NET Core 6. Usaremos a interface do usuário do Swagger para visualizar e interagir com nossos endpoints e o MS SQL Server como nosso banco de dados. O aplicativo incluirá um módulo de autenticação e um módulo de evento. Os usuários conectados poderão visualizar os eventos associados à sua conta, enquanto os usuários com a função de Administrador podem criar, atualizar e excluir eventos.

Vamos começar!

Configuração do projeto

Primeiro, precisamos configurar nosso projeto. Para fazer isso, abra o Visual Studio, vá para criar um novo projeto e escolha ASP.NET API Web Principal.

abra o Visual Studio, vá para criar um novo projeto e escolha ASP.NET API Web Principal

Escolha o nome do aplicativo e clique em Avançar.

Escolha o nome do aplicativo no Visual Studio

Configurando o banco de dados de API

Depois de inicializarmos nosso aplicativo, precisamos configurar o banco de dados. Vamos usar o EntityFrameworkCore como ORM, então isso nos ajudará a gerenciar nosso banco de dados. Por esse motivo, devemos instalar alguns pacotes.

Configurando o banco de dados de API no Visual Studio

A próxima coisa a fazer depois de instalar os pacotes com sucesso é criar um DbContext. Crie DataContext.cs arquivo e herde a classe DBContext. Aqui vamos definir nossas tabelas.

public class DataContext: DbContext
{
  public DataContext(DbContextOptions options): base(options)
  {
  }

  //Define our tables
}

Em seguida, devemos abrir Program.cs arquivo e adicionar o dbContext. Devemos especificar dbProvider e cadeia de conexão que vêm de appsettings.json arquivo. O dbProvider pode ser SqlServer, MySql ou InMemory.

// Add Db context

var dbProvider = builder.Configuration.GetConnectionString("Provider");

builder.Services.AddDbContext < DataContext > (options =>
  {

    if (dbProvider == "SqlServer")
    {
      options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServerConnectionString"));
    }
  });

Verifique se você adicionou o ConnectionString e o Provedor ao arquivo appsettings.json da seguinte maneira:

…
"ConnectionStrings": {
  "Provider": "SqlServer",
  "SqlServerConnectionString": "Data Source=(localdb)\\MSSQLLocalDB;Database=HRApplication2;Integrated Security=True;Connect Timeout=30; "
},
…

Após configurar o DbContext, é necessário gerar os modelos de banco de dados. Nesse caso, exigimos duas entidades – Usuário e Evento – e uma terceira tabela – UserEvent – para estabelecer uma relação muitos-para-muitos entre elas. Para fazer isso, é recomendável criar uma pasta Models e uma subpasta DbModels dentro dela, onde podemos criar nossas entidades de banco de dados.

Vamos começar com o modelo do usuário. Cada usuário deve ter um ID exclusivo, e-mail, nome, sobrenome, senha que será armazenado em um formato com hash, função que pode ser usuário e administrador para a demonstração e UserEvents que estará relacionado à tabela UserEvent.

public class User
{
    public string UserId { get; set; }
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Password { get; set; }
    public string Role { get; set; }
    public IList <UserEvent> UserEvents { get; set; }
}

O modelo de evento também deve ter ID, título, categoria, data e também relação exclusiva com a tabela UserEvents.

public class Event
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Category { get; set; }
    public DateTime Date { get; set; }
    public IList <UserEvent> UserEvents { get; set; }
}

Como um usuário pode participar de vários eventos e um evento pode ser assistido por vários usuários, precisamos estabelecer uma relação muitos-para-muitos entre essas entidades. Para fazer isso, criaremos uma tabela adicional chamada UserEvents. Essa tabela incluirá as colunas UserId e EventId que estabelecerão a relação entre as entidades User e Event.

public class UserEvent
{
    public string UserId { get; set; }
    public User User { get; set; }
    public string EventId { get; set; }
    public Event Event { get; set; }
}

Depois de criarmos nossos modelos de banco de dados, a próxima etapa é registrá-los em nosso DbContext. Para conseguir isso, podemos navegar até o arquivo DataContext.cs, adicionar todas as entidades como DbSets e declarar nossos relacionamentos e chaves primárias. Isso pode ser feito substituindo o método OnModelCreating e utilizando a API fluente para configurar as relações e as chaves. Depois de concluído, o resultado deve aparecer da seguinte forma:

public class DataContext: DbContext
{
  public DataContext(DbContextOptions options): base(options)
  {
  }
  
  public DbSet <User> Users { get; set; }
  public DbSet < Event > Events { get; set; }
  public DbSet < UserEvent > UserEvents { get; set; }

  protected override void OnModelCreating(ModelBuilder builder)
  {
    base.OnModelCreating(builder);

    builder.Entity<User>()
      .HasKey(u => new {
        u.UserId
      });

    builder.Entity<Event>()
      .HasKey(e => new {
        e.Id
      });

    builder.Entity<UserEvent>()
      .HasKey(ue => new {
        ue.UserId, ue.EventId
      });

    builder.Entity<UserEvent>()
      .HasOne(ue => ue.User)
      .WithMany(user => user.UserEvents)
      .HasForeignKey(u => u.UserId);

    builder.Entity<UserEvent>()
      .HasOne(uc => uc.Event)
      .WithMany(ev => ev.UserEvents)
      .HasForeignKey(ev => ev.EventId);
  }
}

Depois de estarmos prontos com o design do banco de dados, devemos gerar uma migração inicial que criará o banco de dados.

Abra o Console do Gerenciador de Pacotes e escreva o comando:

Adicionar migração inicialCriar

Add-Migration InitialCreate in Project Manager Console

Depois de executado com sucesso, devemos atualizar o banco de dados com:

Atualizar banco de dados

Em seguida, com o Microsoft SQL Management Studio, você deverá ver o banco de dados recém-criado.

Configurando o AutoMapper

O AutoMapper nos ajudará a transformar um modelo em outro. Isso converterá os modelos de entrada em dbModels. A razão pela qual estamos fazendo isso é que talvez não precisemos que todas as propriedades de um dos modelos sejam incluídas no outro modelo. Você verá exatamente como vamos usá-lo mais adiante no tutorial. Antes disso, primeiro precisamos configurá-lo. Você pode encontrar uma explicação mais detalhada do AutoMapper na documentação oficial.

Para começar, devemos instalar o pacote NuGet do AutoMaper. Em seguida, podemos gerar um arquivo MappingProfiles.cs para definir todos os mapeamentos. Recomenda-se criar esse arquivo em uma pasta Helpers para fins de organização.

Para declarar nossos mapeamentos, MappingProfiles deve herdar a classe Profile e podemos declarar nossos mapeamentos usando o método CreateMap<from, to>(). Se precisarmos da capacidade de mapear modelos na direção oposta, podemos incluir o. ReverseMap().

Depois de concluir nossos mapeamentos, devemos navegar até o arquivo Program.cs e registrar o AutoMapper com nossos MappingProfiles.

…
var config = new MapperConfiguration(cfg =>
  {
    cfg.AddProfile(new MappingProfiles());
  });

var mapper = config.CreateMapper();

builder.Services.AddSingleton(mapper);
…

Configurando a autenticação

Vamos usar tokens JWT para autenticação. Eles nos fornecem uma maneira de transmitir informações com segurança entre as partes como objeto JSON. Você pode ler mais sobre tokens JWT aqui. Para usá-los, devemos primeiro instalar os pacotes NuGet necessários. Exigimos Microsoft.IdentityModel.Tokens e Microsoft.AspNetCore.Authentication.JwtBearer.

Em seguida, devemos definir algumas configurações de token no arquivo appsettings.json. Essas configurações incluem o Emissor, o Público-alvo e a SecretKey.

"Jwt": {
  "Issuer": "https://localhost:7244/",
  "Audience": "https://localhost:7244/",
  "Key": "S1u*p7e_r+S2e/c4r6e7t*0K/e7y"
}

Depois que as configurações de token forem definidas, podemos configurar o serviço JWT no arquivo Program.cs. Isso envolve especificar o esquema que será usado junto com todos os parâmetros de validação necessários.

…
builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(o =>
    {
        o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey
        (Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = false,
            ValidateIssuerSigningKey = true
    };
  });
…

Certifique-se de ter adicionado também o aplicativo. UseAuthentication();

Configurando o Swagger

Para testar nossos endpoints de aplicativo usando a interface do usuário do Swagger, devemos incluir app. UseSwaggerUI() no arquivo Program.cs.

Depois disso, devemos gerar um filtro AuthResponse para ajudar a testar nossos endpoints autenticados usando tokens JWT. Para fazer isso, podemos criar uma classe AuthResponsesOperationFilter que implementa a interface IOperationFilter. O método Apply deve incluir a lógica necessária para adicionar o filtro AuthResponse ao Swagger.

public class AuthResponsesOperationFilter: IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var authAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
            .Union(context.MethodInfo.GetCustomAttributes(true))
            .OfType<AuthorizeAttribute>();

        if (authAttributes.Any())
        {
            var securityRequirement = new OpenApiSecurityRequirement()
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer"
                        }
                    },
                    new List<string>()
                }
            };

            operation.Security = new List<OpenApiSecurityRequirement> {
                securityRequirement
            };

            operation.Responses.Add("401", new OpenApiResponse {
                Description = "Unauthorized"
            });
        }
    }
}

Depois disso, certifique-se de ter adicionado o filtro como uma opção em Program.cs . AddSwaggerGen.

builder.Services.AddSwaggerGen(option =>
    {
        option.SwaggerDoc("v1", new OpenApiInfo {
            Title = "Northwind CRUD", Version = "v1"
        });

        option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
        {
            In = ParameterLocation.Header,
            Description = "Please enter a valid token",
            Name = "Authorization",
            Type = SecuritySchemeType.Http,
            BearerFormat = "JWT",
            Scheme = "bearer"
        });

        option.OperationFilter < AuthResponsesOperationFilter > ();
    }
  );

Você pode ler uma explicação mais detalhada de "O que é Swagger?" na documentação oficial.

Registrar endpoint

Depois de terminarmos as configurações, podemos prosseguir para a criação do endpoint de registro. A primeira etapa é gerar um arquivo RegisterInputModel.cs, que deve estar localizado na pasta Models/InputModels.

O processo de registro requer os campos E-mail, Nome, Sobrenome, Senha e Senha Confirmada. Todos esses campos são obrigatórios, portanto, incluiremos o atributo [Obrigatório]. Também incluiremos o atributo [EmailAddress] para o campo Email. Podemos adicionar atributos adicionais, como comprimento mínimo e máximo, conforme desejado. No entanto, para os propósitos desta demonstração, vamos nos ater a esses atributos.

public class RegisterInputModel
{
    [EmailAddress]
    public string Email { get; set; }

    [Required]
    public string FirstName { get; set; }

    [Required]
    public string LastName { get; set; }

    [Required]
    public string Password { get; set; }

    [Required]
    public string ConfirmedPassword { get; set; }
}

Em seguida, devemos adicionar um mapeamento ao arquivo MappingProfiles.cs que permitirá a conversão entre os modelos RegisterInputModel e User em ambas as direções.

CreateMap<RegisterInputModel, User>(). Mapa Reverso();

Para manter a separação de preocupações, criaremos uma pasta Serviços. Cada módulo terá seu próprio serviço para interagir com o banco de dados. Podemos começar gerando um arquivo AuthService.cs e injetar o DataContext e o Configuration.

Nosso primeiro método no AuthService.cs deve ser GenerateJwtToken, que usa o e-mail e a função como parâmetros e retorna um token JWT contendo informações do usuário.

public string GenerateJwtToken(string email, string role)
{
    var issuer = this.configuration["Jwt:Issuer"];
    var audience = this.configuration["Jwt:Audience"];
    var key = Encoding.ASCII.GetBytes(this.configuration["Jwt:Key"]);
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new []
                {
                    new Claim("Id", Guid.NewGuid().ToString()),
                        new Claim(JwtRegisteredClaimNames.Sub, email),
                        new Claim(JwtRegisteredClaimNames.Email, email),
                        new Claim(ClaimTypes.Role, role),
                        new Claim(JwtRegisteredClaimNames.Jti,
                            Guid.NewGuid().ToString())
                }),
            Expires = DateTime.UtcNow.AddMinutes(5),
            Issuer = issuer,
            Audience = audience,
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    var token = tokenHandler.CreateToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);
}

Para fazer o hash da senha, usaremos BCrypt.Net.BCrypt. Para começar, devemos instalar o pacote e adicioná-lo como uma instrução using no início do arquivo.

usando BC = BCrypt.Net.BCrypt;

Depois, criaremos vários métodos auxiliares. Um verificará se existe um usuário com um determinado e-mail, outro autenticará o usuário e mais dois obterão um usuário por e-mail e ID.

public bool IsAuthenticated(string email, string password)
{
    var user = this.GetByEmail(email);
    return this.DoesUserExists(email) && BC.Verify(password, user.Password);
}

public bool DoesUserExists(string email)
{
    var user = this.dataContext.Users.FirstOrDefault(x => x.Email == email);
    return user != null;
}

public User GetById(string id)
{
    return this.dataContext.Users.FirstOrDefault(c => c.UserId == id);
}

public User GetByEmail(string email)
{
    return this.dataContext.Users.FirstOrDefault(c => c.Email == email);
}

Antes de criarmos o método register, devemos primeiro criar um método para gerar um ID exclusivo. Este método pode ser definido da seguinte forma:

public class IdGenerator
{
    public static string CreateLetterId(int length)
    {
        var random = new Random();
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        return new string(Enumerable.Repeat(chars, length)
            .Select(s => s[random.Next(s.Length)]).ToArray());
    }
}

Agora podemos prosseguir com a implementação do método Register. Dentro deste método, vamos gerar um ID único, verificar se ele já existe e, em caso afirmativo, gerar um novo. Em seguida, faremos o hash da senha do usuário e adicionaremos o novo usuário ao banco de dados.

public User RegisterUser(User model)
{
    var id = IdGenerator.CreateLetterId(10);
    var existWithId = this.GetById(id);

    while (existWithId != null)
    {
        id = IdGenerator.CreateLetterId(10);
        existWithId = this.GetById(id);
    }

    model.UserId = id;
    model.Password = BC.HashPassword(model.Password);
    var userEntity = this.dataContext.Users.Add(model);
    this.dataContext.SaveChanges();

    return userEntity.Entity;
}

Se você ainda não criou o AuthController, agora é a hora. Vá para a pasta Controllers e adicione AuthController, que deve herdar a classe Controller. Devemos também adicionar [ApiController] atributo e [Route("[controller]")] para que seja reconhecido pelo Swagger.

Depois disso, devemos injetar o mapeador, authService e logger se usarmos um e criar RegisterMethod. Deve ser pós-solicitação acessível apenas por usuários não autenticados. Ele deve aceitar RegisterInputModel como argumento e verificar se o ModelState é válido. Nesse caso, ele gerará o token jwt para esse usuário. Todo o método deve ficar assim:

[AllowAnonymous]
[HttpPost("Register")]
public ActionResult<string> Register(RegisterInputModel userModel)
{
    try
    {
        if (ModelState.IsValid)
        {
            if (userModel.Password != userModel.ConfirmedPassword)
            {
                return BadRequest("Passwords does not match!");
            }

            if (this.authService.DoesUserExists(userModel.Email))
            {
                return BadRequest("User already exists!");
            }

            var mappedModel = this.mapper.Map<RegisterInputModel, User>(userModel);
            mappedModel.Role = "User";
            
            var user = this.authService.RegisterUser(mappedModel);
            if (user != null)
            {
                var token = this.authService.GenerateJwtToken(user.Email, mappedModel.Role);
                return Ok(token);
            }
            return BadRequest("Email or password are not correct!");
        }

        return BadRequest(ModelState);
    } catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

Funcionalidade de login

A funcionalidade de login é semelhante, exceto que precisamos procurar um usuário no banco de dados. Devemos primeiro criar LoginInputModel.cs que desta vez terá apenas os campos E-mail e Senha. Não se esqueça de adicioná-lo também no MappingProfiles.cs caso contrário, não funcionará.

public class LoginInputModel
{
    [EmailAddress]
    [Required]
    public string Email { get; set; }

    [Required]
    public string Password { get; set; }
}

Em seguida, em AuthController.cs crie o método Login que usará LoginInputModel como parâmetro e verificará se o usuário está autenticado. Nesse caso, ele deve gerar um token. Caso contrário, ele deve retornar um erro.

[AllowAnonymous]
[HttpPost("Login")]
public ActionResult <string> Login(LoginInputModel userModel)
{
    try
    {
        if (ModelState.IsValid)
        {
            if (this.authService.IsAuthenticated(userModel.Email, userModel.Password))
            {
                var user = this.authService.GetByEmail(userModel.Email);
                var token = this.authService.GenerateJwtToken(userModel.Email, user.Role);

                return Ok(token);
            }

            return BadRequest("Email or password are not correct!");
        }

        return BadRequest(ModelState);

    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

Adicionando CRUD para eventos

Depois de terminarmos a autenticação, podemos desenvolver os endpoints para os eventos. Vamos criar operações CRUD completas. De forma idêntica aos usuários, devemos criar EventService.cs arquivo. Ele incluirá um método para obter um evento por ID, obter todos os eventos de um usuário específico, criar um novo evento, atualizar um evento existente e excluir um evento. Todo o arquivo deve ficar assim:

public class EventService
{
    private readonly DataContext dataContext;

    public EventService(DataContext dataContext)
    {
        this.dataContext = dataContext;
    }

    public Event[] GetAllForUser(string email)
    {
        var user = this.dataContext.Users
            .FirstOrDefault(user => user.Email == email);

        return this.dataContext.Events
            .Include(ev => ev.UserEvents)
            .Where(e => e.UserEvents.FirstOrDefault(ue => ue.UserId == user.UserId) != null)
            .ToArray();
    }

    public Event GetById(string id)
    {
        return this.dataContext.Events
            .Include(ev => ev.UserEvents)
            .FirstOrDefault(c => c.Id == id);
    }

    public Event Create(Event model)
    {
        var id = IdGenerator.CreateLetterId(6);
        var existWithId = this.GetById(id);

        while (existWithId != null)
        {
            id = IdGenerator.CreateLetterId(6);
            existWithId = this.GetById(id);
        }

        model.Id = id;
        var eventEntity = this.dataContext.Events.Add(model);
        this.dataContext.SaveChanges();

        return eventEntity.Entity;
    }

    public Event Update(Event model)
    {
        var eventEntity = this.dataContext.Events
            .Include(ev => ev.UserEvents)
            .FirstOrDefault(c => c.Id == model.Id);

        if (eventEntity != null)
        {
            eventEntity.Title = model.Title != null ? model.Title : eventEntity.Title;

            eventEntity.Date = model.Date != null ? model.Date : eventEntity.Date;

            eventEntity.Category = model.Category != null ? model.Category : eventEntity.Category;

            eventEntity.UserEvents = model.UserEvents.Count! > 0 ? model.UserEvents : eventEntity.UserEvents;

            this.dataContext.SaveChanges();
        }

        return eventEntity;
    }

    public Event Delete(string id)
    {
        var eventEntity = this.GetById(id);

        if (eventEntity != null)
        {
            this.dataContext.Events.Remove(eventEntity);
            this.dataContext.SaveChanges();
        }

        return eventEntity;
    }
}

Em seguida, você deve ir até o controlador e configurar um método para cada solicitação.

Criaremos um EventBindingModel que será usado para armazenar todos os dados necessários do modelo Event.

Para o método GetAll, verifique se ele usa uma solicitação GET e recupera o token do usuário, decodifica-o e busca os eventos desse usuário.

[HttpGet]
[Authorize]
public ActionResult<EventBindingModel[]> GetAll()
{
    try
    {
        var userEmail = this.authService.DecodeEmailFromToken(this.Request.Headers["Authorization"]);
        var events = this.eventService.GetAllForUser(userEmail);
        return Ok(this.mapper.Map<Event[], EventBindingModel[]> (events));

    } catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

…
public string DecodeEmailFromToken(string token)
{
    var decodedToken = new JwtSecurityTokenHandler();
    var indexOfTokenValue = 7;
    var t = decodedToken.ReadJwtToken(token.Substring(indexOfTokenValue));

    return t.Payload.FirstOrDefault(x => x.Key == "email").Value.ToString();
}
…

Get by id também deve ser uma solicitação GET com id como parâmetro.

[HttpGet("{id}")]
[Authorize]
public ActionResult<Event> GetById(string id)
{
    try
    {
        var eventEntity = this.eventService.GetById(id);

        if (eventEntity != null)
        {
            return Ok(eventEntity);
        }

        return NotFound();
    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

O endpoint de exclusão será a solicitação DELETE e também usará id como argumento.

[HttpDelete("{id}")]
[Authorize(Roles = "Administrator")]
public ActionResult<Event> Delete(string id)
{
    try
    {
        var eventEntity = this.eventService.Delete(id);

        if (eventEntity != null)
        {
            return Ok(eventEntity);
        }

        return NotFound();
    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

Para simplificar o processo de adição ou atualização de registros de eventos, vamos criar um EventInputModel especificamente para operações Create e Update. Esse modelo exigirá apenas que forneçamos as propriedades essenciais para userEvents, incluindo o título, a categoria, a data, o userId e o eventId. Usando esse modelo, eliminamos a necessidade de especificar todas as propriedades do modelo Event para cada operação.

[HttpPost]
public ActionResult<Event> Create(EventInputModel model)
{
    try
    {
        if (ModelState.IsValid)
        {
            var mappedModel = this.mapper.Map < EventInputModel,
                Event > (model);
            var eventEntity = this.eventService.Create(mappedModel);

            return Ok(eventEntity);
        }

        return BadRequest(ModelState);
    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

Update será uma solicitação PUT e também usará EventInputModel como parâmetro.

[HttpPut]
public ActionResult<Event> Update(EventInputModel model)
{
    try
    {
        if (ModelState.IsValid)
        {
            var mappedModel = this.mapper.Map<EventInputModel, Event>(model);
            var eventEntity = this.eventService.Update(mappedModel);

            if (eventEntity != null)
            {
                return Ok(eventEntity);
            }

            return NotFound();
        }

        return BadRequest(ModelState);
    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

Adicionando autorização baseada em função

Para restringir determinadas ações a funções de usuário específicas, podemos usar a autorização baseada em função. Em nosso cenário, por exemplo, queremos limitar o acesso aos pontos de extremidade Criar, Atualizar e Excluir para eventos a usuários com uma função de Administrador.

Para configurar isso, precisaremos adicionar o aplicativo. UseAuthorization(); ao nosso arquivo Program.cs. Em seguida, para cada ponto de extremidade que requer acesso restrito, adicionaremos o atributo [Authorize], que especificará as funções permitidas. Por exemplo, podemos garantir que apenas os administradores possam acessar o ponto de extremidade Excluir.

…
[Authorize(Roles = "Administrator")]
public ActionResult<Event> Delete(string id)
…

Criando DbSeeder

Ao executar nosso aplicativo, muitas vezes queremos ter alguns dados pré-preenchidos em nosso banco de dados, seja para fins de teste ou outros motivos. É aqui que entra a semeadura. Para começar, precisaremos definir os dados que queremos usar.

Para fazer isso, podemos criar uma pasta Resources e adicionar dois arquivos JSON: um para usuários e outro para eventos. Esses arquivos devem conter os dados que queremos preencher no banco de dados. Por exemplo, nossos arquivos podem ser mais ou menos assim:

[
    {
        "UserId": "USERABCDE",
        "FirstName": "Kate",
        "LastName": "Lorenz",
        "Password": "kate.lorenz",
        "Email": "klorenz@hrcorp.com",
        "Role": "Administrator"
    },
    {
        "UserId": "ASERABCDE",
        "FirstName": "Anthony",
        "LastName": "Murray",
        "Password": "anthony.murray",
        "Email": "amurray@hrcorp.com",
        "Role": "User"
    }
]

Em seguida, devemos criar uma classe DbSeeder que inclua um método Seed. Esse método lerá os dados que definimos anteriormente e os preencherá no banco de dados. Para fazer isso, precisaremos passar o dbContext como um parâmetro.

public class DBSeeder
{
    public static void Seed(DataContext dbContext)
    {
        ArgumentNullException.ThrowIfNull(dbContext, nameof(dbContext));
        dbContext.Database.EnsureCreated();

        var executionStrategy = dbContext.Database.CreateExecutionStrategy();

        executionStrategy.Execute(
            () => {
                using(var transaction = dbContext.Database.BeginTransaction())
                {
                    try
                    {
                        // Seed Users
                        if (!dbContext.Users.Any())
                        {
                            var usersData = File.ReadAllText("./Resources/users.json");
                            var parsedUsers = JsonConvert.DeserializeObject <User[]>(usersData);
                            foreach(var user in parsedUsers)
                            {
                                user.Password = BC.HashPassword(user.Password);
                            }

                            dbContext.Users.AddRange(parsedUsers);
                            dbContext.SaveChanges();
                        }

                        // Seed Events
                        if (!dbContext.Events.Any())
                        {
                            var eventsData = File.ReadAllText("./Resources/events.json");
                            var parsedEvents = JsonConvert.DeserializeObject <Event[]>(eventsData);

                            dbContext.Events.AddRange(parsedEvents);
                            dbContext.SaveChanges();
                        }

                        transaction.Commit();
                    } 
                    catch (Exception ex)
                    {
                        transaction.Rollback();
                    }
                }
            });
    }
}

Depois disso, em nossa pasta Helpers, devemos criar uma extensão de inicializador de banco de dados, que executará o método Seed.

public static class DBInitializerExtension
{
    public static IApplicationBuilder UseSeedDB(this IApplicationBuilder app)
    {
        ArgumentNullException.ThrowIfNull(app, nameof(app));
        using var scope = app.ApplicationServices.CreateScope();

        var services = scope.ServiceProvider;
        var context = services.GetRequiredService < DataContext > ();

        DBSeeder.Seed(context);

        return app;
    }
}

Por fim, precisaremos abrir o arquivo Program.cs e adicionar o aplicativo. UseSeedDB(). Isso garante que, quando nosso aplicativo for iniciado, ele verificará se há algum dado no banco de dados. Se não houver, o método Seed que criamos anteriormente o preencherá automaticamente com os dados que definimos.

Adicionando CORS

Para habilitar o CORS (compartilhamento de recursos entre origens) para nossos pontos de extremidade, precisaremos adicionar um serviço cors no arquivo Program.cs. Nesse caso, permitiremos o acesso de qualquer região, mas você poderá especificar um domínio específico, se preferir.

builder.Services.AddCors(policyBuilder =>
    policyBuilder.AddDefaultPolicy(policy =>
        policy.WithOrigins("*")
        .AllowAnyHeader()
        .AllowAnyHeader())
);

E depois disso, adicione o aplicativo. UseCors(); método.

Isso nos permitirá acessar nossa API a partir de um aplicativo front-end.

Você pode ler mais sobre o Compartilhamento de Recursos entre Origens (CORS) aqui.

Para resumir tudo...

Criar uma API baseada em função com ASP .NET Core é um aspecto crucial quando se trata de criar aplicativos Web seguros e escalonáveis. Usando a autorização baseada em função, você pode controlar o acesso aos seus recursos de API com base nas funções atribuídas aos seus usuários. Isso garante que apenas usuários autorizados possam acessar dados confidenciais ou executar ações críticas, tornando seu aplicativo mais seguro e confiável. Neste tutorial, vimos como criar uma API simples baseada em função de 0 a 1. Você pode visualizar todo o código da demonstração no GitHub.

Se você quiser ver como conectar a API criada aqui e criar um aplicativo front-end para ela com App Builder TM, leia a Parte II desta postagem do blog –Criando um aplicativo no App Builder usando a API baseada em função.

Solicite uma demonstração