【问题标题】:Generic Web Api controller to support any model支持任何模型的通用 Web Api 控制器
【发布时间】:2012-08-22 16:19:53
【问题描述】:

是否可以有一个通用的 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 方法?我可以使用这个基类为我做这项工作吗?

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

【问题讨论】:

  • 嗨,我知道这是一个老问题,但你能解释一下你是如何设法调用你的通用操作方法的吗?看起来您无法仅使用路由规则来完成此操作。我将不胜感激。

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


【解决方案1】:

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

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

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

【讨论】:

  • 你可以使用一个接口调用它IIdentifier&lt;T&gt; 来表示不同类型的 id 。
  • 没错,但事后看来,我仍然认为尝试先发制人地将 CRUD 操作重构为一个通用控制器并没有从中受益。它在开发后期引起了太多的麻烦。
  • 如果你是新手,你没有理由不拥有所有相同类型的“Id”。
【解决方案2】:

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

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

【讨论】:

  • 那么为什么它不起作用?基本上大多数时候,我们所做的只是对所有领域模型使用相同的操作并重复多次。
  • 你问是否有可能,所以我回答了 :) 诚然,我认为我没有回答剩下的问题,我可能会删除我的答案...
  • 我会给你 +1 不删除你的答案:)
  • 是的,存储库是通用的。这已经是行业的标准。并且有 10 种方法可以使其通用
  • @DarthVader 几年后,所谓的行业将通用存储库视为反模式。 :-)
【解决方案3】:
 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;
    }
}

【讨论】:

  • 您已将[HttpGet] 应用于您的Post 和Put 方法。这是故意的吗?
  • 我们如何从浏览器或邮递员那里调用这个控制器?
  • 想知道反射助手是什么...
【解决方案4】:

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

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

}

【讨论】:

  • 祝你好运 :) 看起来很复杂。我会避免所有这些。:)
  • 对于你正在做的事情,是的,有点矫枉过正;)。但是,上面的代码允许多个客户帐户在每个帐户中使用多个应用程序来使用一个通用控制器管理多个实体(实际上我们已将其分解为每个操作类型的控制器),因此当您考虑它必须解决的问题时并不那么复杂.如果我们可以使用上面的代码并支持数千个响应时间低于 100 毫秒的客户端,那么您就可以开始了;)。无论如何,只是试图展示另一种实现它的方式作为思考的食物。
【解决方案5】:

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

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

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

【讨论】:

    【解决方案6】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-19
      • 2023-03-13
      • 2018-09-02
      • 1970-01-01
      • 2020-10-10
      • 2017-09-20
      • 2015-10-11
      • 2015-01-20
      相关资源
      最近更新 更多