Given this scenario, this is a perfect candidate for using one of .NET 6's new feature, Minimal APIs.
What are Minimal APIs?
Minimal APIs allows the ability to add simple API endpoints in an application's configuration file. As a result, this means that we don't have to go down the MVC route to create API endpoints.
The Web API we wish to upgrade
As stated, it's an ASP.NET Core Web API that we wish to upgrade from version 2.1 to 6.
PostId in a one-to-many relationship.
// Post.cs
using System;
namespace RoundTheCode.FrontEndPostApi.Entites
{
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Article { get; set; }
public string Slug { get; set; }
public DateTime Published { get; set; }
}
}
// Comment.cs
using System;
namespace RoundTheCode.FrontEndPostApi.Entites
{
public class Comment
{
public int Id { get; set; }
public int PostId { get; set; }
public string Message { get; set; }
public DateTime Created { get; set; }
}
}
Next, we have a CreateComment class. This represents the properties that will be requested through the API when we go to create a comment against a post. In this instance, it's the post ID and the comment's message.
// CreateComment.cs
namespace RoundTheCode.FrontEndPostApi.Models
{
public class CreateComment
{
public int PostId { get; set; }
public string Message { get; set; }
}
}
Afterwards, we have the controllers set up for our Web API. These consist of a post controller and a comment controller.
// PostController.cs
using Microsoft.AspNetCore.Mvc;
using RoundTheCode.FrontEndPostApi.Entites;
using RoundTheCode.FrontEndPostApi.Extensions;
using RoundTheCode.FrontEndPostApi.Services;
using System.Collections.Generic;
namespace RoundTheCode.FrontEndPostApi.Controllers
{
[ApiController]
[Route("api/post")]
public class PostController : ControllerBase
{
protected IPostService _postService;
public PostController(IPostService postService)
{
_postService = postService.NotNull();
}
[HttpGet]
public virtual IList<Post> ReadAll()
{
return _postService.ReadAll();
}
[HttpGet("{slug}")]
public virtual ActionResult<Post> ReadBySlug(string slug)
{
var post = _postService.ReadBySlug(slug);
if (post == null)
{
return NotFound();
}
return post;
}
}
}
With the comments controller, there is the ability to read all comments by a post, and to create a comment.
// CommentController.cs
using Microsoft.AspNetCore.Mvc;
using RoundTheCode.FrontEndPostApi.Entites;
using RoundTheCode.FrontEndPostApi.Extensions;
using RoundTheCode.FrontEndPostApi.Models;
using RoundTheCode.FrontEndPostApi.Services;
using System.Collections.Generic;
namespace RoundTheCode.FrontEndPostApi.Controllers
{
[ApiController]
[Route("api/comment")]
public class CommentController : ControllerBase
{
protected IPostService _postService;
protected ICommentService _commentService;
public CommentController(IPostService postService, ICommentService commentService)
{
_postService = postService.NotNull();
_commentService = commentService.NotNull();
}
[HttpGet("{postId}")]
public virtual ActionResult<IList<Comment>> ReadAllByPost(int postId)
{
var post = _postService.ReadById(postId);
if (post == null)
{
return NotFound();
}
return Ok(_commentService.ReadAllByPost(postId));
}
[HttpPost]
public virtual ActionResult<Comment> Create(CreateComment createComment)
{
var post = _postService.ReadById(createComment.PostId);
if (post == null)
{
return NotFound();
}
return _commentService.Create(createComment);
}
}
}
Upgrading to .NET 6
The first thing we need to do is to upgrade the project to .NET 6. We can do that by going into the project's .csproj file and changing the <TargetFramework> to net6.
<!-- RoundTheCode.FrontEndPostApi.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
</Project>
Merging the Program and Startup classes
A change to .NET 6's console template means that we don't have to include the namespace, the Program class and the Main method. There's more details about this in our .NET 6 new features article.
Program.cs file.
Startup.cs file and copy it into the Program.cs file.
- Create a new
builderinstance in ourProgramclass by callingWebApplication. CreateBuilder(args); - Move everything from our
ConfigureServicesmethod in ourStartupclass so it's called from ourServicesproperty in ourbuilderinstance. - Build our
builderinstance, and store it in anappinstance. - Move everything from our
Configuremethod in ourStartupclass so it's called from ourappinstance. - Call
app.Run();to run the application.
The code will look something like this:
// Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RoundTheCode.FrontEndPostApi.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
builder.Services.AddSingleton<IPostService, PostService>();
builder.Services.AddSingleton<ICommentService, CommentService>();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.RoutePrefix = string.Empty;
});
app.UseHttpsRedirection();
app.Run();
Moving from MVC to Minimal APIs
Next, we want to move our API endpoints from our controllers into Minimal APIs.
MapGet and MapPost methods in our app instance. The app instance also has MapPut and MapDelete if we wanted to create update and delete endpoints, but in this instance, we are not going to use them.
// Program.cs
...
app.MapGet("api/comment/{postId}", (IPostService postService, ICommentService commentService, int postId) =>
{
var post = postService.ReadById(postId);
if (post == null)
{
return Results.NotFound();
}
return Results.Ok(commentService.ReadAllByPost(postId));
});
app.MapPost("api/comment", (IPostService postService, ICommentService commentService, CreateComment createComment) =>
{
var post = postService.ReadById(createComment.PostId);
if (post == null)
{
return Results.NotFound();
}
return Results.Ok(commentService.Create(createComment));
});
// Posts API
app.MapGet("api/post", (IPostService postService) =>
{
return postService.ReadAll();
});
app.MapGet("api/post/{slug}", (IPostService postService, string slug) =>
{
var post = postService.ReadBySlug(slug);
if (post == null)
{
return Results.NotFound();
}
return Results.Ok(post);
});
app.Run();
Tidying up the Program class
Finally, we need to tidy up the Program class. We can remove the AddMvc method as our endpoints are now using Minimal APIs.
// Program.cs
// This line can be removed.
builder.Services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
However, we will now find that there are no API endpoints in our Swagger documentation when we do this.
AddSwaggerGen method:
// Program.cs
...
builder.Services.AddEndpointsApiExplorer(); // Need to add this in for it to appear in Swagger
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "Post API", Version = "v1" });
});
...
Removing dead code
That's all our changes needed in our Program class. We can now go ahead and delete the following files:
- Delete the
Startupclass. - Delete both the
PostControllerandCommentControllerclass.
That now leaves us with our entities, our models, our services and the Program class. That's it!