Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to (auto) generate a webapi controller for MVC + Entity or generically query all types

Being new to MVC4 + Entity + WebAPI I was frustrated that in order to simply have GET, POST controller handlers and custom mappings to data models.

What I guess I'm asking is there a generation tool to, for instance make your controller class from your data class so I can just do simple GET commands right from the data?

What is the approach to make a generic RESTful API so commands could be made as such

GET api/1.0/{genericdatatype}/{id}

where the generic data type can be any model and no specific controllers? Say I don't need PUT (handled through the MVC app) so I don't really need POST validation etc.

like image 562
FlavorScape Avatar asked Jan 23 '13 21:01

FlavorScape


3 Answers

There is a tool/package called MVC Scaffolding that will build out your controllers based on your models.

http://mvcscaffolding.codeplex.com/

http://blog.stevensanderson.com/2011/01/13/scaffold-your-aspnet-mvc-3-project-with-the-mvcscaffolding-package/

As for the generic piece, that's a very long and difficult process that would take a good deal of time. I'd love to see that as well if someone has a good answer for it.

EDIT: I have spent some additional time looking into the generic piece. It looks like someone else had a similar thought and asked a nearly identical question here: Generic Web Api controller to support any model.

At the end of the day, they voice some excellent concerns in that conversation. How do you handle different types of IDs? Would they always need to be strings and then parsed out? And once you get into more business rules I suspect that you'd have a very complicated business layer behind the controller. That said, I'd say you're better off sticking with straight scaffolding rather than building out a generic API. Sure, it could probably be done, but at what cost in time and crazy parsing code behind the API? Just my thoughts.

like image 96
David L Avatar answered Oct 17 '22 15:10

David L


Probably this answer is too late for the original poster, but perhaps others can use this.

I just cooked up this generic base api controller for MVC 5 code first CRUD operations:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using My.Models;
using System.Data.Entity.Validation;

namespace My.Controllers.Api
{
  public abstract class CrudController<TEntity>
    : ApiController where TEntity : class
  {
    private readonly MyContext _db = new MyContext();
    private readonly DbSet<TEntity> _dbSet;

    protected CrudController(Func<MyContext, DbSet<TEntity>> dbSet)
    {
      _db = new EtlContext();
      _dbSet = dbSet(_db);
    }

    public IEnumerable<TEntity> Get()
    {
      return _dbSet.AsEnumerable();
    }

    public HttpResponseMessage Post(TEntity entity)
    {
      try
      {
        if (!ModelState.IsValid)
          return Request.CreateResponse(HttpStatusCode.BadRequest);

        _db.Entry(entity).State = EntityState.Added;

        _db.SaveChanges();

        return Request.CreateResponse(HttpStatusCode.Created);
      }
      catch (DbEntityValidationException)
      {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
      }
      catch (DbUpdateException)
      {
        return Request.CreateResponse(HttpStatusCode.Conflict);
      }
    }

    public HttpResponseMessage Put(TEntity entity)
    {
      try
      {
        if (!ModelState.IsValid)
          return Request.CreateResponse(HttpStatusCode.BadRequest);

        _db.Entry(entity).State = EntityState.Modified;

        _db.SaveChanges();

        return Request.CreateResponse(HttpStatusCode.OK);
      }
      catch (DbEntityValidationException)
      {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
      }
      catch (DbUpdateConcurrencyException)
      {
        return Request.CreateResponse(HttpStatusCode.NotFound);
      }
      catch (DbUpdateException)
      {
        return Request.CreateResponse(HttpStatusCode.Conflict);
      }
    }

    public HttpResponseMessage Delete(TEntity entity)
    {
      try
      {
        _db.Entry(entity).State = EntityState.Deleted;

        _db.SaveChanges();

        return Request.CreateResponse(HttpStatusCode.OK);
      }
      catch (DbUpdateConcurrencyException)
      {
        return Request.CreateResponse(HttpStatusCode.NotFound);
      }
    }

    protected override void Dispose(bool disposing)
    {
      _db.Dispose();
      base.Dispose(disposing);
    }
  }
}

I still had to make subclasses for each DbSet like this:

public class CustomersController 
  : CrudController<Customer>
{
  public CustomersController()
    : base(db => db.Customers)
  {}
}

public class ProductsController 
  : CrudController<Product>
{
  public ProductsController()
    : base(db => db.Products)
  {}
}

to make this routing work:

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}",
 );
like image 29
Dave Avatar answered Oct 17 '22 17:10

Dave


I'm offering an alternative myself that our team has adopted. OData. Its a quick way to build your initial RESTful template, with more emphasis on querying than CRUD, it makes data available without having to build out endpoints yourself, manually. I'm doing the front end now for a project that has OData on the Web API server and this works brilliantly to free up the back end devs to work on more pressing matters than collections endpoints.

It works with Entity framework, too.

like image 31
FlavorScape Avatar answered Oct 17 '22 17:10

FlavorScape