How to build a URL Shortener with C# .NET and Redis
Time to read:
URLs are used to locate resources on the internet, but URLs can be long making them hard to use. Especially when you have to type the URL manually, even more so when having to do so on a mobile phone. Long URLs are also problematic when you have a limited amount of characters such as within an SMS segment or a Tweet.
A solution to this is to use a URL shortener which will create URLs that are short and sweet. When you open the shortened URL, the URL will forward you to the long destination URL. This makes it easier to manually type the URL and also save precious characters.
In this tutorial, you'll learn how to create your own URL shortener using C#, .NET, and Redis.
Prerequisites
Here’s what you will need to follow along:
- .NET 6 SDK (later versions will work too)
- A code editor or IDE. I recommend JetBrains Rider, Visual Studio, or VS Code with the C# plugin.
- A Redis database
You can find the source code for this tutorial on GitHub. Use it if you run into any issues, or submit an issue, if you run into problems.
Before creating your application, let's take a look at how URL shorteners work and what you'll be building.
URL Shortener Solution
There are two parts to URL shorteners: forwarding the shortened URLs to the destination URL and the management of the shortened URLs. While these two parts could live within a single application, you will build separate applications for URL management and for URL forwarding.
The two applications will work together as shown in the diagram above.
An administrator can use the CLI CRUD (Create Read Update Delete) application (1) to manage the shortened URL data in a Redis Database (2).
Let's say a mobile user received an SMS with a shortened URL. When the user clicks on the shortened URL, the web browser sends an HTTP GET request to that URL which is received by the Forwarder App (1). The forwarder app will look at the path of the request and look up the destination URL in the same Redis Database and return it to the Forwarder App (2). The Forwarder App then sends back an HTTP response with status code 307 Temporary Redirect pointing towards the destination URL (3).
You'll build the Forwarder App using an ASP.NET Core Minimal API, and you'll also build a command-line interface (CLI) application to provide the CRUD functionality. The CLI application will take advantage of the open-source System.CommandLine libraries. While these libraries are still in preview, they work well and provide common functionality needed to build CLI applications. While you'll build the CRUD functionality in a CLI app, you could build the same app as a website or API using ASP.NET Core, or client app using WPF, WinForms, Avalonia, MAUI, etc.
The CLI application will store the collection of shortened URLs in a Redis database. The key of each shortened URL will be the unique path that will be used to browse to the URL, and the value will be the destination URL.
I chose Redis for this application because it works well as a key-value database, it is well-supported, and there's a great .NET library to interact with Redis. However, you could use any data store you'd like in your own implementation.
Lastly, since these two applications will interact with the same data store and perform some of the same functionality, you'll create a class library for shared functionality for the CRUD operations and validation.
Create the Data Layer
First, open a terminal and run the following commands to create a folder, navigate into it, and create a solution file using the .NET CLI:
Then, create a new class library project for the data layer, and add the library to the solution:
Now open the solution with your preferred editor.
Rename the default C# file called Class1.cs to ShortUrl.cs, and add the following C# code to ShortUrl.cs:
This record will hold on to the Destination and the Path. Path is the unique key that will be matched with the path from the incoming HTTP request in the Forwarder App, and Destination will be the URL the Forwarder App will forward the user to.
Next, create the ShortUrlValidator.cs file which will have code to validate the ShortUrl and its properties. Then, add the following C# code to the file:
ShortUrlValidator has methods for validating the ShortUrl record and for its individual properties. For each validation rule that is not met, a string is added to the validationResults list. The validationResults are then added together into a dictionary. These are:
- The
ShortUrl.Pathproperty can not be null or empty, not be longer than 10 characters, and has to match thePathRegexwhich only allows alphanumeric characters, underscores, and dashes. - The
ShortUrl.Destinationproperty can not be null or empty, and has to be a well formed absolute URL, meaning a URL of format<scheme>://<hostname>:<port>and optionally a path, query, and fragment.
These validation rules will be used in both the Forwarder App and the CLI CRUD App.
Now, the most important responsibility of this project is to manage the data in the Redis database. There's a great library for .NET by StackExchange, the StackOverflow company, for interacting with Redis databases called StackExchange.Redis. Add the StackExchange.Redis NuGet package to the data project using the .NET CLI:
Now, create a new file ShortUrlRepository.cs and add the following C# code:
The ShortUrlRepository is responsible for interacting with the Redis database to manage the shortened URLs. ShortUrlRepository accepts an instance of ConnectionMultiplexer via its constructor which is used to get an IDatabase instance via redisConnection.GetDatabase(). ConnectionMultiplexer takes care of connecting to the Redis database, while the IDatabase class provides the Redis commands as .NET methods.
The ShortUrlRepository has the following methods:
Createto create aShortUrlin the Redis database with the key being the path of the shortened URL, and the value being the destination URL.Updateto update an existingShortUrlin the Redis database.Deleteto delete aShortUrlby deleting the key in the Redis database.Getto get theShortUrlvia the path.GetAllto retrieve all the shortened URLs by retrieving all keys from all the connected Redis servers and then retrieve the values from the Redis database.
This provides all the CRUD functionality for shortened URLs.
Now let's use these classes to create the CRUD CLI App.
Build the Command-Line CRUD Application
Create a new console project in your solution folder, and add the project to the solution:
Then, add a reference from the CLI project to the data project:
Now you can use the public classes from the data project in your CLI project.
Next, add the StackExchange.Redis NuGet package to this project as well:
Now, add the System.CommandLine NuGet package:
The System.CommandLine libraries provide common functionality for building CLI applications. You can define your arguments and commands in C# and the library will execute your commands, and generate help text, command completion, and more.
Open the UrlShortener.Cli/Program.cs file, and replace the code with the following C#:
This code defines the options that you can pass into the CLI application:
destinationOptioncan be passed in using-dor--destination-urland is required.pathOptioncan be passed in using-por--pathand is also required.connectionStringOptioncan be passed in using-cor--connection-string. Alternatively, you can set theURL_SHORTENER_CONNECTION_STRINGenvironment variable which is preferred over passing sensitive strings as an argument. If theURL_SHORTENER_CONNECTION_STRINGenvironment is not present, then theconnectionStringOptionis required.
The destinationOption and pathOption validate the argument by passing in the argument value to their respective ShortUrlValidator methods. The validation results are then concatenated and set to result.ErrorMessage which will be displayed as errors to the user.
Now that the options are defined, you can create commands that receive the options.
Add the following code after your existing code:
The rootCommand is the command that is invoked when you invoke the CLI application without passing any subcommands. The create command takes in the destinationOption, pathOption, and connectionStringOption. The lambda passed into the SetHandler method receives the values for the options and will be executed when the command is run from the CLI.
Since the connectionStringOption may not be required, the connectionString parameter may be null. The GetRedisConnection method checks if it is null, and if so, returns the value from the environment variables. If that is also null, it'll throw an exception.
The command handler will create a new ShortUrlRepository passing in the connection string, and use the Create method to create a new shortened URL.
Now, add the following code which will add the update, delete, get, and list commands:
Note how some commands take less options than others.
Lastly, add the following line of code:
This line is responsible for running the commands and passing in the args string array.
Go back to your terminal and configure the URL_SHORTENER_CONNECTION_STRING environment variable:
If you use PowerShell:
If you use CMD:
If you use Unix based shells such as Bash or Zsh:
Now you can run the project and will see helpful information about the available commands:
The output looks like this:
To get help information about specific commands, specify the command and add the --help argument:
The output looks like this:
Next, try the following commands:
Alternatively, you could publish the project and interact directly with the executable:
Great job! You built the CRUD as a CLI application, now let's build the Forwarder Application.
Build the ASP.NET Core Forwarder Application
In your terminal, head back to the solution folder and then create a new ASP.NET Core Minimal API project:
Just like with the CRUD CLI project, add a reference in the Forwarder project to the data project, and add the StackExchange.Redis NuGet package:
Now, open UrlShortener.Forwarder/Program.cs and replace the contents with the following code:
Let's look at lines 6 to 10 first. The program will retrieve the UrlsDb Redis connection string from the configuration, and if null, throw an exception. Then, a connection to the Redis server is made which is added to the Dependency Injection (DI) container as a singleton.
Next, the ShortUrlRepository is added as a transient service to the DI container. Since ShortUrlRepository accepts a ConnectionMultiplexer object via its constructor, the DI container is able to construct the ShortUrlRepository for you passing in the singleton ConnectionMultiplexer.
Next, let's look at lines 14 to 27. A new HTTP GET endpoint is added with the route /{path}. This endpoint will be invoked by any HTTP request with a single level path. Without a path, ASP.NET Core will return an HTTP status code of 404 Not Found, and so will requests with subdirectory paths.
When the endpoint is invoked, the path of the URL is passed into the path parameter of the lambda. The second lambda parameter, an instance of ShortUrlRepository, is injected by the DI container.
The path is then validated using ShortUrlValidator.ValidatePath. If the path is not valid, an HTTP status code of 400 Bad Request is returned.
If it is valid, the shortened URL is retrieved using the ShortUrlRepository.Get method. If no shortened URL is found, shortUrl will be null. If the shortUrl is null, or when it is not null but the ShortUrl.Destination is null or empty, then an HTTP status code 404 Not Found is returned. Otherwise, the endpoint responds with HTTP status code 307 Temporary Redirect, redirecting to the ShortUrl.Destination.
Now that your code is ready, you'll need to configure the ShortenedUrlsDb connection string. Run the following commands to enable user-secrets, and then configure your connection string as a user-secret:
Finally, run the Forwarder application:
The output of this command will show you the URLs of your application. Open one of the URLs and try some of the shortened URLs you created earlier, like /rr, /sb, /tw, and /sg.
Next steps
You've developed a CLI application to manage shortened URLs and a web application that forwards the shortened URLs to the destination URLs. However, to make the URL as short as possible, you need to also buy a domain that is short and host your URL shortener at that domain. For example, Twilio uses twil.io as a short domain, which is much shorter than www.twilio.com. This is also a good example of how the name of your brand can still be represented in your short domain name.
There are some other ways you could improve the solution:
- You could make the
pathoptional when creating and updating a shortened URL, and instead randomly generate the path. - You could store a time to live (TTL) for every shortened URL and remove the shortened URL when the TTL expires.
- You could track how many times a shortened URL is used for analytics purposes. You could store this data in the Redis database, or track it as an event in Segment.
- You could add an API that is consumable from other applications. For example, an SMS application could dynamically generate shortened URLs via the API for the long URLs they are trying to send to users.
- You could create a Graphical User Interface (GUI) to manage the shortened URL data instead of a CLI application.
Want to keep learning? Learn how to respond to SMS and Voice calls using ASP.NET Core Minimal APIs.
Niels Swimberghe is a Belgian American software engineer and technical content creator at Twilio. Get in touch with Niels on Twitter @RealSwimburger and follow Niels’ personal blog on .NET, Azure, and web development at swimburger.net.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.