How to create a URL Shortener in dotnet ?
Tags: dotnet, dotnetcore, urlshortener, systemdesign
Published: 2023-02-26 18:09:22


## Target Audience

The focus of this article is to provide insights to those seeking to acquire knowledge on designing a dotnet-based URL shortening service.

## Learning Objectives

After completing this article, you will know how to do the following:

- Desgin an url shortener
- Create an API service for url shortener

## What is URL Shortening ?

### Wikipedia Definition

**URL shortening** is a technique on the [World Wide Web](https://en.wikipedia.org/wiki/World_Wide_Web "World Wide Web") in which a [Uniform Resource Locator](https://en.wikipedia.org/wiki/Uniform_Resource_Locator "Uniform Resource Locator") (URL) may be made substantially shorter and still direct to the required page. This is achieved by using a [redirect](https://en.wikipedia.org/wiki/URL_redirection "URL redirection") which links to the [web page](https://en.wikipedia.org/wiki/Web_page "Web page") that has a long URL.

## What are the benefits of using an url shortener ?

1.  Saves characters: URL shorteners help save characters in tweets, texts, and other social media posts where character count is limited. This can make your message more concise and easier to share.
2.  Track clicks: URL shorteners often provide analytics that allow you to track clicks on your links. This can be useful for measuring the success of your marketing campaigns, and for determining which types of content are most engaging to your audience.
3.  Makes links more manageable: Long and complex URLs can be difficult to remember or type out. URL shorteners can make links more manageable and easier to share.
4.  Mask the original URL: Some URL shorteners allow you to mask the original URL, which can be useful for hiding affiliate links or links to sites that you don't want to reveal.
5.  Customize links: Many URL shorteners allow you to customize the shortened link to include a keyword or brand name. This can help increase brand awareness and make links more memorable.

## Design of the service

Here is the general structure of the url shortener system.


![System Design of Url Shortener](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzq6lk7b0w93t786ir79.png)

This design can be modified for the requirements. For example, if we want to handle high volume, we can use a load balancer to handle that traffic. And if we want to provide high availibility, we can use clustered databases.

## Example implementation in dotnet

Here are the endpoints:

```csharp
app.MapGet("{code}", (string code) => $"You are redirected to. Code: {code}");
app.MapGet("/get/{code}", (string code) => $"Here is the code: {code}");
app.MapGet("/get", () => $"Here is the list of urls. List: {GetAll()}");
app.MapPost("/create", ([FromBody] CreateShortUrlRequest request) =>
{
    var url = request.Url;
    return $"Here is your url: {url}";
});
app.MapDelete("/delete/{code}", (string code) => $"Url in {code} deleted successfully");
```

In this demo, we will use `create` endpoint to create a code to represent urls.

Here is the whole implementation:

```csharp
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors();
builder.Services.AddSingleton(_ => new UrlShortenerService(new Database()));
var app = builder.Build();


app.UseCors(x =>
{
    x.AllowAnyHeader();
    x.AllowAnyOrigin();
    x.AllowAnyMethod();
});

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



// here is the middleware to redirect

app.Use(async (context,next) =>
{
    var currentEndpoint = context.GetEndpoint();
    if (currentEndpoint is {DisplayName: "HTTP: GET {code}"} and RouteEndpoint routeEndpoint && routeEndpoint.RoutePattern.RawText == "{code}" && routeEndpoint.RoutePattern.Parameters.Count == 1)
    {
        var urlShortenerService = context.RequestServices.GetService<UrlShortenerService>();
        var reqUrl = context.Request.Path.Value[1..];
        var url = urlShortenerService.Get(reqUrl);
        if (string.IsNullOrWhiteSpace(url))
            await next();
        context.Response.Redirect(url);
    }

    await next();
});

app.MapGet("{code}", (UrlShortenerService service, string code) => service.RedirectTo(code));
app.MapGet("/get/{code}", (UrlShortenerService service,string code) => service.Get(code));
app.MapGet("/get", (UrlShortenerService service) => $"Here is the list of urls. List: {service.GetAll()}");
app.MapPost("/create", (UrlShortenerService service, [FromBody] CreateShortUrlRequest request) => service.Create(request.Url));
app.MapDelete("/delete/{code}", (UrlShortenerService service,string code) => service.Delete(code));

app.Run();

public class Database : Dictionary<string, string>
{
}

public class UrlShortenerService
{
    private readonly Database _database;

    public UrlShortenerService(Database database)
    {
        _database = database;
    }

    public string RedirectTo(string code)
    {
        if (!_database.ContainsKey(code))
        {
            return "Not Found";
        }
        var url = _database[code];
        return $"You are redirected to: {url}";
    }

    public string GetAll()
    {
        if (_database.Count < 0)
        {
            return string.Empty;
        }

        var list = JsonConvert.SerializeObject(_database.Keys);

        return list;
    }
    public string Get(string code)
    {
        if (_database.Count <= 0 || !_database.ContainsKey(code))
        {
            return string.Empty;
        }

        return _database[code];
    }

    public string Create(string url)
    {
        var rnd = Guid.NewGuid().ToString("n").Substring(0, 8);
        _database.Add(rnd, url);
        return $"Here is your new code for url: {rnd}";
    }

    public string Delete(string code)
    {
        _database.Remove(code);
        return $"Successfully removed. Code: {code}";
    }
}

public class CreateShortUrlRequest
{
    [JsonConstructor]
    public CreateShortUrlRequest(string url)
    {
        Url = url;
    }

    [JsonRequired]
    [JsonProperty("url")]
    public string Url { get; set; }
}
```

In this implementation, we created CRUD endpoints for a URL shortener and used a `Dictionary` to represent a key-value pair database. In the middleware, we check the current request method to ensure it matches the intended method, and if it does, we use the unique `code` to search the database for the corresponding long URL and redirect the user to it.

Overall, this implementation demonstrates how we can use basic CRUD operations and a key-value pair database to create a functional URL shortener. Additionally, it showcases how middleware can be used to handle the redirection of shortened URLs to their corresponding long URLs.

Here is a snapshot from network tab:


![This is how redirection happens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8u0foj9j2od92lpp59l.png)