Is it possible to have a generic web api that will support any model in your project?
class BaseApiController :ApiController
{
private IReposito
If you have predefined design-time classes, like one that generated from EF model or Code First then this is too complicated for your system. This is great if you don't have predefined classes (like in my project where data entity classes are generated at run-time).
My solution (not yet correctly implemented) was to create custom IHttpControllerSelector which selects my generic controller for all requests, there i can set controller's descriptor type to concrete from generic via reflection setting generic parameter depending on request path.
Also a good starting point is http://entityrepository.codeplex.com/ (I've found this somewhere here on stackoverflow)
public class GenericApiController<TEntity> : BaseApiController
where TEntity : class, new()
{
[HttpGet]
[Route("api/{Controller}/{id}")]
public IHttpActionResult Get(int id)
{
try
{
var entity = db.Set<TEntity>().Find(id);
if(entity==null)
{
return NotFound();
}
return Ok(entity);
}
catch(Exception ex)
{
return InternalServerError(ex);
}
}
[HttpGet]
[Route("api/{Controller}")]
public IHttpActionResult Post(TEntity entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
var primaryKeyValue = GetPrimaryKeyValue(entity);
var primaryKeyName = GetPrimaryKeyName(entity);
var existing = db.Set<TEntity>().Find(primaryKeyValue);
ReflectionHelper.Copy(entity, existing, primaryKeyName);
db.Entry<TEntity>(existing).State = EntityState.Modified;
db.SaveChanges();
return Ok(entity);
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
[HttpGet]
[Route("api/{Controller}/{id}")]
public IHttpActionResult Put(int id, TEntity entity)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var existing = db.Set<TEntity>().Find(id);
if (entity == null)
{
return NotFound();
}
ReflectionHelper.Copy(entity, existing);
db.SaveChanges();
return Ok(entity);
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
[HttpDelete]
[Route("api/{Controller}/{id}")]
public IHttpActionResult Delete(int id)
{
try
{
var entity = db.Set<TEntity>().Find(id);
if(entity==null)
{
return NotFound();
}
db.Set<TEntity>().Remove(entity);
db.SaveChanges();
return Ok();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
protected internal int GetPrimaryKeyValue(TEntity entity)
{
return ReflectionHelper.GetPrimaryKeyValue(entity);
}
protected internal string GetPrimaryKeyName(TEntity entity)
{
return ReflectionHelper.GetPrimaryKeyName(entity);
}
protected internal bool Exists(int id)
{
return db.Set<TEntity>().Find(id) != null;
}
}
Nothing wrong with this as long as you handle all the heavy lifting in your repositories. You may want to wrap/handle modelstate exceptions in your base controller.
I am actually doing something similar for a large project where users can define their own entities and APIs - ie: one user may want to have users and accounts while another may want to track cars and whatever else. They all use the same internal controller, but they each have their own endpoints.
Not sure how useful our code is to you since we don't use generics (each object is maintained as metadata and manipulated/passed back and forth as JObject dictionaries) but here is some code to give you an idea of what we are doing and maybe provide food for thought:
[POST("{primaryEntity}", RouteName = "PostPrimary")]
public async Task<HttpResponseMessage> CreatePrimary(string primaryEntity, JObject entity)
{
// first find out which params are necessary to accept the request based on the entity's mapped metadata type
OperationalParams paramsForRequest = GetOperationalParams(primaryEntity, DatasetOperationalEntityIntentIntentType.POST);
// map the passed values to the expected params and the intent that is in use
IDictionary<string, object> objValues = MapAndValidateProperties(paramsForRequest.EntityModel, paramsForRequest.IntentModel, entity);
// get the results back from the service and return the data to the client.
QueryResults results = await paramsForRequest.ClientService.CreatePrimaryEntity(paramsForRequest.EntityModel, objValues, entity, paramsForRequest.IntentModel);
return HttpResponseMessageFromQueryResults(primaryEntity, results);
}
I did this for a small project to get something up and running to demo to a client. Once I got into specifics of business rules, validation and other considerations, I ended up having to override the CRUD methods from my base class so it didn't pan out as a long term implementation.
I ran into problems with the routing, because not everything used an ID of the same type (I was working with an existing system). Some tables had int
primary keys, some had strings
and others had guids
.
I ended up having problems with that as well. In the end, while it seemed slick when I first did it, actually using it in a real world implementation proved to be a different matter and didn't put me any farther ahead at all.
What you doing is definitely possible as others said. But for repository dependencies, you should use dependency injection. My typical controller(Api or MVC) would be as follows.
public class PatientCategoryApiController : ApiController
{
private IEntityRepository<PatientCategory, short> m_Repository;
public PatientCategoryApiController(IEntityRepository<PatientCategory, short> repository)
{
if (repository == null)
throw new ArgumentNullException("entitiesContext is null");
m_Repository = repository;
}
}
This is the typical constructor injection pattern. You need to have a sound understanding of DI and containers like NInject or Autofac. If you dont know DI, then you have a long road ahead. But this is an excellent approach. Take a look at this book. https://www.manning.com/books/dependency-injection-in-dot-net
It is absolutely possible as said in previous answer. This is good approach and definetely good architecture. But i don't anderstand why your controllers are not public. May be it is your problem and because of it your code didn't work?