Practical Generics: CrudController
June 20th 2008, 6:48 pm in .NET, C#, Development, NHibernate, Patterns, Snippet.
In my last post I discussed code reuse in the form of an abstract CrudController class: a means of providing create, read, update and delete actions for a given entity. In addition, that class provided the ability to work with an entity specification class to allow for filtered reads - which is just another name for search results.
I’m going to show C# code samples for building with a class like this, but you’ll have to fill in the gaps in terms of how you can work with it. The code to list entities is the most interesting and will demonstrate the concept best, so I’m going to focus on that. To begin with, let’s assume a very simple entity:
public class Post
{
public DateTime CreatedOn { get; set; }
public string Headline { get; set; }
public string Body { get; set; }
public string Username { get; set; }
}
So, to begin with we’d like to list Posts. A typical method to do so would be:
public void List()
{
PropertyBag["posts"] = PostRepository.FindAll();
}
But remember that in my case, I’m trying to create a reusable method of doing this, and that I’m also going to be working with ExtJS which is going to consume JSON. So, I have the following class:
public abstract class CrudController<Entity>
{
private IRepository<Entity> _repository;
public void GetJsonList()
{
RenderText(
JsonHelper.Serialize(_repository.FindAll())
);
}
}
public class PostController : CrudController<Post>{}
I need a shell PostController, but most of the action is happening in the CrudController, and I’m using the magic of Windsor and generics to make it happen. By passing Post as a type parameter to CrudController, Windsor will then give me the correct IRepository<T> to work with, and from there it’s a simple matter of fetching the list of Post Entities in the same way I did in the previous code sample. I want to get a JSON string back, so I’m passing that list to a helper to serialize to JSON.
The next step is to make this listing method a bit more flexible and a bit more powerful. I want to do a couple of things - paging, sorting, and searching. Here’s my new method signature for GetListJson:
void GetListJson(int start, int limit, string sort, string dir, EntitySpecification spec)
The arguments “start” and “limit” are for paging, saying which record I should start from and how many I should return. The “sort” argument tells me the column I should sort on, and “dir” gives me the sort direction. But what about EntitySpecification? Let’s show it in context:
public abstract class CrudController<Entity, EntitySpecification> where EntitySpecification : ISpec
{
private IRepository<Entity> _repository;
public void GetJsonList(int start, int limit, string sort, string dir, [DataBind("spec")]EntitySpecification spec)
{
spec.AddOrder(sort, dir);
spec.FindAll(_repository, start, limit);
}
}
public class PostController : CrudController<Post, PostSpecification>{}
As you can see, EntitySpecification is databound by Monorail; incoming parameters are passed to the specification to build up a criteria for querying, as described in Ayende’s search specification post. That means that I don’t have to explicitly handle searching in my CrudController at all, because it’s all handled by the EntitySpecification. A sample PostSpecification could look like this:
public class PostSpecification : ISpec
{
private ICriteria _criteria;
private string _username;
public virtual string Username
{
get { return _username; }
set {
_username = value;
if (value == null)
return;
_criteria.Add(
Expression.Eq("Username", value)
);
}
}
public void AddOrder(string sort, string dir)
{
var order = (dir == "ASC" ? .Order.Asc(sort) : Order.Desc(sort));
_criteria.AddOrder(new Order());
}
public IList<Post> FindAll(IRepository<Post> repo, int start, int limit)
{
repo.Find(_criteria, start, limit);
}
}
When Monorail runs the databinder, the Username property’s getter will be called and the private criteria will be altered. When the specification’s FindAll method is called, that criteria is passed through to filter the returned records.
Let me know if you have any improvements or suggestions, and thank you again to Ayende for the specification ideas.
Finders with DetachedCriteria
December 17th 2007, 3:57 am in .NET, Active Record, C#, Castle, Patterns.
I should file this under “things that are probably a design pattern but I’m not sure exactly what kind”; it seems like one of those typical things that you will naturally stumble upon when using the Repository pattern and indeed when you start to separate out your query logic.
DetachedCriteria is an “offline” version of the NHibernate Criteria class, used to build up Criteria where an NHibernate Session is not available. The NHibernate documentation has more on how to use DetachedCriteria on its own, but I want to talk about inheriting from it to create a simple custom finder class.
Say we have a Active Record Person class, with height and weight properties:
[ActiveRecord]
public class Person : ActiveRecordBase
{
[PrimaryKey]
public int Id { get; set; }
[Property]
public int Height { get; set; }
[Property]
public int Weight { get; set; }
}
The height and weight properties are simplified, and I’m using C# 3.0 automatic properties for clarity. I want to search for a Person based on their height and weight, so to do so, I’ll create a new finder class:
public class PersonFinder : DetachedCriteria
{
public class PersonFinder(height, weight) : base(typeof(Person))
{
Add(Expression.Eq("Height", height));
Add(Expression.Eq("Weight", weight));
}
}
A simple class, but it clearly states its purpose. Here’s how it could be used:
Person.FindAll(new PersonFinder(200, 300), new Order("Id", true));
For me, the advantage of this code isn’t just in reducing what you have to type. Instead it is in the readability and discoverability of the code.
Basic Windsor Usage with the Repository Pattern
September 15th 2007, 4:39 pm in .NET, C#, Castle, Patterns, Windsor.
I’ve been interested in using the Repository pattern in an application for a while now; same goes for Windsor. When prototyping with repositories it became clear that I could use them together in a fairly simple way which would serve as a good introduction to both.