Creating a Minimal API Dot Net 8

Aysham AliAysham Ali
6 min read

1 - Creating the project in VSCode

  1. Download these extensions

    1. C# Dev Kit

    2. IntelliCode for C# Dev Kit

  2. Click on “Create .Net Project” or can Ctrl-Shift+p to create a new dot net project

    1. Select ASP.Net Core Empty (Web)

    2. Select Folder

    3. 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:

  1. dotnet dev-certs https —clean

  2. 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

  1. Install NuGet Gallery extension

  2. Search for Microsoft.EntityFrameworkCore.Sqlite and chose the same version as your dotnet

  3. Install this package as well: Microsoft.EntityFrameworkCore.Tools

  4. Install entity framework globally

    1. 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

  1. Adding a migration for the project

    1. dotnet ef migrations add InitialMigration --project MovieAPI/Movie.API.csproj
  2. Updating database

    1. 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();
});
0
Subscribe to my newsletter

Read articles from Aysham Ali directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aysham Ali
Aysham Ali