Friday 23 December 2011

Writing ASP.NET MVC 3 Generic Controllers

I am currently working on a project that has an awful lot of data analysis pages built on similar datasets. We decided to build a page model based on generic functionality, which means pages build themselves based on some metadata stored in an SQL Server 2008 database. The following is the planned task flow
  • Figure out a URL structure that'll contain information on the resource or page the user has requested.
  • Use the MVC 3 routing mechanism to resolve the page identity in the URL.
  • Use the page identity to resolve an existing strongly-typed data class authored to maintain datasets for this page.
  • Use the MVC 3 controller resolver to create an instance of a generic controller that takes the data class as a parameter.
  • Write generic methods/routines in the generic controller that gets datasets from the database depending on the data class type.
Lets work our way down the flow.

First, in the Global.asax add a new route that takes an extra parameter.

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

routes.MapRoute(
"GenericController", // Route name
"Generic/{action}/{GenericControllerVariable}/{id}",
new { controller = "Generic", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

}

Notice how in the "GenericController" route, there is an extra parameter "GenericControllerVariable". Now we've established a way to retrieve or resolve the dataset requested, using the "GenericControllerVariable" as the page identity.

Now we want to resolve the generic controller type and assign it the data class type as a parameter.

public class CustomControllerFactory : DefaultControllerFactory
{
public override IController CreateController(RequestContext requestContext, string controllerName)
{
if (controllerName == "Generic")
{
//Use your favourite DI Container to resolve the customcontrollerfactory
var genericControllerResolver = new CustomGenericControllerFactory();
return genericControllerResolver.GetControllerType(requestContext);

}

return base.CreateController(requestContext, controllerName);
}

protected override System.Type GetControllerType(RequestContext requestContext, string controllerName)
{
if(controllerName == "Generic")
{
return typeof (GenericController<SomeModelType>);
}


return base.GetControllerType(requestContext, controllerName);
}
}

//This should implement an interface
public class CustomGenericControllerFactory
{
public IController GetControllerType(RequestContext requestContext)
{
//Use your favourite DI container to set up and resolve the concrete controller type using the
//following genericControllerVariable.
var genericControllerVariable = requestContext.RouteData.Values["GenericControllerVariable"];

switch (genericControllerVariable.ToString())
{
case "Foo":
return new GenericController<FooType>(new GenericRepository<FooType>());
case "Bar":
return new GenericController<BarType>(new GenericRepository<BarType>());
}

return new GenericController<SomeModelType>(new GenericRepository<SomeModelType>());
}
}

The code above intercepts calls into the default controller creation pipeline, retrieves the "GenericControllerVariable" value and uses this to decide and instantiate the generic controller and it's underlying type. This is made possible with the implementation of a custom dependency resolver, to replace the DefaultControllerFactory with our custom one like so;

public class CustomDependencyResolver : IDependencyResolver
{
public object GetService(Type serviceType)
{
//Use your favourite DI container to set up and resolve the concrete controller type using the
//following genericControllerVariable.
//var genericControllerVariable = requestContext.RouteData.Values["GenericControllerVariable"];

if(serviceType.Name == "IControllerFactory")
{
return new CustomControllerFactory();
}


switch (serviceType.Name)
{
case "Foo":
return new GenericController<FooType>(new GenericRepository<FooType>());
case "Bar":
return new GenericController<BarType>(new GenericRepository<BarType>());
}

return null;
//return new GenericController<SomeModelType>(new GenericRepository<SomeModelType>());
}

public IEnumerable<object> GetServices(Type serviceType)
{
return new List<object>();
}
}

Now register this implementation in the Global.asax.cs file like so;

protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);

DependencyResolver.SetResolver(new CustomDependencyResolver());
}

With the generic controller and it's underlying type resolved, we can go ahead and write our custom generic controller to process requests from the user.

public class GenericController<T> : System.Web.Mvc.Controller
{
private readonly IGenericRepository<T> _genericRepository;

public GenericController(IGenericRepository<T> genericRepository)
{
_genericRepository = genericRepository;
}

public ActionResult Index()
{
return View(_genericRepository.GetData());
}
}

You can download the source code for this demo on GitHub.

Tuesday 18 October 2011

Git - Track remote branch from existing local branch

Issuing the Git Clone command (git clone remote-branch local-branch) will get a latest version of the remote branch and update the local-branch config file to track the remote-branch.

How about if the local-branch exists already and all you want to do is tell git to start tracking the remote-branch from the local-branch without overidding all changes in the local-branch?

Try the following command;

git branch --set-upstream local-branch origin/remote-branch

To update your local set up with all available remote branches for a particular remote repository, try

git remote update
git branch -a
git checkout "branchname"

You can create a new remote branch from a local branch by simply issuing a push to the remote repository and specifying the new branch name like so

git push -u origin "new branch name"

Wednesday 12 October 2011

Versioning Visual Studio 2010 Solution using Nant

Creating an automated versioning functionality for a particular project may be straight forward, but often you’d want all projects in the solution to share the same version number without having to author each project’s “Assembly.cs”. If update fails for any of the projects, the solution will be left in an inconsistent state version-wise.

The solution to this is to create a common assembly file and link it to each project in the solution. Then any update to the parent common assembly will ripple through to all projects.

  • Create a solution folder and add an assembly file, namely “CommonAssembly.cs”.


  • For each project add the “CommonAssembly.cs” file AS A LINK, by right-clicking the project, Add, Existing Item.


  • Note the icon on the newly added file link.



  • Now add a Nant target to update the “CommonAssembly.cs” parent file at build time.