SHARE:

ASP.NET Core 6: Minimal APIs, two reasons why I can’t do without it so far

Introduction

A few days ago I introduced you to the minimal APIs, in this blog post: https://anthonygiretti.com/2021/08/12/asp-net-core-6-working-with-minimal-apis/
They are an evolution of the Route to Code concept which was already an interesting concept in ASP.NET Core. I described how it works here: https://anthonygiretti.com/2020/06/29/nano-services-with-asp-net-core-or-how-to-build-a-light-api/
Today I will explain to you the two reasons why I can not do without it

Quick and easy exposure of protobuf files via REST

None of my gRPC projects fail to use the exposure of protobufs files with minimal APIs anymore, it’s so easy to use! The following service is used to list protobuf files and their version, download a protobuf file or display the content of a protobuf file in a browser:

namespace CountryService.gRPC.Services;
public class ProtoService
{
private readonly string _baseDirectory;
public ProtoService(IWebHostEnvironment webHost)
{
_baseDirectory = webHost.ContentRootPath;
}
public Dictionary<string, IEnumerable<string>> GetAll()
{
return Directory.GetDirectories($"{_baseDirectory}/protos")
.Select(x => new { version = x, protos = Directory.GetFiles(x).Select(Path.GetFileName) })
.ToDictionary(o => Path.GetRelativePath("protos", o.version), o => o.protos);
}
public string Get(int version, string protoName)
{
var filePath = $"{_baseDirectory}/protos/v{version}/{protoName}";
var exist = File.Exists(filePath);
return exist ? filePath : null;
}
public async Task<string> ViewAsync(int version, string protoName)
{
var filePath = $"{_baseDirectory}/protos/v{version}/{protoName}";
var exist = File.Exists(filePath);
return exist ? await File.ReadAllTextAsync(filePath) : string.Empty;
}
}

Then, by dependency injection I’m able to expose them easily on a minimal endpoint:

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddGrpc(options => {
options.EnableDetailedErrors = true;
options.IgnoreUnknownServices = true;
options.MaxReceiveMessageSize = 6291456; // 6 MB
options.MaxSendMessageSize = 6291456; // 6 MB
options.CompressionProviders = new List<ICompressionProvider>
{
new BrotliCompressionProvider() // br
};
options.ResponseCompressionAlgorithm = "br"; // grpc-accept-encoding
options.ResponseCompressionLevel = CompressionLevel.Optimal; // compression level used if not set on the provider
options.Interceptors.Add<ExceptionInterceptor>(); // Register custom ExceptionInterceptor interceptor
});
builder.Services.AddGrpcReflection();
builder.Services.AddScoped<ICountryRepository, CountryRepository>();
builder.Services.AddScoped<ICountryServices, CountryServices>();
builder.Services.AddSingleton<ProtoService>();
builder.Services.AddDbContext<CountryContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("CountryService")));
var app = builder.Build();
app.MapGrpcReflectionService();
app.MapGrpcService<CountryGrpcService>();
app.MapGet("/protos", (ProtoService protoService) =>
{
return Results.Ok(protoService.GetAll());
});
app.MapGet("/protos/v{version:int}/{protoName}", (ProtoService protoService, int version, string protoName) =>
{
var filePath = protoService.Get(version, protoName);
if (filePath != null)
return Results.File(filePath);
return Results.NotFound();
});
app.MapGet("/protos/v{version:int}/{protoName}/view", async (ProtoService protoService, int version, string protoName) =>
{
var text = await protoService.ViewAsync(version, protoName);
if (!string.IsNullOrEmpty(text))
return Results.Text(text);
return Results.NotFound();
});
// Custom response handling over ASP.NET Core middleware
app.Use(async (context, next) =>
{
if (!context.Request.Path.Value.Contains("protos/v"))
{
context.Response.ContentType = "application/grpc";
context.Response.Headers.Add("grpc-status", ((int)StatusCode.NotFound).ToString());
}
await next();
});
// Run the app
app.Run();

What I love above all is the efficiency of the static Results class. With that class I can expose json data easily with the Ok() method, expose file content with the Text() method or provide file download with the File() method.

Example with File() method, I’m able to import protobuf via the Connected Services mnu in Visual Studio 2022:

Quick Entity Framework Core dbContext testing

Who doesn’t like Entity Framework Core? I adore. and you know I love it too? to be able to quickly test if my dbContext I have configured it correctly, suddenly I want to quickly test if with a DbSet I can make requests to the database, you never know I may have done something wrong? Well the minimal APIs, still through dependency injection, allow me to inject my Dbcontext and test a LINQ request and see if everything works correctly:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<CountryContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("CountryService")));
var app = builder.Build();
app.MapGet("/countries", (CountryContext dbContext) =>
{
return Results.Ok(dbContext.Countries.Select(x => new
{
x.Name,
x.Description,
x.CapitalCity,
x.Anthem,
SpokenLagnuages = x.CountryLanguages.Select(y => y.Language.Name)
}));
});
// Run the app
app.Run();

Example with Postman:

Practical isn’t it ? 😉

Written by

anthonygiretti

Anthony is a specialist in Web technologies (14 years of experience), in particular Microsoft .NET and learns the Cloud Azure platform. He has received twice the Microsoft MVP award and he is also certified Microsoft MCSD and Azure Fundamentals.