通用Web Api控制器,支持任何模型

时间:2012-08-22 16:19:54

标签: c# generics asp.net-web-api

是否可以使用支持项目中任何模型的通用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>
{
   //..
}

这会是一个好方法吗?

毕竟,我只是重复CRUD方法?我可以使用这个基类为我做这项工作吗?

这样好吗?你会这样做吗?任何更好的想法?

7 个答案:

答案 0 :(得分:23)

我为一个小项目做了这个,以获得一些东西并运行到客户端演示。一旦我了解了业务规则,验证和其他注意事项的细节,我最终不得不从我的基类覆盖CRUD方法,因此它不会成为长期实现的平台。

我遇到了路由问题,因为并非所有内容都使用相同类型的 ID (我使用的是现有的系统)。有些表格有int个主键,有些表格有strings,有些表格有guids

我最终也遇到了问题。最后,虽然当我第一次使用它时看起来很光滑,但实际上在现实世界中使用它被证明是另一回事,并没有让我更进一步。

答案 1 :(得分:5)

这绝对有可能。我以前从来没有理由这样做,但如果它适合你的情况,它应该是好的。

如果可以用完全相同的方式保存和检索所有模型,可能它们应该只是在同一个控制器中而不是?

答案 2 :(得分:2)

 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;
    }
}

答案 3 :(得分:1)

只要您处理存储库中的所有繁重工作,这没有任何问题。您可能希望在基本控制器中包装/处理modelstate异常。

我实际上正在为一个大型项目做类似的事情,用户可以在其中定义自己的实体和API - 即:一个用户可能希望拥有用户和帐户,而另一个用户可能想要跟踪汽车和其他任何内容。它们都使用相同的内部控制器,但它们各自都有自己的端点。

不确定我们的代码对你有多大用处,因为我们不使用泛型(每个对象都作为元数据进行维护,并作为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);

}

答案 4 :(得分:1)

如果您有预定义的设计时类,例如从EF模型或Code First生成的类,那么这对您的系统来说太复杂了。如果您没有预定义的类(例如在我的项目中,在运行时生成数据实体类),这很好。

我的解决方案(尚未正确实现)是创建自定义IHttpControllerSelector,为所有请求选择我的通用控制器,在那里我可以根据请求路径将控制器的描述符类型设置为具体的通用反射设置通用参数。

另一个好的起点是http://entityrepository.codeplex.com/(我在stackoverflow上找到了这个地方)

答案 5 :(得分:1)

正如其他人所说,你所做的事情肯定是可能的。但是对于存储库依赖性,您应该使用依赖注入。我的典型控制器(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;
    }
}

这是典型的构造函数注入模式。您需要对DI和容器(如NInject或Autofac)有充分的了解。如果你不了解DI,那么你前面的路很长。但这是一种很好的方法。看看这本书。 https://www.manning.com/books/dependency-injection-in-dot-net

答案 6 :(得分:0)

如前面的答案中所说的那样绝对可能。这是一种很好的方法,也是一种很好的架构。但我不明白为什么你的控制器不公开。可能是你的问题,因为它你的代码不起作用?