• Stars
    star
    125
  • Rank 286,335 (Top 6 %)
  • Language
    C#
  • License
    MIT License
  • Created over 3 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An example web app based on the new feature in .NET 6 | minimal web API in ASP.NET 6

Minimal APIs in ASP.NET 6

Buy Me a Coffee at ko-fi.com

Building a URL Shortener Web App using Minimal APIs in .NET 6

As announced in the .NET 6 Preview 4 blog, .NET 6 will release an improvement to ASP.NET Core: minimal APIs for hosting and routing in web applications. With these streamlined APIs, we can build microservices and small HTTP APIs with much less ceremony.

In this article, we will first briefly describe the minimal APIs feature in .NET 6. To further demonstrate its use case, we then create a URL shortener web app and containerize it using Docker.

Minimal APIs

To try out a minimal API, we can create an empty web app using the following command.

dotnet new web

This command creates a web project with just a single Program.cs file, which has the following content.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);
await using var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapGet("/", (Func<string>)(() => "Hello World!"));

await app.RunAsync();

The Program.cs file is written in the style of top-level program, which gets rid of class Program and the Main() method thus decreases code clutters. In the code above, the first few lines create a web host, then line 13 defines a route in the routing table using the MapGet method. We can continue to build more APIs using MapGet, MapPost, and similar methods.

The API composition is quite different from the traditional API Controller or MVC style. The minimal APIs have really pushed the number of lines of code to an extreme. If our APIs are simply doing some non-essential computation or CRUD operations, then the minimal APIs can reduce a lot of overhead.

If we run the app, we will see the string "Hello World!" on the homepage. That's it! With only a few lines of code, we now have a fully functioning HTTP API.

URL Shortener

For demo purposes, we will create a URL Shortener web app. The app provides a form that allows users to enter a URL to get its shortened version. The app persists the an entry with the original URL and its shortened version. Therefore, the app is able to redirect a shortened URL to its original address. The following screen recording demonstrates the features to be implemented.

url shortener

1. Creating an API to Shorten a URL

In this app, we are going to store the map entries between shortened and original URLs in a local database. A NoSQL database is suitable for this scenario since this app doesn't have complex data relationships. If we don't consider the scalability, then LiteDB is a good database candidate. If you haven't heard about it, LiteDB is a small, fast and lightweight .NET NoSQL embedded database, and we can think LiteDB as a combination of SQLite and MongoDB.

After install the LiteDB NuGet package, we can register the LiteDatabase in the Dependency Injection (DI) container as follows.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ILiteDatabase, LiteDatabase>(_ => new LiteDatabase("short-links.db"));
await using var app = builder.Build();

Then we can create a POST API which takes in a request body with a URL and returns a JSON object contains the shortened URL.

app.MapPost("/url", ShortenerDelegate);

static async Task ShortenerDelegate(HttpContext httpContext)
{
    var request = await httpContext.Request.ReadFromJsonAsync<UrlDto>();

    if (!Uri.TryCreate(request.Url, UriKind.Absolute, out var inputUri))
    {
        httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        await httpContext.Response.WriteAsync("URL is invalid.");
        return;
    }

    var liteDb = httpContext.RequestServices.GetRequiredService<ILiteDatabase>();
    var links = liteDb.GetCollection<ShortUrl>(BsonAutoId.Int32);
    var entry = new ShortUrl(inputUri);
    links.Insert(entry);

    var result = $"{httpContext.Request.Scheme}://{httpContext.Request.Host}/{entry.UrlChunk}";
    await httpContext.Response.WriteAsJsonAsync(new { url = result });
}

public class UrlDto
{
    public string Url { get; set; }
}

public class ShortUrl
{
    public int Id { get; protected set; }
    public string Url { get; protected set; }
    public string UrlChunk => WebEncoders.Base64UrlEncode(BitConverter.GetBytes(Id));

    public ShortUrl(Uri url)
    {
        Url = url.ToString();
    }
}

In the code above, line 1 defines the route /url for the POST API, and hooks it up with a request delegate ShortenerDelegate to handle HTTP requests. Inside the ShortenerDelegate method, we first parse the request body to get the URL and validate its format. Then we resolve the ILiteDatabase service from the DI container, and insert an entry into the database. In the end, the delegate method returns the shortened URL as a JSON object.

We model the short URLs as a class ShortUrl, where the Url property represents the original URL and the Id property is auto generated when inserting into the NoSQL database. The Id property ensures the uniqueness of each URL in the local database. In order to generate a short URL chunk, we use the WebEncoders.Base64UrlEncode() method to convert from an Id, an integer, to the UrlChunk, a string. Note that you should reference a using Microsoft.AspNetCore.WebUtilities; statement to get the method's namespace.

For example, when Id = 1, the UrlChunk is AQAAAA; when Id = 2, the UrlChunk is AgAAAA; and so on. In the next section, we will convert the UrlChunk back to the Id to get its original Url from the local database.

With that, we now have an API endpoint which accepts HTTP POST requests with a JSON body containing a URL string, and returns a JSON object containing a shortened URL string. We can test the API endpoint using Postman.

postman

2. Creating an API to Redirect URLs

Now we are going to support another feature for redirecting short URLs to their original URLs. This API has to cover lots of variations, thus the easiest way to catch all URLs is to use the MapFallback() method. Also note that we should place this method after all other routes so that those deterministic routes could be matched first.

app.MapFallback(RedirectDelegate);

static async Task RedirectDelegate(HttpContext httpContext)
{
    var db = httpContext.RequestServices.GetRequiredService<ILiteDatabase>();
    var collection = db.GetCollection<ShortUrl>();

    var path = httpContext.Request.Path.ToUriComponent().Trim('/');
    var id = BitConverter.ToInt32(WebEncoders.Base64UrlDecode(path));
    var entry = collection.Find(p => p.Id == id).FirstOrDefault();

    httpContext.Response.Redirect(entry?.Url ?? "/");

    await Task.CompletedTask;
}

The fallback route is hooked to a RedirectDelegate. In this delegate method, we first resolve the ILiteDatabase from the DI container and search the short URL from the database. If found, then the API redirects the page to its original URL. Otherwise, the API redirects the page to the app's homepage.

3. Creating an API to Serve a Static HTML Page

For this app, we only need a simple HTML page to allow end users to send HTTP requests using an input and a button. Thus we are going to create an index.html file and write the user interface there. The web page looks like the following screenshot.

Note that the index.html file needs to be included in this project.

Once the index.html file is ready, we can register the route as follows.

app.MapGet("/", ctx =>
                {
                    ctx.Response.ContentType = "text/html";
                    return ctx.Response.SendFileAsync("index.html");
                });

Now we have completed this web application. You can test it out if you have the latest .NET 6 preview version installed. Or we can try out the application using Docker.

4. Containerizing Our App

Docker allows us to containerize our applications thus easy to deploy, maintain, and scale. I have prepared a Dockerfile for this app.

docker run -it mcr.microsoft.com/dotnet/sdk:6.0-alpine sh
/ # dotnet --info
/ # dotnet new web
docker build -t url-shortener-net6 .
docker run -it --rm -p 8080:80 url-shortener-net6
# then visit http://localhost:8080/

License

Feel free to use the code in this repository as it is under MIT license.

Buy Me a Coffee at ko-fi.com

More Repositories

1

JwtAuthDemo

ASP.NET Core + Angular JWT auth demo; integration tests; login, logout, refresh token, impersonation, authentication, authorization; run on Docker Compose.
C#
342
star
2

ServiceWorkerCronJob

Schedule Cron Jobs using HostedService in ASP.NET Core
C#
262
star
3

HeadFirstDesignPattern

Head First Design Pattern: Completely Rewrite in C#
C#
50
star
4

ApiAuthDemo

A simple demo with JWT Auth APIs and Basic Auth APIs, with Swagger support. Swagger Security; Swagger Auth
C#
47
star
5

SftpService

Working with SFTP in .NET Core
C#
45
star
6

NginxLoadBalancer

Host an ASP.NET Core App with Nginx and Docker: SSL and Load Balancing
C#
42
star
7

FileTransferUsingHttpClient

Upload/Download Files Using HttpClient in C#
C#
34
star
8

HerokuContainer

Dockerized ASP.NET Core Web API app in Heroku
C#
31
star
9

AngularFileUpload

Upload files from Angular client to a .NET API endpoint
C#
26
star
10

WindowsServiceDemo

Windows Service in ASP.NET Core
C#
21
star
11

SemaphoreSlimThrottle

Rate Limiting API Endpoints in ASP.NET Core
C#
20
star
12

ApiControllerIntegrationTests

Integration Tests for API Controllers using MSTest
C#
16
star
13

HandwritingRecognition

Handwriting Recognition using ML.NET
C#
15
star
14

PatternMatchingDemos

Using Pattern Matching to Avoid Massive "if" Statements
C#
13
star
15

Client-IP-SafeList

Client IP safelist for ASP.NET Core | .NET 5 | CIDR | Network | allowed list | IP restriction
C#
12
star
16

CustomConfigurationProviderDemo

Create a Custom Configuration Provider in ASP.NET Core
C#
12
star
17

OptionsPattern

Options Pattern in .NET Core
C#
9
star
18

RunExeFromWebApi

Run an External Executable in ASP.NET Core
C#
7
star
19

UnitTestingWithILogger

Unit Testing with .NET Core ILogger<T>
C#
7
star
20

JsonLabs

Working with JSON in .NET Core 3
C#
7
star
21

KestrelHttps

Enforce HTTPS for an ASP.NET Web API hosted in Kestrel
C#
6
star
22

SerilogFilterDemo

Set up Serilog for .net applications
C#
6
star
23

ChainOfResponsibility

Design Pattern - Chain of Responsibility
C#
4
star
24

Immutable-Collection-InMemory-Cache

Working with Cache using Immutable Collections
C#
4
star
25

SslCertificateChecker

Check SSL Certificate Details Using .NET 6+
C#
3
star
26

BuilderPattern-Moq-UnitTests

Builder Pattern vs Moq in Unit Tests
C#
3
star
27

NETCoreGlobalTools

Create a custom .NET Core CLI tool
C#
2
star
28

HeadFirstOOAD

Coding practice when read HeadFirstOOAD book
C#
2
star
29

StringTemplates

How to Create String Templates in CSharp
C#
2
star
30

ConcurrencyControl

Entity Framework Core Concurrency Control Demo
C#
2
star
31

ConfigurationBuilderDemos

ConfigurationBuilder Demos: APS.NET Core configuration
C#
1
star
32

XmlDocumentationDemo

XML Documentation in CSharp
C#
1
star
33

LifetimeEventsDemo

A demo about the ASP.NET Core Lifetime Events
C#
1
star
34

MemoryCacheLabs

Cache in-memory in ASP.NET Core | tests | immutable
C#
1
star
35

ControllerUnitTests

Unit testing Controllers with ClaimsPrincipal | User | Identity | Claims
C#
1
star