Thursday, September 10, 2009 9:22 PM
Kazi Manzur Rashid
Shrinkr - Url Shrinking Service Developed with Entity Framework 4.0, Unity, ASP.NET MVC And jQuery (Part 1)
Creating a full blown url shrinking service was pocking around in my mind for quite some time(of course by using Twitter). Since I heard quite a few good things on Entity Framework 4.0, so I decided to start with it. The first thing I usually do when developing an application is creating the domain model. But to create the domain model, we first have to define the basic functionalities:
- The system will only use Open ID for authentication.
- User should be able to shrink url without logging in.
- When shrinking url, user should be able to specify alias, if alias is not specified, the system will auto generate it.
- Shrinked url will also have a associated webpage preview image.
- The system will maintain statistic of shrinked url like number of visit, referrer domain, geographic data etc. (requires login)
- The user should be able to reset shrinked url statistics. (requires login)
- Should have a REST service for creating shrinked url which will work upon the daily limit that was previously set.
- It should have nice web 2.0 style interface and should support adaptive rendering.
For the above functionalities, I come up with the following Domain Entities:
As you can see most of entities are nothing but some getter/setter properties, please don’t think it as a anemic domain model, in fact the url shrinking service does not have the kind of behaviors that you can put into your entities. When creating the entities one important thing I did was making the properties virtual, so that Entity Framework can lazy load the associated objects (although it is not necessary for the intrinsic data types). For example, the following shows the codes of User and Alias:
User
namespace Shrinkr
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
public class User : IEntity
{
private ApiSetting apiSetting;
public User()
{
CreatedAt = SystemTime.Now();
LastActivityAt = SystemTime.Now();
Aliases = new List<Alias>();
}
public virtual long Id
{
get;
set;
}
public virtual string Name
{
get;
set;
}
public virtual string Email
{
get;
set;
}
public virtual bool IsLockedOut
{
get;
set;
}
public virtual DateTime CreatedAt
{
get;
set;
}
public virtual DateTime LastActivityAt
{
get;
set;
}
public Role Role
{
[DebuggerStepThrough]
get
{
return (Role) InternalRole;
}
[DebuggerStepThrough]
set
{
InternalRole = (int) value;
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual int InternalRole
{
get;
set;
}
public virtual IList<Alias> Aliases
{
get;
private set;
}
public virtual ApiSetting ApiSetting
{
[DebuggerStepThrough]
get
{
if (apiSetting == null)
{
apiSetting = new ApiSetting();
}
return apiSetting;
}
[DebuggerStepThrough]
set
{
Check.Argument.IsNotNull(value, "value");
apiSetting = value;
}
}
public virtual bool CanAccessApi
{
get
{
bool canAccess= (ApiSetting != null) &&
(ApiSetting.Allowed.GetValueOrDefault()) &&
(ApiSetting.DailyLimit == ApiSetting.InfiniteLimit || ApiSetting.DailyLimit > 0);
return canAccess;
}
}
public virtual void AllowApiAccess(int dailyLimit)
{
if (dailyLimit != ApiSetting.InfiniteLimit)
{
Check.Argument.IsNotNegative(dailyLimit, "dailyLimit");
}
if (string.IsNullOrEmpty(ApiSetting.Key))
{
ApiSetting.Key = Guid.NewGuid().ToString().ToUpperInvariant();
}
ApiSetting.Allowed = true;
ApiSetting.DailyLimit = dailyLimit;
}
public virtual bool HasExceedsDailyLimit()
{
DateTime lastOneDay = SystemTime.Now().AddDays(-1);
bool exceeded = CanAccessApi &&
((ApiSetting.DailyLimit != ApiSetting.InfiniteLimit) &&
(ApiSetting.DailyLimit <= Aliases.Count(alias => alias.CreatedAt > lastOneDay && alias.CreatedByApi)));
return exceeded;
}
}
}
Alias:
namespace Shrinkr
{
using System;
using System.Collections.Generic;
public class Alias : IEntity
{
public Alias()
{
Visits = new List<Visit>();
CreatedAt = SystemTime.Now();
}
public virtual long Id
{
get;
set;
}
public virtual string Name
{
get;
set;
}
public virtual string IPAddress
{
get;
set;
}
public virtual DateTime CreatedAt
{
get;
set;
}
public virtual IList<Visit> Visits
{
get;
private set;
}
public virtual User User
{
get;
set;
}
public virtual ShortUrl ShortUrl
{
get;
set;
}
}
}
Next, define the Repositories, in the above entities there are two aggregate root User and ShortUrl, so we will create repositories for those two:
Repositories:

The last thing in the domain model is the Services. Please don’t confuse the Service with the Web Service or something else, here Service refers to some domain logic which does not belongs to entities or repositories, usually these services are called from the presentation layer in our case the ASP.NET MVC Controllers. In this application, we do have few things that directly does not belongs to the above entities or repositories, For example, shrinking url, ensuring unique alias etc etc.
Services:
If you are wondering about the purpose of FindByUser and GetByAlias method of the above IShortUrlService as they already exits in IShortUrlRepository, let me tell you that returning Domain Entities directly in presentation layer is not a good practise, instead you should create some Data Transfer Objects AKA DTO for returning those. The above two methods should do those kind of mappings - flattering the object hierarchy, so that we can easily map it in the UI and do serialization when required. In this application we will have the following two dtos:
DTOs:
So far we have discussed about application domain model, In the next post, we will disscuss about the domain model mapping to database with Entity Framework 4.0.
Stay tuned!!!
Filed under: Asp.net, MVC, DDD, TDD, ASPNETMVC, ASP.NET MVC, Common Service Locator, IoC/DI, Unity, Mock, Unit Of Work, Best Practise, jQuery, Entity Framework, Shrinkr