Creating a Minimal API Dot Net 8


1 - Creating the project in VSCode
Download these extensions
C# Dev Kit
IntelliCode for C# Dev Kit
Click on “Create .Net Project” or can Ctrl-Shift+p to create a new dot net project
Select ASP.Net Core Empty (Web)
Select Folder
Give project a name and select create project
To Watch Project, run this in terminal
dotnet watch --verbose --project MovieAPI/MovieAPI.csproj
Changing Http to Https by removing the Http profile
These are two urls, one for https and one for http.
"applicationUrl": "
https://localhost:7000;http://localhost:7001
"
If problem with certificate then can do the following:
dotnet dev-certs https —clean
dotnet dev-certs https — trust
2 - Adding Entities
Create a folder called Entites and add the following classes inside:
Director Class
using System;
namespace Movie.API.Entities;
public class Director
{
public int Id { get; set;}
public required string Name { get; set;}
public ICollection<Movie> Movies { get; set;} = new List<Movie>();
public Director(){
}
public Director(int id, string name){
Id = id;
Name = name;
}
}
Movie Class
using System;
namespace Movie.API.Entities;
public class Movie
{
public int Id { get; set; }
public required string Title { get; set; }
public int Year { get; set; }
public int Rating { get; set; }
public ICollection<Director> Directors{ get; set; } = new List<Director>();
public Movie(){}
public Movie(int id,string title, int year, int rating){
Id = id;
Title = title;
Year = year;
Rating = rating;
}
}
3 - Adding Entity Framework Core and DbContext
Install NuGet Gallery extension
Search for Microsoft.EntityFrameworkCore.Sqlite and chose the same version as your dotnet
Install this package as well: Microsoft.EntityFrameworkCore.Tools
Install entity framework globally
dotnet tool install dotnet-ef —global —version 8.0.3
DbContext
using System;
using Microsoft.EntityFrameworkCore;
using Movie.API.Entities;
namespace Movie.API.DBContexts;
public class MovieContext(DbContextOptions<MovieContext> options) : DbContext(options)
{
public DbSet<MovieInfo> Movies { get; set;}
public DbSet<Director> Directors { get; set;}
override protected void OnModelCreating(ModelBuilder modelBuilder){
base.OnModelCreating(modelBuilder);
}
}
Adding Services in Program.cs file
using Microsoft.EntityFrameworkCore;
using Movie.API.DBContexts;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MovieContext>(options=>{
options.UseSqlite(builder.Configuration.GetConnectionString("MovieConnection"));
});
var app = builder.Build();
app.MapGet("/", () => "Hello World");
app.Run();
Updating appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"MovieConnection" : "Data Source=Movies.db"
},
"AllowedHosts": "*"
}
Adding migrations
Adding a migration for the project
dotnet ef migrations add InitialMigration --project MovieAPI/Movie.API.csproj
Updating database
dotnet ef update database —project MovieAPI/Movie.API.csproj
Check Database using this extension:
Database Client JDBC
Create git ignore file
This is to prevent pushing obj and bin folders to github
dotnet new gitignore
Initialize Repository and create Readme file
## Dotnet Core 8 - Movie API
Using all the concepts of a API development.
4 - Creating Simple Endpoints
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Movie.API.DBContexts;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MovieContext>(options=>{
options.UseSqlite(builder.Configuration.GetConnectionString("MovieConnection"));
});
var app = builder.Build();
app.MapGet("/",()=> "Empty!");
app.MapGet("/movies", (MovieContext context)=> {
return context.Movies;
});
app.MapGet("/movies/{title}",(MovieContext context, string title) => {
return context.Movies.FirstOrDefault(x=>x.Title == title);
});
app.MapGet("/movies/{id:int}",(MovieContext context, int id) => {
return context.Movies.FirstOrDefault(x=>x.Id == id);
});
app.Run();
Can also use parameter binding:
// Parameter binding (different way to get parameters)
app.MapGet("/movie",(MovieContext context, [FromQuery] string title)=>{
return context.Movies.FirstOrDefault(x=>x.Title==title);
});
// can pass parameter using headers
app.MapGet("/movieh",(MovieContext context, [FromHeader] string title)=>{
return context.Movies.FirstOrDefault(x=>x.Title==title);
});
// can also give the header a name
app.MapGet("/moviehn",(MovieContext context, [FromHeader(Name = "X-Custom-Header")] string title)=>{
return context.Movies.FirstOrDefault(x=>x.Title==title);
});
Need to pass header to postman to use it: (do the same with moviehn, with X-Custom-Header)
Making Endpoints Asynchronous
app.MapGet("/movies", async (MovieContext context)=> {
return await context.Movies.ToListAsync();
});
app.MapGet("/movies/{title}", async (MovieContext context, string title) => {
return await context.Movies.FirstOrDefaultAsync(x=>x.Title == title);
});
app.MapGet("/movies/{id:int}",async (MovieContext context, int id) => {
return await context.Movies.FirstOrDefaultAsync(x=>x.Id == id);
});
Returning HTTP Results
app.MapGet("/movies/{title}", async (MovieContext context, string? title) => {
var result = title==null
? await context.Movies.ToListAsync()
: await context.Movies.Where(x=>x.Title.Contains(title)).ToListAsync();
return (result.Count<=0 || result==null)
? Results.NoContent()
: Results.Ok(result);
});
Using TypedResults instead of Http Results
TypedResults helpers return strongly typed objects, which can improve code readability, unit testing, and reduce the chance of runtime errors.
app.MapGet("/movies/{title}", async Task<Results<NoContent, Ok<List<MovieInfo>>>>(MovieContext context, string? title) => {
var result = title==null
? await context.Movies.ToListAsync()
: await context.Movies.Where(x=>x.Title.Contains(title)).ToListAsync();
return (result.Count<=0 || result==null)
? TypedResults.NoContent()
: TypedResults.Ok(result);
});
This is just a slightly better way: (using or instead of ternery)
app.MapGet("/movies/{title}", async Task<Results<NoContent, Ok<List<MovieInfo>>>>(MovieContext context, string? title) => {
var result = await context.Movies.Where(x=>title==null || x.Title.Contains(title)).ToListAsync();
return (result.Count<=0 || result==null)
? TypedResults.NoContent()
: TypedResults.Ok(result);
});
5 - Adding Automapper
Data Transfer Objects (DTOs) are a common design pattern in software development. They serve as a way to transfer data between layers or systems in an application
Creating DTOs
DirectorDTO
using System;
namespace Movie.API.DTO;
public class DirectorDTO
{
public int Id { get; set;}
public required string Name { get; set;}
public int MovieId { get; set;}
}
MovieDTO
using System;
namespace Movie.API.DTO;
public class MovieDTO
{
public int Id { get; set; }
public required string Title { get; set; }
public int Year { get; set; }
public double Rating { get; set; }
}
Creatings Profiles
using System;
using AutoMapper;
using Movie.API.DTO;
using Movie.API.Entities;
namespace Movie.API.Profiles;
public class MovieProfile : Profile
{
public MovieProfile(){
CreateMap<MovieInfo, MovieDTO>().ReverseMap();
CreateMap<Director, DirectorDTO>()
.ForMember(x => x.MovieId, o => o.MapFrom(d => d.Movies.First().Id ));
}
}
Injecting Automapper
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
Implementing DTOS in Program file
using AutoMapper;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Movie.API.DBContexts;
using Movie.API.DTO;
using Movie.API.Entities;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MovieContext>(options=>{
options.UseSqlite(builder.Configuration.GetConnectionString("MovieConnection"));
});
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
var app = builder.Build();
app.MapGet("/",()=> "Empty!");
// Get all movies
app.MapGet("/movies", async (MovieContext context, IMapper mapper)=> {
var results = await context.Movies.ToListAsync();
return mapper.Map<IEnumerable<MovieDTO>>(results);
});
// Get all movies with title
app.MapGet("/movies/{title}", async Task<Results<NoContent, Ok<List<MovieInfo>>>>(MovieContext context, string? title) => {
var result = await context.Movies.Where(x=>title==null || x.Title.Contains(title)).ToListAsync();
return (result.Count<=0 || result==null)
? TypedResults.NoContent()
: TypedResults.Ok(result);
});
// Get all directors for a movie
app.MapGet("/movies/{movieId:int}/directors",async (MovieContext context, int movieId, IMapper mapper) => {
var result = await context.Movies
.Include(movie=>movie.Directors)
.FirstOrDefaultAsync(movie=>movie.Id==movieId);
var directors = result?.Directors;
return mapper.Map<ICollection<DirectorDTO>>(directors);
});
app.Run();
6 - Creating Post Request
Creating a new MovieToCreateDTO
using System;
namespace Movie.API.DTO;
public class MovieToCreateDTO{
public required string Title { get; set; }
public int Year { get; set; }
public double Rating {get; set;}
}
Adding Map to Profiles
CreateMap<MovieInfo, MovieToCreateDTO>().ReverseMap();
Giving a name to Endpoint
// Get movie using ID
app.MapGet("/movies/{id:int}", async (MovieContext context, IMapper mapper, int id)=>{
var result = await context.Movies.FirstOrDefaultAsync(x=>x.Id == id);
return mapper.Map<MovieDTO>(result);
}).WithName("GetMovie");
Creating a Post Request which also returns a route of movie
// Post Request
app.MapPost("/movies",async (
MovieContext context,
IMapper mapper,
LinkGenerator linkGenerator,
HttpContext httpContext,
[FromBody] MovieToCreateDTO movieToCreate)=>{
var newMovie = mapper.Map<MovieInfo>(movieToCreate);
await context.Movies.AddAsync(newMovie);
await context.SaveChangesAsync();
var movieToReturn = mapper.Map<MovieDTO>(newMovie);
return TypedResults.CreatedAtRoute(movieToReturn, "GetMovie", new {id = movieToReturn.Id});
// Alternative Way
/* // link generator
var linkToReturn = linkGenerator.GetUriByName(httpContext, "GetMovie", new { id = movieToReturn.Id});
return TypedResults.Created(linkToReturn, movieToReturn);
*/
});
7 - Creating PUT Request
Create new MovieToUpdatDTO
using System;
namespace Movie.API.DTO;
public class MovieToUpdateDTO {
public int Id { get; set;}
public required string Title { get; set; }
public int Year { get; set; }
public double Rating {get; set;}
}
Create Put Request
app.MapPut("/movies/{id:int}", async Task<Results<NotFound, Ok>> (MovieContext context, IMapper mapper, int id, [FromBody] MovieToUpdateDTO movieToUpdate)=>{
var existingMovie = await context.Movies.FirstOrDefaultAsync(x=>x.Id==id);
if(existingMovie==null){
return TypedResults.NotFound();
}
mapper.Map(movieToUpdate, existingMovie);
await context.SaveChangesAsync();
return TypedResults.Ok();
});
Subscribe to my newsletter
Read articles from Aysham Ali directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
