.NET Rest API (Fluent Validation and Code Generator Tool)

drsimplegraffiti

Abayomi Ogunnusi

Posted on January 2, 2024

.NET Rest API (Fluent Validation and Code Generator Tool)

Blog API with EF Core in ASP.NET Core

We will create a blog API with EF Core in ASP.NET Core. We will use the code-first approach to create the database schema. We will use the following tools and technologies:

Visual Studio Code
.NET Core SDK
.NET CLI
In-memory database provider
Entity Framework Core

Prerequisites

  • Basic knowledge of C#
  • Basic knowledge of ASP.NET Core
  • Basic knowledge of Entity Framework Core
  • Basic knowledge of Visual Studio Code

Setup

Create a new folder

mkdir Blogging
cd Blogging
Enter fullscreen mode Exit fullscreen mode

Create a new solution

dotnet new sln
Enter fullscreen mode Exit fullscreen mode

The above command will create a new solution file in the Blogging folder.

dotnet CLI

Install the dotnet ef tool globally

dotnet tool install --global dotnet-ef
Enter fullscreen mode Exit fullscreen mode

The above command will install the dotnet ef tool globally. You can check the version of the dotnet ef tool using the following command:

dotnet ef --version
Enter fullscreen mode Exit fullscreen mode

Create a new ASP.NET Core Web API project

dotnet new webapi --use-controllers -o Blogging
Enter fullscreen mode Exit fullscreen mode

We passed the --use-controllers option to the dotnet new webapi command to create the project with controllers. The above command will create a new ASP.NET Core Web API project with controllers in the Blogging folder.

Add the project to the solution

dotnet sln add **/*.csproj
Enter fullscreen mode Exit fullscreen mode

The above command will add all the projects in the Blogging folder to the solution file.

Build the project

cd Blogging
dotnet build
Enter fullscreen mode Exit fullscreen mode

Image description

Install the Entity Framework Core tools

cd Blogging and run the following command:

dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.EntityFrameworkCore.SqlServer #this is for SQL Server, it is required for scaffolding the controller
dotnet add package Microsoft.EntityFrameworkCore.Tools
Enter fullscreen mode Exit fullscreen mode
  • Tools: The tools package contains the EF Core commands for the dotnet CLI. We will use these commands to create the database schema from the model classes.

  • InMemory: The InMemory package contains the in-memory database provider. We will use this provider to create an in-memory database for testing purposes.

  • Design: The design package contains the EF Core design-time components. We will use these components to create the database schema from the model classes.

Create the model classes

mkdir Models
Enter fullscreen mode Exit fullscreen mode

Image description

Use the prop short cut to create properties in the model classes. The prop short cut will create a property with a getter and a setter and a private field.

Image description

Blog.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Blogging.Models
{
    public class Blog
    {
        public int Id { get; set; }
        public string? Url { get; set; } // ? means nullable, i.e. it can be null

        public string? Topic { get; set; }

        public string? Author { get; set; }

        public string? Content { get; set; }

        public DateTime Date { get; set; } = DateTime.UtcNow;
        public DateTime Updated { get; set; } = default!;

    }
}
Enter fullscreen mode Exit fullscreen mode

DbContext class

We need a connector between the model classes and the database. The DbContext class is a bridge between the model classes and the database. The DbContext class is responsible for the following tasks:

  • Establishing a connection to the database
  • Mapping the model classes to the database tables
  • Querying the database
  • Saving the data to the database

Data folder

Create a new file named BloggingContext.cs in the Data folder and add the following code to it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Data
{
    public class BloggingContext : DbContext
    {
        public BloggingContext(DbContextOptions<BloggingContext> options)
            : base(options)
        {

        }

        public DbSet<Blog> Blogs { get; set; }

    }
}
Enter fullscreen mode Exit fullscreen mode

Let's understand the above code:

  • DbContext : The DbContext class is a bridge between the model classes and the database.
  • DbSet : The DbSet class represents a database table. We will use the DbSet class to query and save data to the database.
  • DbContextOptions : The DbContextOptions class is used to configure the DbContext class. We will use the DbContextOptions class to configure the database provider, connection string, etc.
  • base(options) : The base(options) statement is used to pass the options to the base class constructor.

Note: The argument from the base class constructor is passed from the Program.cs file.

Register the DbContext class in the Program.cs file

using Data;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


// Registering the DbContext class in the IoC container
builder.Services.AddDbContext<BloggingContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

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

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
Enter fullscreen mode Exit fullscreen mode

Let's understand the above code:

  • The AddDbContext method is used to register the DbContext class in the IoC container. The AddDbContext method takes a lambda expression as an argument. The lambda expression is used to configure the DbContext class. We have configured the DbContext class to use the in-memory database provider. We have also configured the DbContext class to use the BloggingDatabase as the database name.
  • The AddControllers method is used to register the controllers in the IoC container.
  • The AddEndpointsApiExplorer method is used to register the API explorer in the IoC container.
  • The AddSwaggerGen method is used to register the Swagger generator in the IoC container.
  • The UseSwagger method is used to enable the Swagger middleware.
  • The UseSwaggerUI method is used to enable the Swagger UI middleware.
  • The UseHttpsRedirection method is used to redirect HTTP requests to HTTPS.

Code generation

We will utilize the code generation feature global dotnet-aspnet-codegenerator to generate the controller and the views. The code generation feature is available as a global tool. We need to install the global tool to use the code generation feature. We can install the global tool using the following command:

dotnet tool install --global dotnet-aspnet-codegenerator
Enter fullscreen mode Exit fullscreen mode

The above command will install the global tool.

Scaffold the controller

Then run:

dotnet aspnet-codegenerator controller -name BlogsController -async -api -m Blog -dc BloggingContext -outDir Controllers
Enter fullscreen mode Exit fullscreen mode

Breaking down the above command:

  • dotnet aspnet-codegenerator : The dotnet aspnet-codegenerator command is used to generate the controller and the views.
  • controller : The controller argument is used to generate the controller.
  • -name BlogsController : The -name BlogsController argument is used to specify the name of the controller.
  • -async : The -async argument is used to generate the asynchronous action methods.
  • -api : The -api argument is used to generate the controller with the API template.
  • -m Blog : The -m Blog argument is used to specify the model class.
  • -dc BloggingContext : The -dc BloggingContext argument is used to specify the DbContext class.
  • -outDir Controllers : The -outDir Controllers argument is used to specify the output directory.

Image description

If you run the above command, you will get the following error:

Image description

So we need to install the following packages:

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
Enter fullscreen mode Exit fullscreen mode

Then we need to run the command again:

dotnet aspnet-codegenerator controller -name BlogsController -async -api -m Blog -dc BloggingContext -outDir Controllers
Enter fullscreen mode Exit fullscreen mode

Image description

The above command will generate the BlogsController.cs file in the Controllers folder. Let's understand the code generated by the code generator.

BlogsController.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Blogging.Models;
using Data;

namespace Blogging.Controllers
{
    [Route("api/[controller]")]         // This attribute indicates that the controller responds to requests at the api/blogs URL path
    [ApiController]                                      // This attribute indicates that the controller responds to web API requests
    public class BlogsController : ControllerBase
    {
        // The controller's constructor uses dependency injection to inject the database context (BloggingContext) into the controller. The database context is used in each of the CRUD methods in the controller.
        private readonly BloggingContext _context;

        public BlogsController(BloggingContext context)
        {
            _context = context;
        }

        // GET: api/Blogs
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Blog>>> GetBlogs()
        // The return type of the method (Task<ActionResult<IEnumerable<Blog>>>) is an ActionResult type. The ActionResult type is a return type for actions that return an HTTP response with a specific HTTP status code and optional value.
        {
            return await _context.Blogs.ToListAsync(); // The ToListAsync method converts the Blogs DbSet into a List of Blog entities asynchronously.
        }

        // GET: api/Blogs/5
        [HttpGet("{id}")] // The id parameter is passed to the method. The id parameter is defined as a route parameter (in the [HttpGet("{id}")] attribute) and is specified in the route template string for the GetBlog method.
        public async Task<ActionResult<Blog>> GetBlog(int id)
        {
            var blog = await _context.Blogs.FindAsync(id);

            if (blog == null)
            {
                return NotFound();
            }

            return blog;
        }

        // PUT: api/Blogs/5
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPut("{id}")]
        public async Task<IActionResult> PutBlog(int id, Blog blog)
        {
            if (id != blog.Id)
            {
                return BadRequest();
            }

            _context.Entry(blog).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!BlogExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // POST: api/Blogs
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPost]
        public async Task<ActionResult<Blog>> PostBlog(Blog blog)
        {
            _context.Blogs.Add(blog);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetBlog", new { id = blog.Id }, blog);
        }

        // DELETE: api/Blogs/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteBlog(int id)
        {
            var blog = await _context.Blogs.FindAsync(id);
            if (blog == null)
            {
                return NotFound();
            }

            _context.Blogs.Remove(blog);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private bool BlogExists(int id)
        {
            return _context.Blogs.Any(e => e.Id == id);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Home Controller

Run the app

To run with HTTPS support in development, run the following command:

dotnet dev-certs https --trust

Enter fullscreen mode Exit fullscreen mode

If you get an error, run the following command to clean the certificate:

dotnet dev-certs https --clean
Enter fullscreen mode Exit fullscreen mode

Then run the following command:

dotnet dev-certs https --trust
Enter fullscreen mode Exit fullscreen mode

To run the app, run the following command:

dotnet run --launch-profile https
Enter fullscreen mode Exit fullscreen mode

Now Command + Click on the URL to open the app in the browser.

Image description

Test the API endpoints

Go to https://localhost:/swagger/index.html

Image description

Create a new blog

Request:

Image description

Response:

Image description

Add Fluent Validation

Install the Fluent Validation package:

dotnet add package FluentValidation.AspNetCore
Enter fullscreen mode Exit fullscreen mode

Add the following code to the ConfigureServices method in the Program.cs file:

builder.Services.AddControllers().AddFluentValidation(s =>
{
    s.RegisterValidatorsFromAssemblyContaining<Program>();
});
Enter fullscreen mode Exit fullscreen mode

Validate the DTOs using Fluent Validation

It is a good practice to validate the DTOs before passing them to the controller.
Create a new folder named Dtos.
Create a new file named CreateBlog.cs in the Dtos folder and add the following code to it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Blogging.Dtos
{
    public class CreateBlog
    {
        public string? Topic { get; set; }

        public string? Author { get; set; }

        public string? Content { get; set; }

    }
}
Enter fullscreen mode Exit fullscreen mode

Validate the DTOs using Fluent Validation

Create a new file named CreateBlogValidator.cs in the Dtos folder and add the following code to it:

using FluentValidation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Blogging.Dtos
{
    public class CreateBlogValidator : AbstractValidator<CreateBlog>
    {
        public CreateBlogValidator()
        {
            RuleFor(x => x.Topic).NotEmpty().WithMessage("Topic is required");
            RuleFor(x => x.Author).NotEmpty().WithMessage("Author is required");
            RuleFor(x => x.Content).NotEmpty().WithMessage("Content is required");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Add the following code to the BlogsController.cs file:

using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Blogging.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BlogsController : ControllerBase
    {
        private readonly BloggingContext _context;
        private readonly IValidator<CreateBlog> _validator;

        public BlogsController(BloggingContext context, IValidator<CreateBlog> validator)
        {
            _context = context;
            _validator = validator;
        }

        // POST: api/Blogs
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPost]
        public async Task<ActionResult<Blog>> PostBlog(CreateBlog createBlog)
        {
            ValidationResult result = _validator.Validate(createBlog);

            if (!result.IsValid)
            {
                return BadRequest(result.Errors);
            }

            Blog blog = new Blog
            {
                Topic = createBlog.Topic,
                Author = createBlog.Author,
                Content = createBlog.Content
            };

            _context.Blogs.Add(blog);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetBlog", new { id = blog.Id }, blog);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Create a Custom Api Response to return the validation errors

Create a new file in the models folder named ApiResponse.cs and add the following code to it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using System.Text.Json.Serialization;

namespace Blogging.Models
{
    public class ApiResponse
    {
        public bool Status { get; set; }
        public string Message { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public object? Data { get; set; }
        [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
        public List<string>? Errors { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Update the BlogsController.cs file

Update the BlogsController.cs file as shown below:

using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Blogging.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BlogsController : ControllerBase
    {
        private readonly BloggingContext _context;
        private readonly IValidator<CreateBlog> _validator;

        public BlogsController(BloggingContext context, IValidator<CreateBlog> validator)
        {
            _context = context;
            _validator = validator;
        }

        // POST: api/Blogs
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPost]
        public async Task<ActionResult<Blog>> PostBlog(CreateBlog createBlog)
        {
            ValidationResult result = _validator.Validate(createBlog);

            if (!result.IsValid)
            {
                return BadRequest(new ApiResponse { Status = false, Errors = result.Errors.Select(e => e.ErrorMessage).ToList() });
            }

            Blog blog = new Blog
            {
                Topic = createBlog.Topic,
                Author = createBlog.Author,
                Content = createBlog.Content
            };

            _context.Blogs.Add(blog);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetBlog", new { id = blog.Id }, blog);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, we learned how to create a blog API with EF Core in ASP.NET Core. We learned how to use the code-first approach to create the database schema. We learned how to use the following tools and technologies:

  • Visual Studio Code
  • .NET Core SDK
  • .NET CLI
  • In-memory database provider
  • Entity Framework Core
  • Fluent Validation

Watch the video version here:

Fluent Validation

If you have any questions or feedback, then please drop a comment below. Thank you for reading. Cheers!

💖 💪 🙅 🚩
drsimplegraffiti
Abayomi Ogunnusi

Posted on January 2, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related