Minimal API - .NET & EF Core
Last modified: 12 February 2025The following guide details how to make a minimal API using .NET and Entity Framework Core.
Minimal APIs introduced in .NET 6 offer a simpler way to create APIs with less boilerplate code and without the need for a controller and class based structure.
Minimal APIs prioritize simplicity and rapid development, ideal for smaller, more straightforward applications.
Create a Clean Project
After creating a new API project the entire application exists within the program.cs file.
When starting a new project just remove the existing demo endpoint along with the variable and record, and you should be left with this -
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.Run();
Modal, DAL & Database - Entity Framework
Install Necessary Libraries
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Add a Model
Create a folder called Models and add a new class file, the model should be a representation of the columns you need in your database table -
public class ToDo
{
public int Id { get; set; }
public string Title { get; set; }
public bool IsCompleted { get; set; }
}
Add Connection String
Add the connection string to the appsettings.json file -
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost; Database=DemoDatabase; Trusted_Connection=false; TrustServerCertificate=True; User Id=sa; Password=password1;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Add Data Access Layer
Create a folder called Data, and add a class called ApplicationDbContext
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
}
Add a DbSet property, the name given to this will be the name of the table that is created
public DbSet<ToDo> ToDos { get; set; }
Next add a method called onModelCreating and pass in the objects to be seeded to the database -
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ToDo>().HasData(
new ToDo() { Id = 1, Title = "Do Homework", IsCompleted = true},
new ToDo() { Id = 2, Title = "London", IsCompleted = false}
);
}
Complete DAL -
using Microsoft.EntityFrameworkCore;
using MinimalAPI_test.Models;
namespace MinimalAPI_test.Data;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<AddressBook> Coupons { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AddressBook>().HasData(
new AddressBook { Id = 1, FirstName = "James", Surname = "Bond", Age = 38, Address = "London"},
new AddressBook { Id = 2, FirstName = "Sherlock", Surname = "Holmes", Age = 45, Address = "London"}
);
}
}
Add the DbContext to program.cs -
builder.Services.AddDbContext<ApplicationDbContext>(option =>
option.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
Create the Database
We can now use Entity Framework to create our database and table
Right-click on the projects name, there will be a section titled Entity Framework Core, within this menu select Add Migration.
In the window that opens give an appropriate name for the migration (e.g. AddAddressBookToDb) and press ok.
A New folder titled Migrations will be added to the project containing the migration file.
Right-click on the project again and go to the Entity Framework Core menu and this time select update database.
The database and table will be auto created and the data in your DAL OnModelCreating method will be seeded to the table.
- Note:
If you receive an error regarding 'Only the invariant culture is supported in globalization-invariant mode', you can resolve this by editing the csprog file, by selecting the file, right-clicking and selecting the edit menu, then selecting Edit 'Filename.csprog' (Or on Mac you can just select the project file and press command + down).
Then set the value of the following line to false -<InvariantGlobalization>false</InvariantGlobalization>
You can then connect to the database to view the table. In rider open the right hand database window and select new connection. Select use connection string, then just paste in the value of your connection string and press next.
You should now be able to see your newly created database, table and seeded data.
Create a Repository
- Note:
A repository, also known as a database repository, refers to an abstraction layer between the data in a program, typically stored in a database, and the business logic of the program.
The repository layer handles all the work that involves data manipulation. This includes all CRUD operations (Create, Read, Update, Delete) and other data-related tasks such as querying, sorting, filtering, and transforming of data.
A repository facilitates a separation of concerns wherein the code involving business logic or presentation is not concerned with data access details. This makes the code more maintainable, readable, and testable.
Create a folder called Repository, add an interface named after your table (e.g. IToDoRepository) -
public interface IToDoRepository
{
}
Add tasks that you would like to perform such as get all, get, post etc. -
public interface IToDoRepository
{
Task<IResult> GetAllAsync();
Task CreateAsync(ToDo toDo);
Task UpdateAsync(ToDo toDo);
Task RemoveAsync(ToDo toDo);
Task SaveAsync();
}
Next create a class file for the repository (e.g. AddressBookRepository) and inherit from the interface -
public class ToDoRepository : IToDoRepository
{
}
Create a property of type ApplicationDbContext called _db, then add a constructor passing the database context in -
public class AddressBookRepository : IAddressBookRepository
{
private readonly ApplicationDbContext _db;
public ToDoRepository(ApplicationDbContext db)
{
_db = db;
}
}
Finally, implement all the methods in the interface by highlighting the class name (which should be underlined red to show there are missing members) and selecting implement missing members:
public class ToDoRepository : IToDoRepository
{
private readonly ApplicationDbContext _db;
public ToDoRepository(ApplicationDbContext db)
{
_db = db;
}
public async Task<IResult> GetAllAsync()
{
throw new NotImplementedException();
}
public Task CreateAsync(ToDo toDo)
{
throw new NotImplementedException();
}
public Task UpdateAsync(ToDo toDo)
{
throw new NotImplementedException();
}
public Task RemoveAsync(ToDo toDo)
{
throw new NotImplementedException();
}
public Task SaveAsync()
{
throw new NotImplementedException();
}
}
This will add all of the methods detailed in your interface, with each just throwing an exception stating they are yet to be implemented.
Update the get all method:
public async Task<IResult> GetAllAsync()
{
var todos = await _db.ToDos.ToListAsync();
return Results.Ok(todos);
}
Now back in the program.cs file you need to add the repository to the services -
builder.Services.AddScoped<IToDoRepository, ToDoRepository>();
Add Endpoints
Move Endpoints to a Separate File
To keep the project clean and manageable, and to reduce the amount of code within the program.cs file, it is a good idea to move the endpoints code into their own file.
Add a new folder called Endpoints, then add a new class file with an appropriate name for your endpoints (e.g ToDoEndpoints)
Make the class static, then add a static method with the name Configure + your class name (e.g. ConfigureToDoEndpoints), then pass in WebApplication -
public static class ToDoEndpoints
{
public static void ConfigureToDoEndpoints(this WebApplication app)
{
}
}
Then in the Program.cs we need to invoke the new class by calling it on the app method -
app.ConfigureToDoEndpoints();
This is usually placed just before app.UseHttpsRedirection()
Add an Endpoint
To keep the code as clean as possible we will separate the logic from the endpoint by moving it to a separate method.
In your endpoints class file, add an endpoint to the method we previously created -
public static class ToDoEndpoints
{
public static void ConfigureToDoEndpoints(this WebApplication app)
{
app.MapGet("/api/todo", GetAllToDos);
}
}
In the above example we are creating a get all endpoint by calling MapGet on the app object, and we are passing in the URI for the endpoint, and then passing the name of the method we would like to call at that endpoint.
The method we passed in is where we will hold our logic, but we have not created this yet, so select the method name and select create method, and the method will be created for us -
namespace MinimalAPI_test.Endpoints;
public static class AddressBookEndpoints
{
public static void ConfigureAddressBookEndpoints(this WebApplication app)
{
app.MapGet("/api/addressbook", GetAllAddressBook);
}
private static Task GetAllAddressBook(HttpContext context)
{
throw new NotImplementedException();
}
}
public static class ToDoEndpoints
{
public static void ConfigureToDoEndpoints(this WebApplication app)
{
app.MapGet("/api/todo", GetAllToDos);
}
private static Task<IResult> GetAllToDos(IToDoRepository repo)
{
return await repo.GetAllAsync();
}
}
We should now be able to run the application and successfully call the endpoint.
Add an HTTP Response Code
We can return an appropriate HTTP response code with the result by wrapping the return object in a Results method. For example, if we wrap our result in Results.Ok, a 200 OK status code will be returned along with the results -
private static async Task<IResult> GetAllAddressBook(IAddressBookRepository _repo)
{
return Results.Ok(await _repo.GetAllAsync());
}