September 2009 - Posts

Those who have seen my tweet, already know that we have started working on our ASP.NET MVC Grid. In post I will show you the very early version of our Grid, so that you can provide your valuable feedback to guide us in the right direction. So far, we have implemented the paging and multi-column sorting. Let me show you the minimum code to set it up.

First, we will create an action method which creates some random data for view:

public ActionResult Basic()
{
    Random rnd = new Random();

    IList<InMemoryCustomer> model = new List<InMemoryCustomer>();

    for (int i = 1; i <= 100; i++)
    {
        InMemoryCustomer c = new InMemoryCustomer
                                 {
                                     Id = rnd.Next(1, 1000),
                                     Name = string.Format("Dummy Name #{0}", i),
                                     Address = string.Format("Dummy Address #{0}", i),
                                     RegisterAt = DateTime.Now.AddMonths(-rnd.Next(1, 48)).AddDays(-rnd.Next(0, 365)),
                                     Balance = rnd.Next(1000, 10000),
                                     IsActive = ((i % 4) == 0)
                                 };

        model.Add(c);
    }

    return View(model);
}

Next, in the view:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<InMemoryCustomer>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    In Memory
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Basic</h2>
    <% Html.Telerik().Grid<InMemoryCustomer>()
           .Name("MyGrid")
           .Columns(
                        columns =>
                        {
                            columns.Add(c => c.Id);
                            columns.Add(c => c.Name);
                            columns.Add(c => c.Address);
                            columns.Add(c => c.RegisterAt).Format("{0:MM/dd/yyyy}");
                            columns.Add(c => c.IsActive).HeaderText("Active");
                        }
                   )
           .SortMode(GridSortMode.MultiColumn)
           .Pager(pager => pager.PageSize(10).Style(GridPagerStyle.FirstPreviousNextLast))
           .BindTo(Model)
           .Render(); %>
</asp:Content>

When you run the above, it will show you a nice looking grid like the following:

Basic

Check that when defining the columns, we are using strongly typed syntax which means you will get the complete benefit of compile time checking, also note that we have set the SortMode to MultiColumn, other values are SingleColumn and None. When you click any of the column headers or the links in the pager, it will append the order by and page number in the query string like the following:

?MyGrid-orderBy=Name asc%2CAddress desc&MyGrid-page=2 // Order By Name Ascending, Address Descending and Page is 2

?MyGrid-orderBy=Name asc%2CRegisterAt asc //Order By Name Ascending, RegisterAt Ascending and Page is 1 that why it is not added

As you can see, we are also using the Grid Name as prefix for the query string parameters, this is required to identify the parameter when there are more than one grid in a view. You can easily turn it off my using the IncludeNameInParameterAsPrefix to false when you have only one Grid in a view. Lets take a another quick look how to turn off the Name prefix:

<% Html.Telerik().Grid<InMemoryCustomer>()
       .Name("MyGrid")
       .Columns(
                    columns =>
                    {
                        columns.Add(c => c.Id);
                        columns.Add(c => c.Name);
                        columns.Add(c => c.Address);
                        columns.Add(c => c.RegisterAt).Format("{0:MM/dd/yyyy}");
                        columns.Add(c => c.IsActive).HeaderText("Active");
                    }
               )
       .SortMode(GridSortMode.MultiColumn)
       .IncludeNameInParameterAsPrefix(false)
       .Pager(pager => pager.PageSize(10).Style(GridPagerStyle.FirstPreviousNextLast))
       .BindTo(Model)
       .Render(); %>

(Check the line number 14 in the above snippet) Now, when you run the above code and click few of the column headers, it will get a similar picture like the following:

PreSorted

Now, you will see the urls are generated without the grid name prefix:

?orderBy=Name desc%2CRegisterAt asc&page=2 // Order By Name Descending, RegisterAt Ascending and Page 2

?orderBy=Address%20asc // Order by Address Ascending and Page 1

So far, we are using the Query String, What the about the routing? yes you can also use the routing instead of the old and ugly query string, but to use Routing you have define the route first, this is the reason, I have shown the query string examples first. Now, let me show you the routing example:

First we will define a route in global.asax like the following:

routes.MapRoute("PrettyUrl", "{controller}/PrettyUrl/{orderBy}/{page}", new { controller = "Grid", action = "PrettyUrl", orderBy = string.Empty, page = 1 });

Next, we will create an action method very similar to the above query string example, except, it now accepts the orderBy and page as argument:

public ActionResult PrettyUrl(string orderBy, int? page)
{
    Random rnd = new Random();

    IList<InMemoryCustomer> model = new List<InMemoryCustomer>();

    for (int i = 1; i <= 100; i++)
    {
        InMemoryCustomer c = new InMemoryCustomer
                                 {
                                     Id = rnd.Next(1, 1000),
                                     Name = string.Format("Dummy Name #{0}", i),
                                     Address = string.Format("Dummy Address #{0}", i),
                                     RegisterAt =
                                         DateTime.Now.AddMonths(-rnd.Next(1, 48)).AddDays(-rnd.Next(0, 365)),
                                     Balance = rnd.Next(1000, 10000),
                                     IsActive = ((i%4) == 0)
                                 };

        model.Add(c);
    }

    return View(model);
}

And the view is exactly same as above:

<% Html.Telerik().Grid<InMemoryCustomer>()
       .Name("MyGrid")
       .Columns(
                    columns =>
                    {
                        columns.Add(c => c.Id);
                        columns.Add(c => c.Name);
                        columns.Add(c => c.Address);
                        columns.Add(c => c.RegisterAt).Format("{0:MM/dd/yyyy}");
                        columns.Add(c => c.IsActive).HeaderText("Active");
                    }
               )
       .SortMode(GridSortMode.MultiColumn)
       .IncludeNameInParameterAsPrefix(false)
       .Pager(pager => pager.PageSize(10).Style(GridPagerStyle.FirstPreviousNextLast))
       .BindTo(Model)
       .Render(); %>

Now when run it, the urls will appear like the following:

/Grid/PrettyUrl/Name asc,Address asc,RegisterAt asc/2

/Grid/PrettyUrl/Name desc,IsActive asc

IE will replace the spaces with Url encoded %20, If you have used the ADO.NET Data Service you already guess that we are following the same style for formatting order by clause as ADO.NET Data Service. The default value for orderBy and page parameters are same as there names, if your route is declared with some other names or you want to change the values for query string, use the following syntax (Check line 14 and 15):

<% Html.Telerik().Grid<InMemoryCustomer>()
       .Name("MyGrid")
       .Columns(
                    columns =>
                    {
                        columns.Add(c => c.Id);
                        columns.Add(c => c.Name);
                        columns.Add(c => c.Address);
                        columns.Add(c => c.RegisterAt).Format("{0:MM/dd/yyyy}");
                        columns.Add(c => c.IsActive).HeaderText("Active");
                    }
               )
       .SortMode(GridSortMode.MultiColumn)
       .OrderByParameterName("sortBy")
       .CurrentPageParameterName("p")
       .Pager(pager => pager.PageSize(10).Style(GridPagerStyle.FirstPreviousNextLast))
       .BindTo(Model)
       .Render(); %>

Currently, the routing only works for single grid due to the limitation of the ASP.NET Routing, I will post more on this later on.

Before concluding the post, there are one more nice feature that I like to mention, if you are wondering how the above code is doing the auto paging and sorting, let me tell you that we are using the same DataEngine as our Wpf Control suite is using, the nice things about it that it has the complete support to propagate the arbitrary expressions to query providers, which means if you are using LinqToSQL, LinqToEntities, LinqToNHibernate or any database Linq provider then the actual processing will be done by database server rather than in memory operation. But, if want to take charges of this operations, you can certainly do it, just set the RequireProcessing to false.

This is a just the start, there are lot more features we will be adding before our final release, We will be posting the features as we develop to gather feedback from you, so please let us know what you like, what you do not like and how we can improve.

Thanks.

Shout it

In the previous post, we have created our initial repositories, in this post I will show how you can use the compiled query of Entity Framework in our repository. To use the compiled query we will put each query of our repositories into its own class and create some common interfaces that we can use in our repositories.

IQuery

namespace Shrinkr.Infrastructure.EntityFramework
{
    public interface IQuery<TResult>
    {
        TResult Execute(Database database);
    }
}

IQueryFactory

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System.Collections.Generic;

    public interface IQueryFactory
    {
        bool UseCompiled
        {
            get;
        }

        IQuery<User> CreateUserById(long userId);

        IQuery<User> CreateUserByName(string userName);

        IQuery<User> CreateUserByApiKey(string apiKey);

        IQuery<ShortUrl> CreateShortUrlById(long shortUrlId);

        IQuery<ShortUrl> CreateShortUrlByHash(string urlHash);

        IQuery<ShortUrl> CreateShortUrlByAlias(string alias);

        IQuery<int> CreateShortUrlCountByUserId(long userId);

        IQuery<IEnumerable<ShortUrl>> CreateShortUrlsByUserId(long userId, int start, int max);
    }
}

Now we will modify our RepositoryBase, so that we can pass the IQuaryFactory in its constructor.

RepositoryBase

namespace Shrinkr.Infrastructure.EntityFramework
{
    public abstract class RepositoryBase<TEntity> where TEntity : class, IEntity
    {
        protected RepositoryBase(Database database, IQueryFactory queryFactory)
        {
            Check.Argument.IsNotNull(database, "database");
            Check.Argument.IsNotNull(queryFactory, "queryFactory");

            Database = database;
            QueryFactory = queryFactory;
        }

        protected Database Database
        {
            get;
            private set;
        }

        protected IQueryFactory QueryFactory
        {
            get;
            private set;
        }

        public virtual void Add(TEntity entity)
        {
            Check.Argument.IsNotNull(entity, "entity");

            Database.ObjectSet<TEntity>().AddObject(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            Check.Argument.IsNotNull(entity, "entity");

            Database.ObjectSet<TEntity>().DeleteObject(entity);
        }
    }
}

We can now use the query factory in our repository, for example, in UserRepository we will be able to use:

public User GetById(long id)
{
    IQuery<User> query = QueryFactory.CreateUserById(id);

    return query.Execute(Database);
}

Now, lets check how the query is constructed, first the base class which implements the IQuery<T> interface:

QueryBase

namespace Shrinkr.Infrastructure.EntityFramework
{
    public abstract class QueryBase<TResult> : IQuery<TResult>
    {
        protected QueryBase(bool useCompiled)
        {
            UseCompiled = useCompiled;
        }

        protected bool UseCompiled
        {
            get;
            private set;
        }

        public abstract TResult Execute(Database database);
    }
}

as mentioned that each query will have its own class, for example, for the above user by id query, we will have the following:

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System;
    using System.Data.Objects;
    using System.Linq;
    using System.Linq.Expressions;

    public class UserByIdQuery : QueryBase<User>
    {
        private static readonly Expression<Func<Database, long, User>> expression = (Database database, long id) => database.Users.SingleOrDefault(user => user.Id == id);
        private static readonly Func<Database, long, User> plainQuery = expression.Compile();
        private static readonly Func<Database, long, User> compiledQuery = CompiledQuery.Compile(expression);

        private readonly long userId;

        public UserByIdQuery(bool useCompiled, long userId) : base(useCompiled)
        {
            Check.Argument.IsNotNegative(userId, "userId");

            this.userId = userId;
        }

        public override User Execute(Database database)
        {
            Check.Argument.IsNotNull(database, "database");

            return UseCompiled ?
                   compiledQuery(database, userId) :
                   plainQuery(database, userId);
        }
    }
}

and the implementation of QueryFactory

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System.Collections.Generic;

    public class QueryFactory : IQueryFactory
    {
        public QueryFactory(bool useCompiled)
        {
            UseCompiled = useCompiled;
        }

        public bool UseCompiled
        {
            get;
            private set;
        }

        public IQuery<User> CreateUserById(long userId)
        {
            return new UserByIdQuery(UseCompiled, userId);
        }
    }
}

Now, when unit testing the Repositories we will be using the plain queries, for example the UserRepository will be constructed like the following in unit tests:

public UserRepositoryTests()
{
    database = new Mock<Database>(configurationManager.Object, "Dummy");
    var queryFactory = new QueryFactory(false); // plain query

    repository = new UserRepository(database.Object, queryFactory);
}

and that’s it. But for the data access layer, I would highly recommend to  have the integration tests as well. The reasons are:

  1. It will ensure the underlying Linq Providers does support the Linq queries that we have in your repositories, although the Linq queries we have written here are very simple.
  2. By using the SQL Profiler we can ensure the generated SQLs are really optimized.

But to start writing the integration tests, we have one more important thing to do, the UnitOfWork, which persist the changes in our database.

UnitOfWork

namespace Shrinkr.Infrastructure.EntityFramework
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly Database database;

        public UnitOfWork(Database database)
        {
            Check.Argument.IsNotNull(database, "database");

            this.database = database;
        }

        public void Commit()
        {
            database.Commit();
        }
    }
}

Now, lets write our first integration test:

namespace Shrinkr.IntegrationTests
{
    using System;

    using Xunit;
    using Xunit.Extensions;

    using IoC = global::Microsoft.Practices.ServiceLocation.ServiceLocator;

    public class UserRepositoryTests : TestBase
    {
        private const string Name = "http://kazimanzurrashid.myopenid.com";

        private readonly IUnitOfWork unitOfWork;
        private readonly IUserRepository repository;

        public UserRepositoryTests()
        {
            unitOfWork = IoC.Current.GetInstance<IUnitOfWork>();
            repository = IoC.Current.GetInstance<IUserRepository>();
        }

        [Fact, AutoRollback]
        public void Should_be_able_to_add_user()
        {
            var user = CreateUser();

            Assert.NotEqual(0, user.Id);
        }

        [Fact, AutoRollback]
        public void Should_be_able_to_update_user()
        {
            var user = CreateUser();

            user.Email = "kazimanzurrashid@gmail.com";

            unitOfWork.Commit();

            var updatedUser = repository.GetById(user.Id);

            Assert.Equal("kazimanzurrashid@gmail.com", updatedUser.Email);
        }

        [Fact, AutoRollback]
        public void Should_be_able_to_delete_user()
        {
            var userId = CreateUser().Id;
            var user = repository.GetById(userId);

            repository.Delete(user);
            unitOfWork.Commit();

            user = repository.GetById(userId);

            Assert.Null(user);
        }

        [Fact, AutoRollback]
        public void Should_be_able_to_get_user_by_id()
        {
            var userId = CreateUser().Id;
            var user = repository.GetById(userId);

            Assert.NotNull(user);
        }

        [Fact, AutoRollback]
        public void Should_be_able_to_get_user_by_name()
        {
            CreateUser();

            var user = repository.GetByName(Name);

            Assert.NotNull(user);
        }

        [Fact, AutoRollback]
        public void Should_be_able_to_get_user_by_api_key()
        {
            var apiKey = CreateUser().ApiSetting.Key;
            var user = repository.GetByApiKey(apiKey);

            Assert.NotNull(user);
        }

        private User CreateUser()
        {
            var user = new User { Name = Name };

            user.ApiSetting.Allowed = true;
            user.ApiSetting.DailyLimit = 1000;
            user.ApiSetting.Key = Guid.NewGuid().ToString().ToUpperInvariant();

            repository.Add(user);
            unitOfWork.Commit();

            return user;
        }
    }
}

When we run the above test, it will generate the following SQL statements, which I think is pretty much optimized:

GetById

exec sp_executesql N'SELECT 
[Limit1].[Id] AS [Id], 
[Limit1].[Name] AS [Name], 
[Limit1].[Email] AS [Email], 
[Limit1].[IsLockedOut] AS [IsLockedOut], 
[Limit1].[CreatedAt] AS [CreatedAt], 
[Limit1].[Role] AS [Role], 
[Limit1].[LastActivityAt] AS [LastActivityAt], 
[Limit1].[C1] AS [C1], 
[Limit1].[ApiKey] AS [ApiKey], 
[Limit1].[ApiAllowed] AS [ApiAllowed], 
[Limit1].[DailyLimit] AS [DailyLimit]
FROM ( SELECT TOP (2) 
	[Extent1].[Id] AS [Id], 
	[Extent1].[Name] AS [Name], 
	[Extent1].[Email] AS [Email], 
	[Extent1].[IsLockedOut] AS [IsLockedOut], 
	[Extent1].[CreatedAt] AS [CreatedAt], 
	[Extent1].[Role] AS [Role], 
	[Extent1].[ApiKey] AS [ApiKey], 
	[Extent1].[ApiAllowed] AS [ApiAllowed], 
	[Extent1].[DailyLimit] AS [DailyLimit], 
	[Extent1].[LastActivityAt] AS [LastActivityAt], 
	1 AS [C1]
	FROM [dbo].[User] AS [Extent1]
	WHERE [Extent1].[Id] = @p__linq__0
)  AS [Limit1]',N'@p__linq__0 bigint',@p__linq__0=125

GetByName

exec sp_executesql N'SELECT 
[Limit1].[Id] AS [Id], 
[Limit1].[Name] AS [Name], 
[Limit1].[Email] AS [Email], 
[Limit1].[IsLockedOut] AS [IsLockedOut], 
[Limit1].[CreatedAt] AS [CreatedAt], 
[Limit1].[Role] AS [Role], 
[Limit1].[LastActivityAt] AS [LastActivityAt], 
[Limit1].[C1] AS [C1], 
[Limit1].[ApiKey] AS [ApiKey], 
[Limit1].[ApiAllowed] AS [ApiAllowed], 
[Limit1].[DailyLimit] AS [DailyLimit]
FROM ( SELECT TOP (2) 
	[Extent1].[Id] AS [Id], 
	[Extent1].[Name] AS [Name], 
	[Extent1].[Email] AS [Email], 
	[Extent1].[IsLockedOut] AS [IsLockedOut], 
	[Extent1].[CreatedAt] AS [CreatedAt], 
	[Extent1].[Role] AS [Role], 
	[Extent1].[ApiKey] AS [ApiKey], 
	[Extent1].[ApiAllowed] AS [ApiAllowed], 
	[Extent1].[DailyLimit] AS [DailyLimit], 
	[Extent1].[LastActivityAt] AS [LastActivityAt], 
	1 AS [C1]
	FROM [dbo].[User] AS [Extent1]
	WHERE [Extent1].[Name] = @p__linq__0
)  AS [Limit1]',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'http://kazimanzurrashid.myopenid.com/'

GetByApiKey

exec sp_executesql N'SELECT 
[Limit1].[Id] AS [Id], 
[Limit1].[Name] AS [Name], 
[Limit1].[Email] AS [Email], 
[Limit1].[IsLockedOut] AS [IsLockedOut], 
[Limit1].[CreatedAt] AS [CreatedAt], 
[Limit1].[Role] AS [Role], 
[Limit1].[LastActivityAt] AS [LastActivityAt], 
[Limit1].[C1] AS [C1], 
[Limit1].[ApiKey] AS [ApiKey], 
[Limit1].[ApiAllowed] AS [ApiAllowed], 
[Limit1].[DailyLimit] AS [DailyLimit]
FROM ( SELECT TOP (2) 
	[Extent1].[Id] AS [Id], 
	[Extent1].[Name] AS [Name], 
	[Extent1].[Email] AS [Email], 
	[Extent1].[IsLockedOut] AS [IsLockedOut], 
	[Extent1].[CreatedAt] AS [CreatedAt], 
	[Extent1].[Role] AS [Role], 
	[Extent1].[ApiKey] AS [ApiKey], 
	[Extent1].[ApiAllowed] AS [ApiAllowed], 
	[Extent1].[DailyLimit] AS [DailyLimit], 
	[Extent1].[LastActivityAt] AS [LastActivityAt], 
	1 AS [C1]
	FROM [dbo].[User] AS [Extent1]
	WHERE [Extent1].[ApiKey] = @p__linq__0
)  AS [Limit1]',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'9C1A8F98-9CC6-4967-8B48-269CA92833E5'

Insert

exec sp_executesql N'insert [dbo].[User]([Name], [Email], [IsLockedOut], [CreatedAt], [Role], [ApiKey], [ApiAllowed], [DailyLimit], [LastActivityAt])
values (@0, null, @1, @2, @3, @4, @5, @6, @7)
select [Id]
from [dbo].[User]
where @@ROWCOUNT > 0 and [Id] = scope_identity()',N'@0 nvarchar(256),@1 bit,@2 datetime,@3 int,@4 nchar(36),@5 bit,@6 int,@7 datetime',@0=N'http://kazimanzurrashid.myopenid.com',@1=0,@2='2009-08-02 22:04:17:067',@3=0,@4=N'9C1A8F98-9CC6-4967-8B48-269CA92833E5',@5=1,@6=1000,@7='2009-08-02 22:04:17:067'

Update

exec sp_executesql N'update [dbo].[User]
set [Email] = @0
where ([Id] = @1)
',N'@0 nvarchar(256),@1 bigint',@0=N'kazimanzurrashid@gmail.com',@1=142

Delete

exec sp_executesql N'delete [dbo].[User]
where ([Id] = @0)',N'@0 bigint',@0=125

You will find other tests in the integration test project.

That is it for this post, in the next post we will discuss on other infrastructural item such shrinking logic, http content,  IoC etc etc.

Stay tuned!!!

Shout it

In the previous post we have created our initial domain model, in this post I will show you how the domain model  is mapped to database with Entity Framework 4.0. But before that I would like to discuss how I usually structure the Visual Studio Projects. Most often I prefer to have a one class library and one web project where each has its own unit test project and only one integration test project, for example:

  • Shrinkr.Core
  • Shrinkr.Core.UnitTest
  • Shrinkr.Web
  • Shrinkr.Web.UnitTest
  • Shrinkr.IntegrationTest

The Core Project is then further divided into following folders:

  • Shrink.Core
    • Common (Utility, Invariant etc etc)
    • EntityObjects (Contains both domain objects and DTOs)
    • Extensions (Extension methods)
    • Repositories (Contains both interface and implementation)
    • Services (Contains both interface and implementation)
    • Infrastructure
      • Caching
      • Database
      • Email
      • FileSystem
      • Http
      • IoC
      • Logging
      • etc etc etc.

But since it is an open source project, I should give privilege to community to replace any part of if with the their preferred technology. for example, replacing Entity Framework with NHibernate or may be Azure Storage, Unity with StructureMap or NInject etc etc. So I decided to structure it based upon the component dependency.

SoutionExplorer

As you can understand that the Shrinkr.Infrastructure.Microsoft.Practices will contain the Unity and other EntLib related codes and Shrinkr.Infrastructure.EntityFramework for the concrete repositories and other data access codes. Although Entity Framework Team has released few more add-ons (known as Features CTP1), but for the time being we will not use those. The first thing we will do is create a new class which inherits from the ObjectContext, lets name it as Database.

Database

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System.Data.Objects;
    using System.Diagnostics;

    public class Database : ObjectContext
    {
        private IObjectSet<User> users;
        private IObjectSet<ShortUrl> shortUrls;
        private IObjectSet<Alias> aliases;
        private IObjectSet<Visit> visits;

        public Database(IConfigurationManager configurationManager, string connectionStringName) : base(GetConnectionString(configurationManager, connectionStringName), "ShrinkrEntities")
        {
            ContextOptions.DeferredLoadingEnabled = true;
        }

        public IObjectSet<User> Users
        {
            [DebuggerStepThrough]
            get
            {
                if (users == null)
                {
                    users = ObjectSet<User>();
                }

                return users;
            }
        }

        public IObjectSet<ShortUrl> ShortUrls
        {
            [DebuggerStepThrough]
            get
            {
                if (shortUrls == null)
                {
                    shortUrls = ObjectSet<ShortUrl>();
                }

                return shortUrls;
            }
        }

        public IObjectSet<Alias> Aliases
        {
            [DebuggerStepThrough]
            get
            {
                if (aliases == null)
                {
                    aliases = ObjectSet<Alias>();
                }

                return aliases;
            }
        }

        public IObjectSet<Visit> Visits
        {
            [DebuggerStepThrough]
            get
            {
                if (visits == null)
                {
                    visits = ObjectSet<Visit>();
                }

                return visits;
            }
        }

        public virtual IObjectSet<TEntity> ObjectSet<TEntity>() where TEntity : class, IEntity
        {
            return CreateObjectSet<TEntity>();
        }

        public virtual void Commit()
        {
            SaveChanges();
        }

        private static string GetConnectionString(IConfigurationManager configurationManager, string connectionStringName)
        {
            Check.Argument.IsNotNull(configurationManager, "configurationManager");
            Check.Argument.IsNotNullOrEmpty(connectionStringName, "connectionStringName");

            string connectionString = configurationManager.ConnectionString(connectionStringName);

            return connectionString;
        }
    }
}

Nothing complex, we are just exposing our Domain Entities as properties like Users, ShortUrls, Aliases etc. One important thing you should check in the above code is that instead of using the CreateObjectSet<T>() in the properties, I have created a virtual method ObjectSet<T>() which in turns calls the CreateObjectSet<T>. The reasons behind creating this new virtual method are:

  1. CreateObjectSet<T> does not return IObjectSet<T>, instead it returns the concrete ObjectSet<T>.
  2. CreateObjectSet<T> is not a virtual method, which means we cannot mock it the unit tests (Although it is debatable whether to write unit tests over any Linq provider, but that is an another story).

I am not sure why the Entity Framework team decided to return the concrete class instead of the interface also not making the method virtual, if they did, we do not have to write this workarounds and I am pretty sure many people will call it a design smell and finds it frustrating. Next, we will create the base repository which the UserRepository and ShortUrlRepository inherits. Although it is a common practise specially in the NHibernate world to have only one  Repository<T> instead of individual repository for each aggregate root which I will discuss in my next post.

RepositoryBase

namespace Shrinkr.Infrastructure.EntityFramework
{
    public abstract class RepositoryBase<TEntity> where TEntity : class, IEntity
    {
        protected RepositoryBase(Database database)
        {
            Check.Argument.IsNotNull(database, "database");

            Database = database;
        }

        protected Database Database
        {
            get;
            private set;
        }

        public virtual void Add(TEntity entity)
        {
            Check.Argument.IsNotNull(entity, "entity");

            Database.ObjectSet<TEntity>().AddObject(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            Check.Argument.IsNotNull(entity, "entity");

            Database.ObjectSet<TEntity>().DeleteObject(entity);
        }
    }
}

Check that we have marked both Add and Delete method as virtual so that the concrete repository can override if it has some extra logic. Now, lets create the Unit Test for it.

RepositoryBaseTests

namespace Shrinkr.Infrastructure.EntityFramework.UnitTests
{
    using System.Collections.Generic;

    using Moq;
    using Xunit;

    public class RepositoryBaseTests
    {
        private readonly Mock<FakeObjectSet<Dummy>> objectSet;
        private readonly Mock<Database> database;
        private readonly DummyRepository repository;

        public RepositoryBaseTests()
        {
            var objects = new List<Dummy> {
                                            new Dummy { Id = 1},
                                            new Dummy { Id = 2},
                                            new Dummy { Id = 3}
                                          };

            objectSet = new Mock<FakeObjectSet<Dummy>>(objects);

            var configurationManager = new Mock<IConfigurationManager>();
            configurationManager.Setup(mgr => mgr.ConnectionString(It.IsAny<string>())).Returns("Dummy Connection String");

            database = new Mock<Database>(configurationManager.Object, "Dummy");
            database.Setup(db => db.ObjectSet<Dummy>()).Returns(objectSet.Object);

            repository = new DummyRepository(database.Object);
        }

        [Fact]
        public void Should_be_able_to_add()
        {
            objectSet.Setup(set => set.AddObject(It.IsAny<Dummy>())).Verifiable();

            repository.Add(new Dummy());

            objectSet.Verify();
        }

        [Fact]
        public void Should_be_able_to_delete()
        {
            objectSet.Setup(set => set.DeleteObject(It.IsAny<Dummy>())).Verifiable();

            repository.Delete(new Dummy());

            objectSet.Verify();
        }
   } 

    public class Dummy : IEntity
    {
        public long Id
        {
            get;
            set;
        }
    }

    public class DummyRepository : RepositoryBase<Dummy>
    {
        public DummyRepository(Database database) : base(database)
        {
        }
    }
}

To test the RepositoryBase we have to create few fake classes and in the unit test we are using those. The reason is, if we create mock of RepositoryBase which methods are virtual the mock framework(Moq) will replace those, so the codes of Add and Delete will not be executed. By using the DummyRepository we are making sure the RepositoryBase methods are called. Another important thing you might have noticed that when setting up expectations on the database.ObjectSet (line 28) method we are using another new class FakeObjectSet. This is the another frustrating part of Entity Framework, you cannot pass/set collection of objects in the ObjectSet<T>. This is what the FakeObjectSet does, allowing us to pass the collection of objects so that our unit test can run properly.

FakeObjectSet

namespace Shrinkr.Infrastructure.EntityFramework.UnitTests
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data.Objects;
    using System.Linq;
    using System.Linq.Expressions;

    public abstract class FakeObjectSet<T> : IObjectSet<T> where T : class
    {
        private readonly IEnumerable<T> objects;

        protected FakeObjectSet(IEnumerable<T> objects)
        {
            this.objects = objects;
        }

        public Expression Expression
        {
            get
            {
                return objects.AsQueryable().Expression;
            }
        }

        public IQueryProvider Provider
        {
            get
            {
                return objects.AsQueryable().Provider;
            }
        }

        public Type ElementType
        {
            get
            {
                return typeof(T);
            }
        }

        public abstract void AddObject(T entity);

        public abstract void Attach(T entity);

        public abstract void DeleteObject(T entity);

        public IEnumerator<T> GetEnumerator()
        {
            return objects.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
}

Now, creating the concrete repositories are plain and simple.

UserRepository

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System.Linq;

    public class UserRepository : RepositoryBase<User>, IUserRepository
    {
        public UserRepository(Database database) : base(database)
        {
        }

        public User GetById(long id)
        {
            Check.Argument.IsNotZeroOrNegative(id, "id");

            return Database.Users.SingleOrDefault(user => user.Id == id);
        }

        public User GetByName(string name)
        {
            Check.Argument.IsNotNullOrEmpty(name, "name");

            return Database.Users.SingleOrDefault(user => user.Name == name);
        }

        public User GetByApiKey(string apiKey)
        {
            Check.Argument.IsNotNullOrEmpty(apiKey, "apiKey");

            return Database.Users.SingleOrDefault(user => user.ApiSetting.Key == apiKey);
        }
    }
}

UserRepositoryTests

namespace Shrinkr.Infrastructure.EntityFramework.UnitTests
{
    using System;
    using System.Collections.Generic;

    using Moq;
    using Xunit;

    public class UserRepositoryTests
    {
        private const long UserId = 1;
        private const string UserName = "http://kazimanzurrashid.myopenid.com";
        private readonly static string ApiKey = Guid.NewGuid().ToString().ToUpperInvariant();

        private readonly Mock<Database> database;
        private readonly UserRepository repository;

        public UserRepositoryTests()
        {
            var users = new List<User> { new User { Id = UserId, Name = UserName } };

            users[0].ApiSetting.Key = ApiKey;

            var userSet = new Mock<FakeObjectSet<User>>(users);

            var configurationManager = new Mock<IConfigurationManager>();
            configurationManager.Setup(mgr => mgr.ConnectionString(It.IsAny<string>())).Returns("Dummy Connection String");

            database = new Mock<Database>(configurationManager.Object, "Dummy");

            database.Setup(db => db.ObjectSet<User>()).Returns(userSet.Object);

            repository = new UserRepository(database.Object, queryFactory);
        }

        [Fact]
        public void Should_be_able_to_get_by_id()
        {
            var user = repository.GetById(UserId);

            Assert.Equal(UserName, user.Name);
        }

        [Fact]
        public void Should_be_able_to_get_by_name()
        {
            var user = repository.GetByName(UserName);

            Assert.Equal(UserId, user.Id);
        }

        [Fact]
        public void Should_be_able_to_get_by_api_key()
        {
            var user = repository.GetByApiKey(ApiKey);

            Assert.Equal(UserId, user.Id);
        }
    }
}

ShortUrlRepository

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System.Collections.Generic;
    using System.Linq;

    public class ShortUrlRepository : RepositoryBase<ShortUrl>, IShortUrlRepository
    {
        public ShortUrlRepository(Database database) : base(database)
        {
        }

        public ShortUrl GetById(long id)
        {
            Check.Argument.IsNotZeroOrNegative(id, "id");

            return Database.ShortUrls.SingleOrDefault(shortUrl => shortUrl.Id == id);
        }

        public ShortUrl GetByHash(string hash)
        {
            Check.Argument.IsNotNullOrEmpty(hash, "hash");

            return Database.ShortUrls.SingleOrDefault(shortUrl => shortUrl.Hash == hash);
        }

        public ShortUrl GetByAliasName(string aliasName)
        {
            Check.Argument.IsNotNullOrEmpty(aliasName, "aliasName");

            return Database.ShortUrls.SingleOrDefault(shortUrl => shortUrl.Aliases.Any(alias => alias.Name == aliasName));
        }

        public PagedResult<ShortUrl> FindByUserId(long userId, int start, int max)
        {
            Check.Argument.IsNotZeroOrNegative(userId, "userId");
            Check.Argument.IsNotNegative(start, "start");
            Check.Argument.IsNotNegative(max, "max");

            int total = Database.Aliases.Count(alias => alias.User.Id == userId);

            IQueryable<ShortUrl> result = Database.Aliases.Where(alias => alias.User.Id == userId)
                                                  .OrderByDescending(alias => alias.CreatedAt)
                                                  .Select(alias => alias.ShortUrl)
                                                  .Skip(start)
                                                  .Take(max);

            return new PagedResult<ShortUrl>(result, total);
        }
    }
}

ShortUrlRepositoryTests

namespace Shrinkr.Infrastructure.EntityFramework.UnitTests
{
    using System.Collections.Generic;

    using Moq;
    using Xunit;

    public class ShortUrlRepositoryTests
    {
        private readonly Mock<Database> database;
        private readonly ShortUrlRepository repository;

        public ShortUrlRepositoryTests()
        {
            var shortUrls = new List<ShortUrl> {
                                                    new ShortUrl { Id = 1, Title = "Shrinkr.com",Url = "http://shrinkr.com", Hash = "http://shrinkr.com".Hash() },
                                                    new ShortUrl { Id = 2, Title = "DotNetShoutout.com", Url = "http://dotnetshoutout.com", Hash = "http://dotnetshoutout.com".Hash() }
                                               };

            shortUrls[1].Aliases.Add(new Alias { Name = "dtntshtt" });

            var shortUrlSet = new Mock<FakeObjectSet<ShortUrl>>(shortUrls);

            var user = new User { Id = 1 };

            var aliases = new List<Alias>{
                                            new Alias { Id = 1, User = user, ShortUrl = shortUrls[0] },
                                            new Alias { Id = 2, User = user, ShortUrl = shortUrls[1] },
                                         };

            var aliasSet = new Mock<FakeObjectSet<Alias>>(aliases);

            var configurationManager = new Mock<IConfigurationManager>();
            configurationManager.Setup(mgr => mgr.ConnectionString(It.IsAny<string>())).Returns("Dummy Connection String");

            database = new Mock<Database>(configurationManager.Object, "Dummy");
            database.Setup(db => db.ObjectSet<ShortUrl>()).Returns(shortUrlSet.Object);
            database.Setup(db => db.ObjectSet<Alias>()).Returns(aliasSet.Object);

            repository = new ShortUrlRepository(database.Object);
        }

        [Fact]
        public void Should_be_able_to_get_by_id()
        {
            var shortUrl = repository.GetById(1);

            Assert.Equal(1, shortUrl.Id);
        }

        [Fact]
        public void Should_be_able_to_get_by_hash()
        {
            var shortUrl = repository.GetByHash("http://shrinkr.com".Hash());

            Assert.Equal(1, shortUrl.Id);
        }

        [Fact]
        public void Should_be_able_to_get_by_alias_name()
        {
            var shortUrl = repository.GetByAliasName("dtntshtt");

            Assert.Equal(2, shortUrl.Id);
        }

        [Fact]
        public void Should_be_able_to_find_by_user_id()
        {
            var shortUrls = repository.FindByUserId(1, 0, 10);

            Assert.Equal(2, shortUrls.Total);
            Assert.Equal(2, shortUrls.Result.Count);
        }
    }
}

and that’s it, we have completed the initial implementation of our Repositories. What do you think, what it is currently lacking? Yes we are not taking the advantages of Compiled Queries. In the next post, I will show how you can use the both compiled and regular queries in your repositories.

Stay tuned!!!

Shout it

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:

DomainObjects

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:

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:

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:

DataTransferObjects 

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!!!

Shout it

[Update: Maarten Balliauw confirmed that he has applied the suggested fix in MVC Sitemap provider]

As you know that we will be including Menu in our final release, when defining the menu, it will allow to specify the Route Name, Controller/Action name and associated route values for an menu item so that we can generate the corresponding url. One of the basic feature that we want to include is, when rendering the Menu it will scan through the controller’s actions and only render the menu items that the currently visiting user has permission. As you can guess, it is related with the AuthorizeAttribute of ASP.NET MVC framework. The actual method that is responsible for checking the permission of this attribute is AuthorizeCore which is marked as protected, so there is no way we can call this method from our code. So I decided to check, how other peoples are handling this issue, so far I have found two solutions:

  1. ASP.NET MVC Sitemap provider of Maarten Balliauw and
  2. MVCContrib.org.

But none of them are actually handling it correctly!!!.

Before moving to what is wrong with these solutions, let me paste the main portion of the AuthorizeAttribute code that is responsible for checking the permission:

public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
    public virtual void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (AuthorizeCore(filterContext.HttpContext))
        {
            HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
            cachePolicy.SetProxyMaxAge(new TimeSpan(0));
            cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
        }
        else
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
    }

    protected virtual bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException("httpContext");
        }

        IPrincipal user = httpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            return false;
        }

        if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
        {
            return false;
        }

        if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
        {
            return false;
        }

        return true;
    }
}

As you can see the only public method OnAuthorization (which is also an implementation of IAuthorizationFilter interface, we will discuss this interface after a little while) is calling the protected AuthorizeCore to check the permission, if it is permitted then it is adding some callback to sync with the ASP.NET Caching otherwise it returns Unauthorized Result.

Now lets see, what is wrong with the above two solutions, First the ASP.NET MVC SiteMap provider, the code that is responsible for checking the controller/action permission is the following:

IController controller = provider.GetController(requestContext, mvcNode.Controller); // get controller
if (controller is ControllerBase)
{
    var controllerContext = new ControllerContext(requestContext, (ControllerBase)controller);
    var authorizationContext = new AuthorizationContext(controllerContext);

    foreach (IAuthorizationFilter att in controller.GetType().GetCustomAttributes(typeof(IAuthorizationFilter), true).Union(// get controller authorization filters
    controller.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(m =>
    {
        var nameAtt = m.GetCustomAttributes(typeof(ActionNameAttribute), true).FirstOrDefault() as ActionNameAttribute;
        return string.Equals(mvcNode.Action, nameAtt != null ? nameAtt.Name : m.Name, StringComparison.OrdinalIgnoreCase);
    })
    .SelectMany(m => m.GetCustomAttributes(typeof(IAuthorizationFilter), true))))// get authorization filters from all related methods
    {
        if (att is ValidateAntiForgeryTokenAttribute)
        {
            return true;
        }
        att.OnAuthorization(authorizationContext); // run authorization
        if (authorizationContext.Result != null) // authorization failed
        {
            return false;
        }
    }
}

There are two issues with the above code:

  1. It depends upon the IAuthorizationFilter rather than AuthorizeAttribute, the problem with this approach is there more filters other than AuthorizeAttribute which implements this interface, as you can see there is a check for ValidateAntiForgeryTokenAttribute in the above code, there are few more already in the ASP.NET MVC Framework e.g. ValidateInputAttribute, RequireSslAttribute and anyone can write a custom filter that implements this interface and decorate the controller/action, so this code will return incorrect results, in those cases.
  2. The next issue, when it is executed for AuthorizeAttribute which in turns adds caching callback and there is no way to remove that callback once the OnAuthorization calls gets completed, so it will screw the ASP.NET Caching and might hurt the application performance.

The correct way to address this issue:

  1. Don’t depend on the IAuthorizationFilter interface, instead check the concrete AuthorizeAttribute which is meant to be dealing with Roles/Users, also it is not marked as sealed, so it is expected that you will be inheriting this class and override the AuthorizeCore method if you want to put your custom logic.
  2. Find a way to call the protected AuthorizeCore method which is actually responsible for checking the permission.

Now, lets see how the MVCContrib team is handling this issue, basically they have created a new class inherited from the AuthorizeAttribute and it also contains a public method Authorized which in turn calls the protected AuthorizeCore method:

public class OpenAuthorizeAttribute : AuthorizeAttribute
{
    public OpenAuthorizeAttribute(AuthorizeAttribute attribute)
    {
        Order = attribute.Order;
        Roles = attribute.Roles;
        Users = attribute.Users;
    }

    public bool Authorized(RequestContext requestContext)
    {
        return AuthorizeCore(requestContext.HttpContext);
    }
}

And whenever they wants to check permission they pass the actual attribute to this new class and calls the Authorized method. Much better than the previous solution, still it will not work when you create a Custom AuthorizationAttribute, as it will call the AuthorizeCore of the original AuthorizationAttribute rather than yours.

After discussing with the ASP.NET MVC Team and Levi Broderick explained me really well that AuthorizeAttribute was not really designed to be used in this kind of scenario.To solve it, yes we can use InvokeMethod of reflection, but invoking a protected method will not work in medium trust environment. So the only option that is left to monkey patch this issue is IL rewritting. The logic is, when checking the permission it will check whether a custom AuthorizeAttribute is used, if not it will follow the same as the MVCContrib is currently doing, if a custom Authorization attribute is used, it will runtime create a inherited class with a public method which we will call to check the permission, of course there is slight a performance overhead associated, but we can easily overcome it with proper caching. Now lets take a quick look how we are checking whether the user has permission of an action, assuming that the AuthorizeAttributes are collected from another service:

foreach (AuthorizeAttribute authorizationAttribute in authorizationAttributes)
{
    if (authorizationAttribute != null)
    {
        try
        {
            Type currentAuthorizationAttributeType = authorizationAttribute.GetType();

            IAuthorizeAttribute subclassedAttribute = (currentAuthorizationAttributeType == typeof(AuthorizeAttribute)) ?
                                                       new InternalAuthorize() : // No need to use Reflection.Emit when asp.net mvc built-in attribute is used
                                                       reflectedAuthorizeAttributeCache.GetAttribute(currentAuthorizationAttributeType);

            subclassedAttribute.Order = authorizationAttribute.Order;
            subclassedAttribute.Roles = authorizationAttribute.Roles;
            subclassedAttribute.Users = authorizationAttribute.Users;

            // Copy the remaining properties (if there is any)
            objectCopier.Copy(authorizationAttribute, subclassedAttribute, "Order", "Roles", "Users" /* Excluded properties */);

            allowed = subclassedAttribute.IsAuthorized(requestContext.HttpContext);
        }
        catch
        {
            // do not allow on exception
            allowed = false;
        }

        if (!allowed)
        {
            break;
        }
    }
}

As you can see when the default AuthorizeAttribute is used we are creating InternalAttribute, if not we are requesting a caching service to get the runtime version of this attribute, for each custom attribute we are using Reflection.Emit to create a subclass of that attribute and then we are caching it so that we can use the same attribute without recreating it with Reflection.Emit. Also to avoid naming collision, we created an interface IAuthorizeAttribute which we implements with IL. The IAuthorizeAttribute contains the same properties as the original AuthorizeAttribute:

public interface IAuthorizeAttribute
{
    int Order
    {
        get;
        set;
    }

    string Roles
    {
        get;
        set;
    }

    string Users
    {
        get;
        set;
    }

    bool IsAuthorized(HttpContextBase httpContext);
}

Now here is the magic code that generates the class at runtime:

// (c) Copyright Telerik Corp. 
// This source is subject to the Microsoft Public License. 
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. 
// All other rights reserved.

namespace Telerik.Web.Mvc.Infrastructure.Implementation
{
    using System;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Web;

    public class AuthorizeAttributeBuilder : IAuthorizeAttributeBuilder
    {
        private static readonly Type authorizeAttributeType = typeof(IAuthorizeAttribute);
        private static readonly ModuleBuilder module = CreateModuleBuilder();

        public ConstructorInfo Build(Type parentType)
        {
            Guard.IsNotNull(parentType, "parentType");

            string typeName = "$" + parentType.FullName.Replace(".", string.Empty);

            TypeBuilder typeBuilder = module.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, parentType, new[] { authorizeAttributeType });
            typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
            typeBuilder.AddInterfaceImplementation(authorizeAttributeType);

            WriteProperty(parentType, typeBuilder, "Order", typeof(int));
            WriteProperty(parentType, typeBuilder, "Roles", typeof(string));
            WriteProperty(parentType, typeBuilder, "Users", typeof(string));
            WriteIsAuthorized(parentType, typeBuilder);

            Type type = typeBuilder.CreateType();

            return type.GetConstructor(Type.EmptyTypes);
        }

        private static void WriteProperty(Type parentType, TypeBuilder builder, string name, Type type)
        {
            string getName = "get_" + name;
            string setName = "set_" + name;

            MethodInfo parentGetMethod = parentType.GetMethod(getName, BindingFlags.Public | BindingFlags.Instance);
            MethodBuilder implementedGetMethod = builder.DefineMethod(getName, MethodAttributes.Public | MethodAttributes.Virtual, type, Type.EmptyTypes);
            ILGenerator getIl = implementedGetMethod.GetILGenerator();
            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Call, parentGetMethod);
            getIl.Emit(OpCodes.Ret);

            MethodInfo interfaceGetMethod = authorizeAttributeType.GetMethod(getName, BindingFlags.Public | BindingFlags.Instance);
            builder.DefineMethodOverride(implementedGetMethod, interfaceGetMethod);

            MethodInfo parentSetMethod = parentType.GetMethod(setName, BindingFlags.Public | BindingFlags.Instance);
            MethodBuilder implementedSetMethod = builder.DefineMethod(setName, MethodAttributes.Public | MethodAttributes.Virtual, typeof(void), new[] { type });
            ILGenerator setIl = implementedSetMethod.GetILGenerator();
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Call, parentSetMethod);
            setIl.Emit(OpCodes.Ret);

            MethodInfo interfaceSetMethod = authorizeAttributeType.GetMethod(setName, BindingFlags.Public | BindingFlags.Instance);
            builder.DefineMethodOverride(implementedSetMethod, interfaceSetMethod);
        }

        private static void WriteIsAuthorized(Type parentType, TypeBuilder builder)
        {
            MethodInfo protectedMethod = parentType.GetMethod("AuthorizeCore", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod);
            MethodBuilder implementedMethod = builder.DefineMethod("IsAuthorized", MethodAttributes.Public | MethodAttributes.Virtual, typeof(bool), new[] { typeof(HttpContextBase) });
            ILGenerator il = implementedMethod.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Call, protectedMethod);
            il.Emit(OpCodes.Ret);

            MethodInfo interfaceMethod = authorizeAttributeType.GetMethod("IsAuthorized", BindingFlags.Public | BindingFlags.Instance);
            builder.DefineMethodOverride(implementedMethod, interfaceMethod);
        }

        private static ModuleBuilder CreateModuleBuilder()
        {
            const string Name = "InheritedAuthorizeAttributes";

            AssemblyName assemblyName = new AssemblyName(Name + "Assembly")
                                            {
                                                Version = typeof(AuthorizeAttributeBuilder).Assembly.GetName().Version
                                            };

            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(Name + "Module");

            return moduleBuilder;
        }
    }
}

There is only one public method Build where you have to pass the custom AuthorizeAttribute type, the rest will be taken care by it. And if you want to use the above code, you are permitted to do so, as it is licensed under MS-PL.

The moral of this story is though ASP.NET MVC is one of the most extensible framework by MS, yet there are some places where it requires some re-work, but it does not mean you would say some harsh word like some negative minded people of our community, instead you should look for the solution and share it.

Shout it
More Posts