当类是通用的时,找不到Web API控制器

时间:2017-10-24 17:33:53

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

我试图在我的Web API中使用通用控制器。我目前无法实现的目标是从我的前端传入一个会说出typeId的对象。基于这种类型我将使用工厂注入通用接口的正确类实现。我相信我的工厂,接口和服务是正确的,但出于某种原因,当我向API添加Generic时,我得到的是404.它没有通用,只是一种测试方法。我正在使用autofac进行IoC注册。

API控制器:

public class ListItemsController<T> : ApiControllerBase
{
    private readonly IListItemsService<T> _service;

    public ListItemsController(int listItemTypeId)
    {
        _service = ListItemsFactory<T>.InitializeService(listItemTypeId);
    }

    [HttpGet]
    [Route("{listItemTypeId: int}")]
    public IEnumerable<T> GetAll()
    {
        return _service.GetAll();
    }

    [HttpGet]
    [Route("test")]
    public IHttpActionResult Test()
    {
        return Ok();
    }
}

厂:

public class ListItemsFactory<T>
{
    public ListItemsFactory(IPrimaryContext context) : base()
    {
    }

    public static IListItemsService<T> InitializeService(int listItemType)
    {
        switch (listItemType)
        {
            case 1: return (IListItemsService<T>)
                new FloorTypeService(new PrimaryContext());
            default: return null;
        }
    }
}

接口:

public interface IListItemsService<T>
{
    IEnumerable<T> GetAll();
    void Save(T obj);
    T GetById(int id);
    void Delete(int id);
}

错误:

  

未找到与请求URI匹配的HTTP资源&#39; http://localhost:9000/api/v1/listitems/test&#39;。未找到与名为&#39; listitems&#39;。

的控制器匹配的类型

我不确定我在这里失踪了什么。我使用的是路由属性,但这是我的API配置:

private static void SetupRoutes(HttpConfiguration config)
{
    config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
    config.Routes.MapHttpRoute("DefaultApi", "api/v{version}/{controller}/{id}",
        new { id = RouteParameter.Optional });
}

2 个答案:

答案 0 :(得分:2)

除了解析类型并尝试映射到正确的Controller之外,您还可以为每个Type创建一个Controller,它继承自GenericController。然后您不必复制代码,但每个Type都有一个Controller,您可以通过RouteAttribute路由到。

public class ListItemsController<T> : ApiControllerBase
{
    //Properties/Fields should be protected to can be accessed from InstanceController.
    protected readonly IListItemsService<T> _service;
    // I think listItemTypeId is not necessary, if generic-type T is used?
    public ListItemsController()
    {
        _service = ListItemsFactory<T>.InitializeService();
    }

    [HttpGet] // No need for RouteAttribute, because it will be in InstanceController.
    public IEnumerable<T> GetAll()
    {
        return _service.GetAll();
    }

    [HttpGet]
    [Route("test")] // This can rest here, because you want to use it.
    public IHttpActionResult Test()
    {
        return Ok();
    }



}

实现的InstanceController可能如下所示:

[RoutePrefix("api/{controller}")]
public class FloorItemsController  ListItemsController<Floor> 
{
    // delegate the Constructor-call to base()
    public ListItemsController()
        :base()
    {

    }

     // No need to reimplement Methods.
}

应该将RouteConfiguration设置回默认值,因为为此设置了RouteAttributes。

答案 1 :(得分:1)

基本上,您需要做的是使用自定义实现替换控制器激活器。

首先,创建一个实现IHttpControllerSelector接口的类。在创建自定义激活器之前,请查看at this link以了解您应该注意的一些事项。在底部有一个指向自定义实现的代码示例的链接。

现在,这取决于您的规则实际是什么,但出于性能原因,您应该尝试构建一个始终将相同控制器名称映射到通用控制器类型的相同封闭类型的解决方案。您案例的简单实现如下所示:

public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
    IHttpRouteData routeData = request.GetRouteData();
    if (routeData == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    //get the generyc type of your controller
    var genericControllerType = typeof(ListItemsController<>);

    // Get the route value from which you'll get the type argument from your controller.
    string typeParameterArgument = GetRouteVariable<string>(routeData, 'SomeKeyUsedToDecideTheClosedType');
    Type typeArgument = //Somehow infer the generic type argument,  form your route value based on your needs
    Type[] typeArgs = { typeof(typeArgument) };
    //obtain the closed generyc type
    var t = genericControllerType.MakeGenericType(typeArgs);            

    //configuration must be an instance of HttpConfiguration, most likeley you would inject this on the activator constructor on the config phase
    new HttpControllerDescriptor(_configuration, t.Name, t); 

}

最后,在您的ApiConfig课程中,您需要添加以下内容:

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), 
        new MyOwnActivatior());

我现在无法测试此代码,因此可能需要进行一些调整,但希望这会引导您朝着正确的方向前进。请注意上面粘贴的链接,因为在实现自定义激活器之前,您需要考虑一些重要的注意事项。另外,请检查该帖子上链接的代码示例,了解如何实施GetControllerMapping方法