Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic CRUD controllers and views

Tags:

asp.net-mvc

I'm just going through some intro tutorials for ASP.NET and I've got a decent idea of how to implement a simple CRUD admin app.

Are there any commonly used patterns to implement generic List/Create/Update/Delete actions? It seems pretty tedious to have to build scaffolding for every model, and then to maintain all of the add, edit and list views and controllers. It would be a lot more efficient and less error-prone to implement generic actions like:

/List/Model
/Edit/Model/id
/Update/Model/id
/Delete/Model/id

that would handle any model.

like image 800
pixelmike Avatar asked Nov 19 '14 16:11

pixelmike


1 Answers

I've done something similar, I think, to what you're talking about in an admin application I built. Basically, the key is to use generics. In other words, you create a controller like:

public abstract class AdminController<TEntity> : Controller
    where TEntity : IEntity, class, new()
{
    protected readonly ApplicationDbContext context;

    public virtual ActionResult Index()
    {
        var entities = context.Set<TEntity>()
        return View(entities);
    }

    public virtual ActionResult Create()
    {
        var entity = new TEntity();
        return View(entity);
    }

    [HttpPost]
    public virtual ActionResult Create(TEntity entity)
    {
        if (ModelState.IsValid)
        {
            context.Set<TEntity>().Add(entity);
            context.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(entity);
    }

    ...
}

In other words, you just build an entire reusable controller structure, with the key parts being that you're using the generic TEntity instead of a concrete class. Notice that TEntity is defined as IEntity, class, new(). This does a few things. First, class allows you to treat it as a concrete type and new() means that the type will be something that can be instantiated, rather than something like an abstract class. IEntity is just a placeholder for whatever you may be using in your application to ensure all the types have some common denominator. At the very least for a CRUD-style application, you'll need this to gain access to an Id or similar property for things like your edit and delete actions. Saying that TEntity implements IEntity lets you utilize any properties on IEntity. If you use a concrete type here instead of an interface, you can leave off the class part, e.g. where TEntity : Entity, new().

Then, in order to use this, you just define a new controller that inherits from AdminController<> and specify the type you're working with:

public class WidgetController : AdminController<Widget>
{
    public WidgetController(ApplicationDbContext context)
    {
        this.context = context;
    }
}

That could be potentially all you need for your individual controllers. Also, worth noting here is that I've set this up to employ dependency injection for your context. You could always change your constructor to something like:

public WidgetController()
{
    this.context = new ApplicationDbContext();
}

But, I recommend you do look into using dependency injection, in general. Also, I'm using the context directly here for ease of explanation, but usually you'd be employing services, repositories, etc. here instead.

Finally, if you find you need to customize certain parts of a CRUD action, but not necessarily the whole thing, you can always add methods as extension points. For example, let's say you needed to populate a select list for one particular entity, you might do something like:

public abstract class AdminController<TEntity> : Controller
    where TEntity : IEntity, class, new()
{
    ...

    public virtual ActionResult Create()
    {
        var entity = new TEntity();
        BeforeReturnView();
        return View(entity);
    }

    ...

    protected virtual void BeforeReturnView()
    {
    }

    ...

And then:

public class WidgetController : AdminController<Widget>
{
    ...

    protected override void BeforeReturnView()
    {
        ViewBag.MySelectList = new List<SelectListItem>
        {
            ...
        };
    }
}

In other words, you have a hook in your base action method that you override to just change that particular bit of functionality instead of having to override the whole action itself.

You can also take this farther to include things like view models, where you might expand your generic class definition to something like:

 public abstract class AdminController<TEntity, TEntityViewModel, TEntityCreateViewModel, TEntityUpdateViewModel>
     where TEntity : IEntity, class, new()
     where TEntityViewModel : class, new()
     ...

And then:

public class WidgetController : AdminController<Widget, WidgetViewModel, WidgetCreateViewModel, WidgetUpdateViewModel>
{
    ...
}

It all depends on what your application needs.

like image 175
Chris Pratt Avatar answered Sep 28 '22 08:09

Chris Pratt