C# Tricks: Slimming down your controllers
This blog post is dedicated to my colleague Seminda who has been experimenting with how to create simple and powerful web applications. Thank you for showing me your ideas and discussing improvements with me, Seminda.
I find many C# applications have much unnecessary code. This is especially true as the weight of the business logic of many applications are shifting from the backend to JavaScript code in the web pages. When the job of your application is to provide data to a front-end, it’s important to keep it slim.
In this article, I set out to simplify a standard MVC 4 API controller by generalizing the functionality, centralizing exception handling and adding extension methods to the DB set that is used to fetch my data.
If you generate an API controller based on an existing Entity and remove some of the noise, your code may look like this:
public class PersonController : ApiController
{
private readonly DbContext \_dbContext;
private readonly IDbSet \_people;
public PersonController()
{
\_dbContext = new ApplicationDbContext();
\_people = dbContext.Set();
}
public Person GetPerson(int key)
{
return \_people.Find(key);
}
public HttpResponseMessage PostPerson(Person value)
{
\_people.Add(value);
\_dbContext.SaveChanges();
return Request.CreateResponse(HttpStatusCode.Created);
}
public IEnumerable GetPeople()
{
return \_people.ToList();
}
public IEnumerable GetAdminsInCity(string city)
{
return \_people
.Where(p => p.City == city && p.Type == PersonType.Admin)
.Select(p => new PersonSummary { FullName = p.FirstName + " " + p.LastName });
}
public HttpResponseMessage DeletePerson(int id)
{
Person person = db.Persons.Find(id);
\_people.Remove(person);
try
{
\_dbContext.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
}
return Request.CreateResponse(HttpStatusCode.OK, person);
}
}
This code is a simplified version of what the API 4 Controller wizard will give you. It includes a GetPerson method that returns a person by Id, PostPerson which saves a new person, GetPeople which returns all people in the database, GetAdminsInCity, which filters people on city and type and DeletePerson which finds the person with the specified Id and deletes it.
I have replaced DbContext and IDbSet with interfaces instead of the concrete subclass of DbContext makes it easy to create tests that use a double for the database, for example MockDbSet.
Generify the controller
This is easy and safe as long as things are simple. As a general tip, rename you methods to “Get”, “Post” and “Index” rather than “GetPerson”, “PostPerson” and “GetPeople”. This will make it possible to generalize the controller thus:
public class PersonController : EntityController
{
public PersonController() : base(new ApplicationDbContext())
{
}
public IEnumerable GetAdminsInCity(string city)
{
return \_dbSet
.Where(p => p.City == city && p.Type == PersonType.Admin)
.Select(p => new PersonSummary { FullName = p.FirstName + " " + p.LastName });
}
}
The generic EntityController can be used for any class that is managed with EntityFramework.
public abstract class EntityController : ApiController where T : class
{
private readonly DbContext \_dbContext;
protected IDbSet \_dbSet;
protected EntityController(DbContext dbContext)
{
\_dbContext = dbContext;
\_dbSet = dbContext.Set();
}
public HttpResponseMessage Post(Person value)
{
\_dbSet.Add(value);
\_dbContext.SaveChanges();
return Request.CreateResponse(HttpStatusCode.Created);
}
public IEnumerable Get()
{
return \_dbSet.ToList();
}
public T Get(int key)
{
return \_dbSet.Find(key);
}
public HttpResponseMessage Delete(int id)
{
T entity = \_dbSet.Find(id);
\_dbSet.Remove(entity);
try
{
\_dbContext.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
}
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
}
Exception handling
I will dare to make a bold generalization: Most of the bugs that remain in production software are in error handling. Therefore I have a very simple guideline: No catch blocks that don’t rethrow another exception.
The actual handling of exceptions should be centralized. This both makes the code easier to read and it ensures that exceptions are handled consistently. In MVC 4, the place to do this is with a ExceptionFilterAttribute.
We can thus simplify the EntityController:
[HandleApplicationExceptions]
public class PersonController : ApiController
{
// Post and Get omitted for brevity
public HttpResponseMessage Delete(int id)
{
\_dbSet.Remove(\_dbSet.Find(id));
\_dbContext.SaveChanges();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
The HandleApplicationException looks like this:
public class HandleApplicationExceptionsAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
var exception = context.Exception;
if (exception is DbUpdateConcurrencyException)
{
context.Response =
context.Request.CreateErrorResponse(HttpStatusCode.NotFound, exception);
return;
}
context.Response =
context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, exception);
}
}
This code code of course add special handling of other types of exceptions, logging etc.
DbSet as a repository
But one piece remains in PersonController: The rather complex use of LINQ to do a specialized query. This is where many developers would introduce a Repository with a specialized “FindAdminsByCity” and perhaps even a separate service layer with a method for “FindSummaryOfAdminsByCity”.
If you start down that path, many teams will choose to introduce the same layers (service and repository) even when these layers do nothing and create a lot of bulk in their applications. From my personal experience, this is worth fighting against! Needless layers is the cause of a huge amount of needless code in modern applications.
Instead, you can make use of C# extension methods:
public static class PersonCollections
{
public static IQueryable GetAdminsInCity(this IDbSet persons, string city, PersonType personType)
{
return persons.Where(p => p.City == city && p.Type == personType);
}
public static IEnumerable SelectSummary(this IQueryable persons)
{
return persons.Select(p => new PersonSummary { FullName = p.FirstName + " " + p.LastName });
}
}
The resulting controller method becomes nice and encapsulated:
```csharp
public IEnumerable SummarizeAdminsInCity(string city)
{
return \_dbSet.WhereTypeAndCity(city, PersonType.Admin).SelectSummary();
}
The extension methods can easily be reused and can be freely combined.
### Conclusions
With these tricks, most of your controllers would look something like this:
```csharp
public class PersonController : EntityController
{
public PersonController(DbContext dbContext) : base(dbContext)
{
}
public IEnumerable SummarizeAdminsInCity(string city)
{
return \_dbSet.WhereTypeAndCity(city, PersonType.Admin).SelectSummary();
}
}
That's pretty minimal and straightforward!
I've showed three tricks to reduce the complexity in your controllers: First, parts of your controllers can be generalized, especially if you avoid the name of the entity in action method names; second, exception handling can be centralized, removing noise from the main application flow and enforcing consistency; finally, using extension methods on IQueryable<Person> gives my DbSet domain specific methods without having to implement extra layers in the architecture.
I'd love to hear if you're using these approach or if you've tried something like it but found it lacking.
Comments:
[quesa] - Aug 13, 2014
I tested the generification of my controller but had issues when trying to ‘keep things simple’ as my return needed to include multple tables joined or included. I needed to make custom modifications to the generified controller and that didn’t seem to mix well with what you are trying to accomplish.
PS. I’m nowhere near an expert coder, such as yourself, so it could be that I’m trying to do the modifications in the wrong place.
Johannes Brodwall - Aug 14, 2014
A good question. This approach is in the spirit of “make simple things simple”. When things get more complicated, other approaches are needed.
A query that requires joins or other operators that are on DbSets, but not on IQueryable can still be made into an extension method on PersonCollections (but this time, on IQueryable). I would start my experimentation there.
If that becomes cumbersome, I would revert to the old repository pattern.
The most important thing to remember is that different problems have different solutions. Don’t solve simple problems in a complex way just to be consistent with the complex problems. :-)
[neil scales] - Dec 30, 2015
In a professional application, you must always do server side validation of inputs. With your controller, once I’ve got a login to your site, I can delete someone else’s records !
Johannes Brodwall - Jan 2, 2016
You’re right, but authorization (not validation!) is a cross-cutting concern and should not be done in the specific controller. I drafted a blog post on it, but never finished it, sadly. :-(