Wednesday, January 25, 2012

FubuMVC Conventional Design List View

Coming from Asp.Net MVC I was so used to the structure expected, for example all controllers had to end in “Controller” and your controllers were separate from the Views etc.  Nothing wrong with this design, but coming into Fubu and having so much freedom to place files how ever you want them was a bit daunting at first.  What I want to cover over my next blog posts are the following conventional ways FubuMVC makes structuring your project easier, and how it does a lot of the heavy work for you out of the box.  This post assumes you have something similar to the configuration as this.

  1) Conventional List Views (this post)
  2) Conventional Create Views
  3) Conventional Edit Views
 

I chose these as they are the most commonly structured tree within a web application.  So this looks something like:

root
   - EntityFolder
       - List.spark
       - Edit.spark
       - Create.spark (or New.spark)

This is pretty common so it makes sense to target the 80% audience and hopefully someone gets something out of this.  To start off I’m assuming you use the following structure and that your entities use an interface like so:

public interface IEntity
{
    int Id { get; set; }
}


Note: Id can be int or Guid, doesn’t matter



Now going back to my Asp.Net experience, wanting a list view to render, the most common style was creating a Controller and then an ActionResult method named Index.  I could do this same thing in Fubu, however if my style of entity design is always going to be Create, Edit, and List then why not let Fubu perform this task for me conventionally.  To get started we will need to build up our convention, lets add a class and call it ListEntity<T> so that it accepts any of our entity classes.  This guy will be responsible for getting a collection of our objects and returning them.



public class ListEntity<TEntity, TList>

where TEntity: class, IEntity

where TList : IEntityList<TEntity>, new()

{
    private readonly IRepository _repository;  	
  	
    public ListEntity(IRepository repository)  	
    {  	
          _repository = repository;	  	
    } 
	  	
    public TList<T> Get(EntityList<TEntity> items)  	
    {
       return new TList<T>() {Items = _repository.All<TEntity>()};
    }	
}


Now lets create our Model that we will use to bind our list



public class EntityList<T> : IEntityList<T> where T : IEntity	  	
{  	
     public IEnumerable<T> Items { get; set; } 	  	
}


You see I’m using an IEntityList, that is nothing more than an interface requiring an IEnumerable<T> Items.  Now what we have here is the conventional piece that will be responsible for delivering EntityList<T> to our Model.  We need a class that takes in IActionSource and will be responsible for ensuring our convention is triggered which will call our ListEntity Get method.



public class ListEntityActionSource : IActionSource  	
{	  	
     private static readonly string MethodName = ReflectionHelper.GetMethod<ListEntity<IEntity, EntityList<IEntity>>>(a => a.Get(null)).Name;	  	
	  	
     public static MethodInfo GetExecuteMethod(Type action)  	
     {  	
         return action.GetMethod(MethodName);	  	
     }
	  	
     public IEnumerable<ActionCall> FindActions(TypePool types)
     {
	 return types.TypesMatching(type => type.IsConcreteTypeOf<IEntity>())
	  	.Select(t =>
	  	 {
	  	     var action = typeof(ListEntity<,>).MakeGenericType(t, getListType(t);
	  	     return new ActionCall(action, GetExecuteMethod(action));
	  	  });

}

 

private Type getListType(Type entity)
     {
            return _container
                .Model
                .DefaultTypeFor(typeof(IEntityList<>).MakeGenericType(entity));
     }

}


And now we need to generate a way to register this with fubu as well as set our route up



public class ListEntityEndpoints : IFubuRegistryExtension	  	
{	  	
     public static string ToRoutePattern(Type entityType)  	
     {	  	
        return "{0}/list".ToFormat(entityType.Name.ToLower());	  	
     }	  	
	  	
     public void Configure(FubuRegistry registry)	  	
     {	  	
         registry.Actions.FindWith(new ListEntityActionSource());	  	
         registry.ApplyConvention(new LambdaConfigurationAction(graph => graph.Actions().Where(x => x.HandlerType.Closes(typeof(ListEntity<,>))).Each(ModifyChain)));	  	
     }	  	
	  	
     public static void ModifyChain(ActionCall action)	  	
     {	  	
         var chain = action.ParentChain();  	
         var pattern = ToRoutePattern(action.HandlerType.GetGenericArguments()[0]);
         chain.Route = new RouteDefinition(pattern);	  	
         chain.Route.AddHttpMethodConstraint(action.Method.Name.ToUpper());	  	
     }	  	
}


Then to register your ListEntityEndpoints, simply import it into your FubuRegistry like so:



public class MyFubuRegistry : FubuRegistry
{
    ... 
    Import<ListEntityEndpoints>();
    
    ...
}


And now we add into our Registry a section informing Structuremap how to use our IEntityList types, since we are using a generic we have to implement a closing type otherwise spark may have issues:



public class WebRegistry : Registry
{
    public WebRegistry()
    {
        Scan(x =>
                 {
                     x.TheCallingAssembly();
                     x.WithDefaultConventions();
                     x.ConnectImplementationsToTypesClosing(typeof (IEntityList<>));
                 });
    }
}


This is so awesome how this works, now that I have configured my convention, all I have to do from now on for all my entities that I want a list view is to use IEntityList<T> or I can create a model that inherits EntityList<MyEntityClass> (EntityList inherits IEntityList) for the model of my spark view and voilà! This works for your simplest case.  If you need additional behaviors for your list, this of course would need additional behaviors added.  But for the most part, if you need a view that renders a basic list of data then this covers your common list scenarios.  Fubu will wire this up automatically and you now have a list view, you do not require another Endpoint (Or Controller for Asp.Net MVC guys) from here on out.  Keep in mind this adds a List to all of your IEntity types (person/list, address/list, etc), the only difference is only entities that are wired up to a View will actually be chained to a View endpoint.



 



I tried pulling parts out of my codebase and we all know its difficult to get what is needed for a blog post only and exclude the rest of the sections, so if you have some issues getting started with this don’t hesitate to contact me gary.l.coxjr AT gmail.com



 



Happy Coding!

1 comment:

KevM said...

Thanks for the great post. At Dovetail we've used this technique to apply an entire subsystems of functionality (website UI, navigation, and search) based on the type of the Entity.

 
Creative Commons License
Blogged Information and Code is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.