I recently wrote a post about frustration I experienced when trying out .NET having worked with Node.js for the past several years. Turns out my Google-fu was off and I should have been searching for "ASP.NET Minimal APIs" which leads to this amazing document : https://docs.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0
All the fine grained control I am used to is right there! No laborious MVC to wade though, no more butting heads with razor pages, just plain GET and POST requests.
I wanted to see how hard it was to duplicate things I normally do with a simple express API:
- Can I use url path parameters in GET requests?
- Can I access the request and response objects?
- Can I process input from a POST request body?
- Can I write middleware for requests?
- Can I do an async web request and return the result within a handler?
- Can I serve static files?
- Can I render basic html templates?
- Can I add a swagger UI?
Yes! The answer to all of the above was yes! I was shocked. My experience of trying to get back into .NET had been like someone who normally goes wherever they want on a bicycle suddenly being restricted to traveling by train. Now I have a bike back!
Here are the details on the items above:
1) Using request path parameters is as simple as adding {placeholders} to the path.
app.MapGet("/hello/{name}", (string name) => $"Hello {name}!");
2 and 3) Request and response objects are available via a HttpContext binding. Parsing of JSON body happens automatically via parameter binding.
app.MapPost("/thing", Thing (HttpContext context, Thing thang) => {
Console.WriteLine(context.Request.Method);
return thang;
});
4) Middleware looks an awful lot like it does in express.
app.Use(async (context, next) =>
{
Console.WriteLine("Halo Fren - I iz Middleware! " + context.Request.Path);
context.Items.Add("user", "Doge");
await next.Invoke();
});
5) Async HTTP requests within a handler are nice and easy and automatically parse JSON.
app.MapGet("/proxy", async Task<Todo> (HttpContext context) => {
Console.WriteLine("Middleware says I am " + (string)context.Items["user"]);
var client = new HttpClient();
var todo = await client.GetFromJsonAsync<Todo>("https://jsonplaceholder.typicode.com/todos/1");
return todo;
}).WithTags("Proxy"); // Sets swagger tag
6) Serving static files is a one-liner.
app.UseStaticFiles();
7) To serve HTML I found the well maintained Handlebars.NET.
app.MapGet("/html", async context =>
{
var source = System.IO.File.ReadAllText(@"./views/demo.html");
var template = Handlebars.Compile(source);
var data = new
{
title = "Demo Html",
body = "This is super simple html!"
};
var result = template(data);
await context.Response.WriteAsync(result);
});
8) Swagger was also super easy to setup. I did have to re-write several of my handlers to add types for their input parameters and output. I even found how to setup groups (see code for #5 above). The full swagger setup is in the demo repo.
Bonus) After getting all this stuff working I noticed that the project template included a dockerfile. The dockerfile built and ran first try which was really fun to see.