23

是否有可能拥有一个支持项目中任何模型的通用 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 方法?我可以使用这个基类为我做这项工作吗?

这个可以吗?你会这样做吗?有更好的主意吗?

4

6 回答 6

27

我为一个小项目做了这个,以便启动并运行一些东西来演示给客户。一旦我了解了业务规则、验证和其他注意事项的细节,我最终不得不从我的基类中重写 CRUD 方法,因此它没有成为长期实现。

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

我最终也遇到了问题。最后,虽然当我第一次这样做时它看起来很流畅,但在现实世界的实现中实际使用它被证明是另一回事,并且根本没有让我走得更远。

于 2012-08-22T17:44:27.003 回答
5

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

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

于 2012-08-22T16:21:46.853 回答
4
 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;
    }
}
于 2018-09-05T09:14:49.827 回答
1

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

实际上,我正在为一个用户可以定义自己的实体和 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);

}
于 2012-08-23T01:43:42.710 回答
1

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

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

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

于 2013-12-17T09:03:48.590 回答
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

于 2015-12-22T13:05:15.087 回答