.NET 10: Construindo uma API REST com ASP.NET Core

Guia completo para construir uma API REST profissional com .NET 10 e ASP.NET Core. Controllers, Entity Framework Core, validacao integrada e boas praticas explicadas.

Guia para construir uma API REST com .NET 8 e ASP.NET Core

O .NET 10 e a versao atual com suporte de longo prazo (LTS), trazendo validacao nativa para Minimal APIs, OpenAPI 3.1 por padrao e melhorias significativas de desempenho. O ASP.NET Core combina C# com uma arquitetura modular adequada para aplicacoes corporativas. Este guia cobre a criacao de uma API REST pronta para producao, desde a configuracao inicial ate o deploy.

.NET 10 LTS

O .NET 10 tem suporte ate o final de 2028. A validacao integrada para Minimal APIs, suporte a OpenAPI 3.1 e melhorias em Native AOT o tornam a escolha ideal para novos projetos de API.

Configuracao do projeto com .NET 10 CLI

A criacao de um projeto de API com ASP.NET Core utiliza a CLI do .NET para gerar uma estrutura de projeto otimizada. A configuracao dos pacotes NuGet essenciais prepara a base para o desenvolvimento.

bash
# terminal
# Check installed .NET version
dotnet --version
# Expected: 10.0.x

# Create the API project
dotnet new webapi -n ProductApi -o ProductApi
cd ProductApi

# Add essential packages
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Swashbuckle.AspNetCore

Esses comandos criam um projeto de API com dependencias para Entity Framework Core e documentacao Swagger. O FluentValidation nao e mais necessario para validacao basica no .NET 10.

Program.cscsharp
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.Services;

var builder = WebApplication.CreateBuilder(args);

// Configure Entity Framework Core with SQL Server
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Register business services
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<ICategoryService, CategoryService>();

// Configure controllers with built-in validation (.NET 10)
builder.Services.AddControllers();
builder.Services.AddValidation();

// Configure OpenAPI 3.1 (default in .NET 10)
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new() { Title = "Product API", Version = "v1" });
});

var app = builder.Build();

// Middleware pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

O metodo AddValidation() habilita a validacao integrada introduzida no ASP.NET Core 10, eliminando a necessidade de bibliotecas de terceiros como FluentValidation para cenarios comuns.

Modelos de dados e Entity Framework Core 10

Os modelos representam as entidades de negocio da aplicacao. O Entity Framework Core cuida do mapeamento objeto-relacional com configuracao fluente e convencoes inteligentes.

Models/Product.cscsharp
namespace ProductApi.Models;

public class Product
{
    // Primary key with auto-increment
    public int Id { get; set; }

    // Required properties (non-nullable in C# 14)
    public required string Name { get; set; }
    public required string Description { get; set; }

    // Price with decimal precision
    public decimal Price { get; set; }

    // Stock with default value
    public int StockQuantity { get; set; } = 0;

    // Product status
    public bool IsActive { get; set; } = true;

    // Relationship with Category (foreign key)
    public int CategoryId { get; set; }
    public Category? Category { get; set; }

    // Automatic tracking dates
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public DateTime? UpdatedAt { get; set; }
}

A palavra-chave required garante que as propriedades essenciais sejam sempre inicializadas durante a criacao.

Models/Category.cscsharp
namespace ProductApi.Models;

public class Category
{
    public int Id { get; set; }

    public required string Name { get; set; }

    // Slug for friendly URLs
    public required string Slug { get; set; }

    public string? Description { get; set; }

    // Inverse navigation: list of products in this category
    public ICollection<Product> Products { get; set; } = new List<Product>();

    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
Data/AppDbContext.cscsharp
using Microsoft.EntityFrameworkCore;
using ProductApi.Models;

namespace ProductApi.Data;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    // DbSets for each entity
    public DbSet<Product> Products => Set<Product>();
    public DbSet<Category> Categories => Set<Category>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Product entity configuration
        modelBuilder.Entity<Product>(entity =>
        {
            // Index on name for fast search
            entity.HasIndex(p => p.Name);

            // Price precision: 18 digits, 2 decimals
            entity.Property(p => p.Price)
                .HasPrecision(18, 2);

            // Relationship with Category
            entity.HasOne(p => p.Category)
                .WithMany(c => c.Products)
                .HasForeignKey(p => p.CategoryId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Category entity configuration
        modelBuilder.Entity<Category>(entity =>
        {
            // Unique slug
            entity.HasIndex(c => c.Slug).IsUnique();

            // Maximum name length
            entity.Property(c => c.Name).HasMaxLength(100);
        });
    }
}

A configuracao com Fluent API oferece controle preciso sobre o esquema do banco de dados gerado pelas migrations do EF Core.

Entity Framework Core Migrations

As migrations versionam o esquema do banco de dados. Executar dotnet ef migrations add InitialCreate e depois dotnet ef database update aplica as alteracoes.

DTOs e validacao integrada no ASP.NET Core 10

Os DTOs (Data Transfer Objects) separam os modelos de dominio dos dados expostos pela API. O ASP.NET Core 10 introduz validacao integrada usando DataAnnotations, eliminando a necessidade de bibliotecas de terceiros na maioria dos casos.

DTOs/ProductDtos.cscsharp
using System.ComponentModel.DataAnnotations;

namespace ProductApi.DTOs;

// DTO for product creation with DataAnnotations
public record CreateProductDto(
    [Required(ErrorMessage = "Product name is required.")]
    [MaxLength(200, ErrorMessage = "Name cannot exceed 200 characters.")]
    string Name,

    [Required(ErrorMessage = "Description is required.")]
    [MinLength(10, ErrorMessage = "Description must contain at least 10 characters.")]
    string Description,

    [Range(0.01, 999999.99, ErrorMessage = "Price must be between 0.01 and 999,999.99.")]
    decimal Price,

    [Range(0, int.MaxValue, ErrorMessage = "Stock cannot be negative.")]
    int StockQuantity,

    [Required(ErrorMessage = "A valid category is required.")]
    int CategoryId
);

// DTO for product update
public record UpdateProductDto(
    [MaxLength(200)]
    string? Name,
    string? Description,
    [Range(0.01, 999999.99)]
    decimal? Price,
    [Range(0, int.MaxValue)]
    int? StockQuantity,
    bool? IsActive
);

// DTO for response (read)
public record ProductDto(
    int Id,
    string Name,
    string Description,
    decimal Price,
    int StockQuantity,
    bool IsActive,
    string CategoryName,
    DateTime CreatedAt
);

// DTO for list with pagination
public record ProductListDto(
    int Id,
    string Name,
    decimal Price,
    int StockQuantity,
    bool IsActive,
    string CategoryName
);

O uso de records do C# torna os DTOs imutaveis e concisos, com igualdade por valor automatica. A validacao integrada retorna automaticamente erros 400 estruturados quando a validacao falha. Para preparacao de entrevistas sobre padroes do ASP.NET Core, consulte o guia de perguntas de entrevista de ASP.NET Core.

Pronto para mandar bem nas entrevistas de .NET?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Servicos de negocio e camada de abstracao

A camada de servicos encapsula a logica de negocio e as operacoes de banco de dados, facilitando os testes e a manutencao.

Services/IProductService.cscsharp
using ProductApi.DTOs;

namespace ProductApi.Services;

public interface IProductService
{
    // Retrieval with pagination
    Task<(IEnumerable<ProductListDto> Items, int TotalCount)> GetAllAsync(
        int page = 1,
        int pageSize = 10,
        string? search = null,
        int? categoryId = null);

    // Retrieval by ID
    Task<ProductDto?> GetByIdAsync(int id);

    // Creation
    Task<ProductDto> CreateAsync(CreateProductDto dto);

    // Update
    Task<ProductDto?> UpdateAsync(int id, UpdateProductDto dto);

    // Deletion
    Task<bool> DeleteAsync(int id);
}
Services/ProductService.cscsharp
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.DTOs;
using ProductApi.Models;

namespace ProductApi.Services;

public class ProductService : IProductService
{
    private readonly AppDbContext _context;

    public ProductService(AppDbContext context)
    {
        _context = context;
    }

    public async Task<(IEnumerable<ProductListDto> Items, int TotalCount)> GetAllAsync(
        int page = 1,
        int pageSize = 10,
        string? search = null,
        int? categoryId = null)
    {
        // Build base query
        var query = _context.Products
            .Include(p => p.Category)
            .AsQueryable();

        // Filter by text search
        if (!string.IsNullOrWhiteSpace(search))
        {
            query = query.Where(p =>
                p.Name.Contains(search) ||
                p.Description.Contains(search));
        }

        // Filter by category
        if (categoryId.HasValue)
        {
            query = query.Where(p => p.CategoryId == categoryId.Value);
        }

        // Total count before pagination
        var totalCount = await query.CountAsync();

        // Apply pagination
        var items = await query
            .OrderByDescending(p => p.CreatedAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(p => new ProductListDto(
                p.Id,
                p.Name,
                p.Price,
                p.StockQuantity,
                p.IsActive,
                p.Category!.Name))
            .ToListAsync();

        return (items, totalCount);
    }

    public async Task<ProductDto?> GetByIdAsync(int id)
    {
        // Retrieve with category inclusion
        var product = await _context.Products
            .Include(p => p.Category)
            .FirstOrDefaultAsync(p => p.Id == id);

        if (product == null) return null;

        // Map to DTO
        return new ProductDto(
            product.Id,
            product.Name,
            product.Description,
            product.Price,
            product.StockQuantity,
            product.IsActive,
            product.Category?.Name ?? "Uncategorized",
            product.CreatedAt);
    }

    public async Task<ProductDto> CreateAsync(CreateProductDto dto)
    {
        // Create entity
        var product = new Product
        {
            Name = dto.Name,
            Description = dto.Description,
            Price = dto.Price,
            StockQuantity = dto.StockQuantity,
            CategoryId = dto.CategoryId
        };

        // Add and save
        _context.Products.Add(product);
        await _context.SaveChangesAsync();

        // Load category for response
        await _context.Entry(product)
            .Reference(p => p.Category)
            .LoadAsync();

        return new ProductDto(
            product.Id,
            product.Name,
            product.Description,
            product.Price,
            product.StockQuantity,
            product.IsActive,
            product.Category?.Name ?? "Uncategorized",
            product.CreatedAt);
    }

    public async Task<ProductDto?> UpdateAsync(int id, UpdateProductDto dto)
    {
        // Retrieve existing entity
        var product = await _context.Products
            .Include(p => p.Category)
            .FirstOrDefaultAsync(p => p.Id == id);

        if (product == null) return null;

        // Conditional field updates
        if (!string.IsNullOrEmpty(dto.Name))
            product.Name = dto.Name;

        if (!string.IsNullOrEmpty(dto.Description))
            product.Description = dto.Description;

        if (dto.Price.HasValue)
            product.Price = dto.Price.Value;

        if (dto.StockQuantity.HasValue)
            product.StockQuantity = dto.StockQuantity.Value;

        if (dto.IsActive.HasValue)
            product.IsActive = dto.IsActive.Value;

        // Update modification date
        product.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        return new ProductDto(
            product.Id,
            product.Name,
            product.Description,
            product.Price,
            product.StockQuantity,
            product.IsActive,
            product.Category?.Name ?? "Uncategorized",
            product.CreatedAt);
    }

    public async Task<bool> DeleteAsync(int id)
    {
        // Direct deletion without prior loading (EF Core 7+)
        var result = await _context.Products
            .Where(p => p.Id == id)
            .ExecuteDeleteAsync();

        return result > 0;
    }
}

O uso de ExecuteDeleteAsync melhora o desempenho ao evitar o carregamento da entidade antes da exclusao. Para padroes avancados, consulte o guia de Clean Architecture com .NET.

Controllers de API e endpoints REST

Os controllers expoem endpoints REST e orquestram as chamadas aos servicos de negocio com tratamento adequado dos codigos de status HTTP.

Controllers/ProductsController.cscsharp
using Microsoft.AspNetCore.Mvc;
using ProductApi.DTOs;
using ProductApi.Services;

namespace ProductApi.Controllers;

[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    /// <summary>
    /// Retrieves the list of products with pagination and filters.
    /// </summary>
    [HttpGet]
    [ProducesResponseType(typeof(PaginatedResponse<ProductListDto>), StatusCodes.Status200OK)]
    public async Task<IActionResult> GetAll(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 10,
        [FromQuery] string? search = null,
        [FromQuery] int? categoryId = null)
    {
        // Validate pagination parameters
        if (page < 1) page = 1;
        if (pageSize < 1 || pageSize > 100) pageSize = 10;

        var (items, totalCount) = await _productService.GetAllAsync(
            page, pageSize, search, categoryId);

        // Standardized paginated response
        var response = new PaginatedResponse<ProductListDto>
        {
            Items = items,
            Page = page,
            PageSize = pageSize,
            TotalCount = totalCount,
            TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
        };

        return Ok(response);
    }

    /// <summary>
    /// Retrieves a product by its identifier.
    /// </summary>
    [HttpGet("{id:int}")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetById(int id)
    {
        var product = await _productService.GetByIdAsync(id);

        if (product == null)
        {
            return NotFound(new { message = $"Product with ID {id} not found." });
        }

        return Ok(product);
    }

    /// <summary>
    /// Creates a new product.
    /// </summary>
    [HttpPost]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Create([FromBody] CreateProductDto dto)
    {
        // Validation is automatic via AddValidation()
        var product = await _productService.CreateAsync(dto);

        // Returns 201 with the created resource URL
        return CreatedAtAction(
            nameof(GetById),
            new { id = product.Id },
            product);
    }

    /// <summary>
    /// Updates an existing product.
    /// </summary>
    [HttpPut("{id:int}")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Update(int id, [FromBody] UpdateProductDto dto)
    {
        var product = await _productService.UpdateAsync(id, dto);

        if (product == null)
        {
            return NotFound(new { message = $"Product with ID {id} not found." });
        }

        return Ok(product);
    }

    /// <summary>
    /// Deletes a product.
    /// </summary>
    [HttpDelete("{id:int}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> Delete(int id)
    {
        var deleted = await _productService.DeleteAsync(id);

        if (!deleted)
        {
            return NotFound(new { message = $"Product with ID {id} not found." });
        }

        // 204 No Content for successful deletion
        return NoContent();
    }
}

Os atributos ProducesResponseType documentam os possiveis codigos de resposta para a geracao automatica de documentacao Swagger.

DTOs/PaginatedResponse.cscsharp
namespace ProductApi.DTOs;

public class PaginatedResponse<T>
{
    public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
    public int Page { get; set; }
    public int PageSize { get; set; }
    public int TotalCount { get; set; }
    public int TotalPages { get; set; }
    public bool HasPreviousPage => Page > 1;
    public bool HasNextPage => Page < TotalPages;
}
Route Constraints

O uso de constraints como {id:int} evita conflitos de roteamento e retorna automaticamente um 404 se o formato estiver incorreto.

Middleware de tratamento global de erros

Um middleware de tratamento de erros centraliza o processamento de excecoes para oferecer respostas consistentes e seguras.

Middleware/ExceptionMiddleware.cscsharp
using System.Net;
using System.Text.Json;

namespace ProductApi.Middleware;

public class ExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionMiddleware> _logger;
    private readonly IHostEnvironment _env;

    public ExceptionMiddleware(
        RequestDelegate next,
        ILogger<ExceptionMiddleware> logger,
        IHostEnvironment env)
    {
        _next = next;
        _logger = logger;
        _env = env;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            // Continue pipeline
            await _next(context);
        }
        catch (Exception ex)
        {
            // Log the error
            _logger.LogError(ex, "An unhandled exception occurred");

            // Prepare response
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

            // Different response based on environment
            var response = _env.IsDevelopment()
                ? new ErrorResponse(
                    StatusCode: context.Response.StatusCode,
                    Message: ex.Message,
                    Details: ex.StackTrace)
                : new ErrorResponse(
                    StatusCode: context.Response.StatusCode,
                    Message: "An internal error occurred.",
                    Details: null);

            // Serialize with camelCase options
            var options = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };

            var json = JsonSerializer.Serialize(response, options);
            await context.Response.WriteAsync(json);
        }
    }
}

// DTO for error responses
public record ErrorResponse(int StatusCode, string Message, string? Details);

// Extension to register middleware
public static class ExceptionMiddlewareExtensions
{
    public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<ExceptionMiddleware>();
    }
}
Program.cs (adding middleware)csharp
var app = builder.Build();

// Exception middleware must be first
app.UseExceptionMiddleware();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
// ... rest of configuration

Configuracao e variaveis de ambiente

A configuracao externalizada permite adaptar a aplicacao a diferentes ambientes sem alterar o codigo.

appsettings.jsonjson
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=ProductDb;User Id=sa;Password=YourPassword;TrustServerCertificate=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Warning"
    }
  },
  "ApiSettings": {
    "DefaultPageSize": 10,
    "MaxPageSize": 100,
    "ApiVersion": "1.0"
  }
}
Configuration/ApiSettings.cscsharp
namespace ProductApi.Configuration;

public class ApiSettings
{
    public int DefaultPageSize { get; set; } = 10;
    public int MaxPageSize { get; set; } = 100;
    public string ApiVersion { get; set; } = "1.0";
}
Program.cs (injecting configuration)csharp
builder.Services.Configure<ApiSettings>(
    builder.Configuration.GetSection("ApiSettings"));

// Usage in a service
public class ProductService : IProductService
{
    private readonly ApiSettings _settings;

    public ProductService(IOptions<ApiSettings> settings)
    {
        _settings = settings.Value;
    }
}

Testes unitarios com xUnit

Os testes unitarios validam o comportamento dos servicos e controllers de forma isolada.

Tests/ProductServiceTests.cscsharp
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.DTOs;
using ProductApi.Models;
using ProductApi.Services;
using Xunit;

namespace ProductApi.Tests;

public class ProductServiceTests
{
    private AppDbContext CreateInMemoryContext()
    {
        // Configure in-memory database
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        return new AppDbContext(options);
    }

    [Fact]
    public async Task CreateAsync_ValidDto_ReturnsProductDto()
    {
        // Arrange
        using var context = CreateInMemoryContext();

        // Add test category
        var category = new Category { Id = 1, Name = "Electronics", Slug = "electronics" };
        context.Categories.Add(category);
        await context.SaveChangesAsync();

        var service = new ProductService(context);
        var dto = new CreateProductDto(
            Name: "Test Product",
            Description: "Test Description",
            Price: 99.99m,
            StockQuantity: 10,
            CategoryId: 1);

        // Act
        var result = await service.CreateAsync(dto);

        // Assert
        Assert.NotNull(result);
        Assert.Equal("Test Product", result.Name);
        Assert.Equal(99.99m, result.Price);
        Assert.Equal("Electronics", result.CategoryName);
    }

    [Fact]
    public async Task GetByIdAsync_NonExistent_ReturnsNull()
    {
        // Arrange
        using var context = CreateInMemoryContext();
        var service = new ProductService(context);

        // Act
        var result = await service.GetByIdAsync(999);

        // Assert
        Assert.Null(result);
    }

    [Fact]
    public async Task DeleteAsync_ExistingProduct_ReturnsTrue()
    {
        // Arrange
        using var context = CreateInMemoryContext();

        var category = new Category { Id = 1, Name = "Test", Slug = "test" };
        var product = new Product
        {
            Id = 1,
            Name = "To Delete",
            Description = "Will be deleted",
            Price = 10.00m,
            CategoryId = 1
        };

        context.Categories.Add(category);
        context.Products.Add(product);
        await context.SaveChangesAsync();

        var service = new ProductService(context);

        // Act
        var result = await service.DeleteAsync(1);

        // Assert
        Assert.True(result);
        Assert.Null(await context.Products.FindAsync(1));
    }
}

Os testes sao executados com dotnet test a partir da raiz do projeto. Para mais informacoes sobre padroes de desempenho do EF Core, incluindo estrategias de testes, consulte o guia dedicado.

Fontes

Checklist para criar APIs .NET de producao

O .NET 10 com ASP.NET Core oferece um ecossistema completo e de alto desempenho para construir APIs REST profissionais. A combinacao de validacao integrada, Entity Framework Core para acesso a dados e injecao de dependencias nativa permite criar aplicacoes manteniveis e testaveis.

  • Separar DTOs dos modelos de dominio
  • Implementar uma camada de servicos para a logica de negocio
  • Usar validacao integrada com DataAnnotations (.NET 10+)
  • Configurar middleware global de tratamento de erros
  • Externalizar a configuracao com IOptions
  • Escrever testes unitarios para os servicos
  • Documentar a API com OpenAPI 3.1

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

A arquitetura em camadas (Controllers, Services, Repository/DbContext) promove a separacao de responsabilidades e facilita a evolucao da aplicacao. Os recursos do .NET 10 como validacao integrada, OpenAPI 3.1 por padrao e suporte aprimorado a Native AOT modernizam o desenvolvimento de APIs e melhoram o desempenho.

Desafio do dia

Você saberia encontrar o bug em .NET?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 22 de agosto de 2026

Tags

#dotnet
#aspnet core
#csharp
#rest api
#entity framework

Compartilhar

Artigos relacionados