C# 支持任何模型的通用 Web Api 控制器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12077361/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generic Web Api controller to support any model
提问by DarthVader
Is it possible to have a generic web api that will support any model in your project?
是否有可能有一个通用的 web api 来支持您项目中的任何模型?
class BaseApiController<T> :ApiController
{
private IRepository<T> _repository;
// inject repository
public virtual IEnumerable<T> GetAll()
{
return _repository.GetAll();
}
public virtual T Get(int id)
{
return _repositry.Get(id);
}
public virtual void Post(T item)
{
_repository.Save(item);
}
// etc...
}
class FooApiController : BaseApiController<Foo>
{
//..
}
class BarApiController : BaseApiController<Bar>
{
//..
}
Would this be a good approach?
这会是一个好方法吗?
After all, i m just repeating the CRUD methods ? Can i use this base class to do the work for me?
毕竟,我只是在重复 CRUD 方法?我可以使用这个基类来为我做这项工作吗?
is this OK? would you do this? any better ideas?
这个可以吗?你会这样做吗?有什么更好的想法吗?
采纳答案by Nick Albrecht
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.
我为一个小项目做了这个,以启动并运行向客户演示。一旦我了解了业务规则、验证和其他注意事项的细节,我最终不得不从我的基类中覆盖 CRUD 方法,因此它没有成为长期实现。
I ran into problems with the routing, because not everything used an IDof the same type (I was working with an existingsystem). Some tables had intprimary keys, some had stringsand others had guids.
我遇到了路由问题,因为并非所有东西都使用相同类型的ID(我正在使用现有系统)。有些表有int主键,有些有strings,有些有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.
我最终也遇到了问题。最后,虽然当我第一次这样做时它看起来很光滑,但在现实世界的实现中实际使用它被证明是另一回事,并没有让我走得更远。
回答by eouw0o83hf
It's definitely possible. I've never had a reason to do that before, but if it works for your situation, it should be good.
绝对有可能。我以前从未有过这样做的理由,但如果它适用于您的情况,那应该很好。
If all of your models can be saved and retrieved in the exact same way, maybe they should just all be in the same controller instead though?
如果您的所有模型都可以以完全相同的方式保存和检索,也许它们应该都在同一个控制器中?
回答by Kirill Bestemyanov
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?
正如之前的答案中所说,这是绝对可能的。这是一个很好的方法,也是一个很好的架构。但我不明白为什么你的控制器不公开。可能是您的问题,因此您的代码不起作用?
回答by AlexGad
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.
我实际上正在为一个大型项目做类似的事情,用户可以在其中定义自己的实体和 API - 即:一个用户可能想要拥有用户和帐户,而另一个用户可能想要跟踪汽车和其他任何东西。它们都使用相同的内部控制器,但它们都有自己的端点。
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:
不确定我们的代码对您有多大用处,因为我们不使用泛型(每个对象都作为元数据维护并作为 JObject 字典来回操作/传递)但这里有一些代码可以让您了解我们正在做什么和也许提供思考的食物:
[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);
}
回答by Alexey
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).
如果您有预定义的设计时类,例如从 EF 模型或 Code First 生成的类,那么这对您的系统来说太复杂了。如果您没有预定义的类(例如在我的项目中在运行时生成数据实体类),这很好。
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.
我的解决方案(尚未正确实现)是创建自定义 IHttpControllerSelector,它为所有请求选择我的通用控制器,在那里我可以根据请求路径通过反射设置通用参数将控制器的描述符类型从通用设置为具体。
Also a good starting point is http://entityrepository.codeplex.com/(I've found this somewhere here on stackoverflow)
还有一个很好的起点是http://entityrepository.codeplex.com/(我在 stackoverflow 的某个地方找到了这个)
回答by VivekDev
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.
正如其他人所说,你所做的绝对是可能的。但是对于存储库依赖项,您应该使用依赖项注入。我的典型控制器(Api 或 MVC)如下。
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
这是典型的构造函数注入模式。您需要对 DI 和容器(如 NInject 或 Autofac)有充分的了解。如果你不知道DI,那么你还有很长的路要走。但这是一个很好的方法。看看这本书。https://www.manning.com/books/dependency-injection-in-dot-net
回答by CourtenayDavison
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;
}
}

