Click here to Skip to main content
15,868,292 members
Articles / Web Development / HTML

Building a simple URL shorten service with Redis

Rate me:
Please Sign up or sign in to vote.
4.57/5 (4 votes)
16 Sep 2014CC (BY-ND 3.0)3 min read 17.5K   258   13   2
In the article, I'll show you how to build a simple URL shorten service with Redis and ASP.NET MVC

 

Before reading this article, I suggest you download the source code first.

What's Redis

Similar to memcached, Redis is a in-memory database. Apart from this, Redis also allows you to save your data to a hard drive, which makes Redis not only a cache but also a NoSQL database.

Building the web

First, create an ASP.NET MVC project in Visual Studio with no authentication.

Image 1

Then add a class to store the URL data in Models folder

C#
public class MicroUrlItem
{
    [Display(Name = "Shorten URL")]
    public string ShortenUrl { get; set; }

    [Required]
    [Display(Name = "Origin URL")]
    [Url]
    public string OrignUrl { get; set; }

    [Range(1, double.MaxValue)]
    [Display(Name = "Expire in Days")]
    public int ExpireInDays { get; set; }

    [Display(Name = "Expire Mode")]
    public ExpireMode ExpireMode { get; set; }
}

ExpireMode is a custom enum so we need to declarate it.

C#
public enum ExpireMode
{
    [Display(Name = "By Created")]
    ByCreated = 0,
    [Display(Name = "By Last Accessed")]
    ByLastAccessed = 1,
    [Display(Name = "Never")]
    Never = 2
}

After that, add a Home page

HTML
@model MicroUrl.Models.MicroUrlItem

@{
    ViewBag.Title = "Home Page";
}
<h3>Get you micro URL here</h3>
<div class="row">
    @using (Html.BeginForm("Index", "Home", FormMethod.Post, new { role = "form", @class = "col-lg-6" }))
    {
        <div class="form-group">
            @Html.LabelFor(m => m.OrignUrl)
            @Html.TextBoxFor(m => m.OrignUrl, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.OrignUrl, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.ShortenUrl)
            @Html.TextBoxFor(m => m.ShortenUrl, new { @class = "form-control", placeHolder = "Keep it empty to generate automatically" })
            @Html.ValidationMessageFor(m => m.ShortenUrl, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.ExpireMode)
            @Html.EnumDropDownListFor(m => m.ExpireMode, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.ExpireMode, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.ExpireInDays)
            @Html.TextBoxFor(m => m.ExpireInDays, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.ExpireInDays, "", new { @class = "text-danger" })
        </div>
        <button type="submit" class="btn btn-default">Create</button>
    }
</div>

and it will look like this

Image 2

One interesting thing in this code snippet is the EnumDropDownListFor method in HtmlHeper, which in not available until ASP.NET MVC 5.2.

In previous version of ASP.NET MVC, without this method, we had to create the dropdown list items manually, which was a dirty work.

But now we can use this method to generate items from an enum, and use the Display attribute to set its display name.

Then we need a page to tell the user that the operation was succeed in Success.cshtml, that's pretty simple.

HTML
@model MicroUrl.Models.MicroUrlItem

@{
    ViewBag.Title = "Success";
}

<h2>Success</h2>
<h3>
    @{
        var url = string.Format("http://{0}:{1}/{2}", Request.Url.Host, Request.Url.Port, Model.ShortenUrl);
    }
    Your Micro URL is <a href="@url">@url</a>
</h3>

Saving objects to Redis

With those preparations above, we can now start writing code to store data in Redis.

First a Nuget package called ServiceStack.Redis is needed. We can simply search for redis in Package Manager, or install in Console using this following command:

PM> Install-Package ServiceStack.Redis

BTW, you can install ServiceStack.Redis.Signed if a reference to a signed assembly, which has a strong name, is required.

Then we can create a redis client with a using statement, and get a typed client to deal with MicroUrlItems

C#
using (IRedisClient client = new RedisClient())
{
    var typedClient = client.As<MicroUrlItem>();
    //...
}

Before saving objects in Redis, we need to mark the ShortenUrl property as the primary key with ServiceStack.DataAnnotations.PrimaryKey attribute, so finally the ShortenUrl property in MicroUrlItem should be

C#
public class MicroUrlItem
{
    [ServiceStack.DataAnnotations.PrimaryKey]
    [Display(Name = "Shorten URL")]
    public string ShortenUrl { get; set; }
    
    //other properties
    //...
}

Another thing we need to do is to generate a random url when the ShortenUrl is not specified.

C#
if (string.IsNullOrWhiteSpace(model.ShortenUrl))
{
    string url;
    do
    {
        url = GetRandomUrl(6);
    } while (typedClient.GetById(url) != null);
    model.ShortenUrl = url;
}

And here is how I generate a random url

C#
private static string randomList = "0123456789abcdefghijklmnopqrstuwxyz";

private static string GetRandomUrl(int length)
{
    var sb = new StringBuilder(length);
    var random = new Random();
    for (int i = 0; i < length; i++)
    {
        var index = random.Next(0, randomList.Length);
        sb.Append(randomList[index]);
    }
    return sb.ToString();
}

Now the model is ready to be saved to Redis

C#
typedClient.Store(model);

Finally, we need to tell Redis when the object will expire if the expiration is specified user.

C#
if (model.ExpireMode != ExpireMode.Never)
    typedClient.ExpireIn(model.ShortenUrl, TimeSpan.FromDays(model.ExpireInDays));

Getting objects from Redis

C#
[Route("{shortenUrl}")]
public ActionResult Index(string shortenUrl)
{
    using (IRedisClient client = new RedisClient())
    {
        var typedClient = client.As<MicroUrlItem>();
        var microUrlItem = typedClient.GetById(shortenUrl);
        if (microUrlItem != null)
        {
            //renew the record
            if (microUrlItem.ExpireMode == ExpireMode.ByLastAccessed)
                typedClient.ExpireIn(microUrlItem.ShortenUrl, TimeSpan.FromDays(microUrlItem.ExpireInDays));
            return Redirect(microUrlItem.OrignUrl);
        }
    }
    return HttpNotFound();
}

Now we can create a typed client in the same way above, and return a response with HTTP 302 if a matching record was found. When a shorten url is accessed, wo should renew it when its ExpireMode property is set to ByLastAccessed.

Another important thing is to make the Route attribute work, just simply map the AttributeRoutes to the ASP.NET MVC route system when application starts.

C#
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    //map the AttributeRoutes
    routes.MapMvcAttributeRoutes();

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Run the application

Before running this application, we need to start Redis, to get Redis, you can go to http://redis.io/download to download it.

For Windows, start a command prompt and go to the Redis folder, and run 

redis-server.exe --maxheap 1gb

and we can also launch a command line client to start a monitor

Image 3

Then we can start the website in Visual Studio, and create a shorten url.

Image 4

Here is the custom url

Image 5

And here is the url generated by the service

After we've got the micro url, we can see what happened in redis in the monitor.

Image 6

Thanks for reading!

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-NoDerivatives 3.0 Unported


Written By
Student
United States United States
A high school student. Loves programming and physics.

Comments and Discussions

 
Questionwonderful. Pin
SamLangTen22-Oct-14 5:36
SamLangTen22-Oct-14 5:36 
QuestionNot that good and bad perf implementation (high collision) Pin
Thewak19-Sep-14 23:45
Thewak19-Sep-14 23:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.