.NET

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<Person> _people;
    public PersonController()
    {
        _dbContext = new ApplicationDbContext();
        _people = dbContext.Set<Person>();
    }
    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<Person> GetPeople()
    {
        return _people.ToList();
    }
    public IEnumerable<PersonSummary> 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<Person>
{
    public PersonController() : base(new ApplicationDbContext())
    {
    }
    public IEnumerable<PersonSummary> 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<T> : ApiController where T : class
{
    private readonly DbContext _dbContext;
    protected IDbSet<T> _dbSet;
    protected EntityController(DbContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = dbContext.Set<T>();
    }
    public HttpResponseMessage Post(Person value)
    {
        _dbSet.Add(value);
        _dbContext.SaveChanges();
        return Request.CreateResponse(HttpStatusCode.Created);
    }
    public IEnumerable<T> 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<Person> GetAdminsInCity(this IDbSet<Person> persons, string city, PersonType personType)
    {
        return persons.Where(p => p.City == city && p.Type == personType);
    }
    public static IEnumerable<object> SelectSummary(this IQueryable<Person> persons)
    {
        return persons.Select(p => new PersonSummary { FullName = p.FirstName + " " + p.LastName });
    }
}

The resulting controller method becomes nice and encapsulated:

public IEnumerable<object> 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:

public class PersonController : EntityController<Person>
{
    public PersonController(DbContext dbContext) : base(dbContext)
    {
    }
    public IEnumerable<object> 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.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button