ASP.NET Web API中具有多个GET方法的单个控制器


167

在Web API中,我有一类类似的结构:

public class SomeController : ApiController
{
    [WebGet(UriTemplate = "{itemSource}/Items")]
    public SomeValue GetItems(CustomParam parameter) { ... }

    [WebGet(UriTemplate = "{itemSource}/Items/{parent}")]
    public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}

由于我们可以映射各个方法,因此在正确的位置获得正确的请求非常简单。对于只有一个GET方法但也有一个Object参数的类似类,我成功使用IActionValueBinder。但是,在上述情况下,出现以下错误:

Multiple actions were found that match the request: 

SomeValue GetItems(CustomParam parameter) on type SomeType

SomeValue GetChildItems(CustomParam parameter, SomeObject parent) on type SomeType

我正在尝试通过覆盖ExecuteAsync方法来解决此问题,ApiController但到目前为止还没有运气。关于这个问题有什么建议吗?

编辑:我忘了提一下,现在我正在尝试在ASP.NET Web API上移动此代码,而ASP.NET Web API具有不同的路由方法。问题是,如何使代码在ASP.NET Web API上工作?


1
您是否还有{parent}作为RouteParameter.Optional?
安东尼·斯科特

是的,我做到了。也许我使用IActionValueBinder的方式错误,因为对于诸如int id之类的类型(如演示中),它确实可以正常工作。
paulius_l 2012年

抱歉,我应该更清楚了。我本以为将其作为可选项意味着它与Item路由以及子项路由都匹配,这将解释您所看到的错误消息。
安东尼·斯科特

如果下面的方法(使用多个路由)是否违反正确的REST规则,我们目前正在讨论。我认为这很好。我的同事认为这不好。对此有何评论?
雷米2012年

当我开始阅读有关REST的文章时,我通​​常对此表示反对。我仍然不确定这是否是正确的方法,但有时它更方便或更用户友好,因此稍微弯曲一下规则可能还不错。只要它能解决特定问题。自从我发布此问题以来已经过去了6个月,从那时起,我们对使用此方法没有任何遗憾。
paulius_l 2012年

Answers:


249

这是我发现支持额外的GET方法以及支持常规REST方法的最佳方法。将以下路由添加到WebApiConfig:

routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });
routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new {action = "Post"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Post)});

我通过下面的测试类验证了此解决方案。我能够在下面的控制器中成功命中每种方法:

public class TestController : ApiController
{
    public string Get()
    {
        return string.Empty;
    }

    public string Get(int id)
    {
        return string.Empty;
    }

    public string GetAll()
    {
        return string.Empty;
    }

    public void Post([FromBody]string value)
    {
    }

    public void Put(int id, [FromBody]string value)
    {
    }

    public void Delete(int id)
    {
    }
}

我确认它支持以下请求:

GET /Test
GET /Test/1
GET /Test/GetAll
POST /Test
PUT /Test/1
DELETE /Test/1

注意,如果多余的GET操作不是以“ Get”开头,则可能需要向该方法添加HttpGet属性。


4
这是一个很好的答案,它对另一个相关的问题也有很大帮助。谢谢!!
2012年

4
尝试过-似乎不起作用。路由全部随机映射到GetBlah(long id)方法。:(
BrainSlugs83 2013年

1
@ BrainSlugs83:取决于顺序。您将想要添加(到“ withId”方法中),constraints: new{id=@"\d+"}
Eric Falsken 2013年

4
如何添加另一种方法-Get(int id,string name)?...失败了
Anil Purswani

1
我不得不添加这样的额外路由routes.MapHttpRoute("DefaultApiPut", "Api/{controller}", new {action = "Put"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Put)});我的Put方法,否则这是给我404
赛义德·阿里·塔基

57

从此:

config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
            new { id = RouteParameter.Optional });

对此:

config.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
            new { id = RouteParameter.Optional });

因此,您现在可以指定要将HTTP请求发送到的操作(方法)。

发布到“ http:// localhost:8383 / api / Command / PostCreateUser”会调用:

public bool PostCreateUser(CreateUserCommand command)
{
    //* ... *//
    return true;
}

并发布到“ http:// localhost:8383 / api / Command / PostMakeBooking”会调用:

public bool PostMakeBooking(MakeBookingCommand command)
{
    //* ... *//
    return true;
}

我在一个自托管的WEB API服务应用程序中尝试了此方法,它的工作原理很像:)


8
感谢您的帮助。我想补充一点,如果您以Get,Post等开头您的方法名称,那么您的请求将基于所使用的HTTP动词映射到那些方法。但你也可以命名你的方法什么,然后用它们装点[HttpGet][HttpPost]等属性映射动词的方法。
indot_brad 2013年

敬请见我的问题
-Moeez

@DikaArtaKarunia没问题,很高兴我的答案在6年后仍然适用:D
uggeh

31

与通过代码手动添加属性相比,我发现使用属性更干净。这是一个简单的例子。

[RoutePrefix("api/example")]
public class ExampleController : ApiController
{
    [HttpGet]
    [Route("get1/{param1}")] //   /api/example/get1/1?param2=4
    public IHttpActionResult Get(int param1, int param2)
    {
        Object example = null;
        return Ok(example);
    }

}

您还需要在webapiconfig中使用它

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

config.Routes.MapHttpRoute(
    name: "ActionApi",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

一些良好的链接 http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api 这一个可以更好地说明路由。 http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api


3
我还需要添加config.MapHttpAttributeRoutes();WebApiConfig.cs,并且GlobalConfiguration.Configuration.EnsureInitialized();WebApiApplication.Application_Start()方法的末尾使路由属性起作用。
Ergwun '17

@Ergwun这个评论对我很有帮助。只是要添加到它,config.MapHttpAttributeRoutes();需要路由映射之前出现(如前config.Routes.MappHttpRoute(...
菲利普·斯特拉特福

11

您需要像这样在global.asax.cs中定义其他路由:

routes.MapHttpRoute(
    name: "Api with action",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

5
是的,的确如此,但最好能看到这些路线的示例。这将使该答案对社区更有价值。(您会从我这里得到+1 :)
阿兰·穆赫兰

你可以在这里阅读的例子- stackoverflow.com/questions/11407267/...
汤姆Kerkhove的

2
实际的解决方案会更好。
这么多地精2012年

6

使用更新的Web Api 2,拥有多个get方法变得更加容易。

如果传递给GET方法的参数足够不同,以使属性路由系统能够像ints和Guids 一样区分其类型,则可以在[Route...]属性中指定期望的类型

例如 -

[RoutePrefix("api/values")]
public class ValuesController : ApiController
{

    // GET api/values/7
    [Route("{id:int}")]
    public string Get(int id)
    {
       return $"You entered an int - {id}";
    }

    // GET api/values/AAC1FB7B-978B-4C39-A90D-271A031BFE5D
    [Route("{id:Guid}")]
    public string Get(Guid id)
    {
       return $"You entered a GUID - {id}";
    }
} 

有关此方法的更多详细信息,请参见此处http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/

另一种选择是为GET方法提供不同的路线。

    [RoutePrefix("api/values")]
    public class ValuesController : ApiController
    {
        public string Get()
        {
            return "simple get";
        }

        [Route("geta")]
        public string GetA()
        {
            return "A";
        }

        [Route("getb")]
        public string GetB()
        {
            return "B";
        }
   }

详情请参阅这里-http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/


5

在ASP.NET Core 2.0中,可以将Route属性添加到控制器:

[Route("api/[controller]/[action]")]
public class SomeController : Controller
{
    public SomeValue GetItems(CustomParam parameter) { ... }

    public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}

4

我试图使用Web Api 2属性路由来允许多个Get方法,并且我并入了先前答案中的有用建议,但是在Controller中,我仅修饰了“特殊”方法(示例):

[Route( "special/{id}" )]
public IHttpActionResult GetSomethingSpecial( string id ) {

...也没有在控制器的顶部放置[RoutePrefix]:

[RoutePrefix("api/values")]
public class ValuesController : ApiController

我收到错误消息,指出找不到与提交的URI匹配的Route。当我同时使用[Route]装饰该方法和[RoutePrefix]装饰整个Controller时,它就起作用了。


3

我不确定您是否找到了答案,但是我做到了,而且有效

public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

// GET /api/values/5
public string Get(int id)
{
    return "value";
}

// GET /api/values/5
[HttpGet]
public string GetByFamily()
{
    return "Family value";
}

现在在global.asx中

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapHttpRoute(
    name: "DefaultApi2",
    routeTemplate: "api/{controller}/{action}",
    defaults: new { id = RouteParameter.Optional }
);

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

3

您是否尝试过切换到WebInvokeAttribute并将“方法”设置为“ GET”?

我相信我有一个类似的问题,并转而明确地告诉我,即使不是全部,大多数方法也应该使用哪种方法(GET / PUT / POST / DELETE)。

public class SomeController : ApiController
{
    [WebInvoke(UriTemplate = "{itemSource}/Items"), Method="GET"]
    public SomeValue GetItems(CustomParam parameter) { ... }

    [WebInvoke(UriTemplate = "{itemSource}/Items/{parent}", Method = "GET")]
    public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}

WebGet 应该处理它,但是我已经看到它在使用多个Get少一些具有相同返回类型的多个Get方面存在一些问题。

[编辑:在WCF WebAPI的日落以及MVC堆栈上向ASP.Net WebAPI的迁移中,所有这些都不有效]


1
抱歉,我忘记提及自WCF Web API停用以来,我正在将代码移至ASP.NET Web API。我编辑了帖子。谢谢。
paulius_l 2012年

2
**Add Route function to direct the routine what you want**
    public class SomeController : ApiController
    {
        [HttpGet()]
        [Route("GetItems")]
        public SomeValue GetItems(CustomParam parameter) { ... }

        [HttpGet()]
        [Route("GetChildItems")]
        public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
    }

欢迎使用Stack Overflow!请编辑您的答案,以包括对您的代码的解释,并说明其与此处的其他十四个答案的不同之处。这个问题已经有八年历史了,已经有了一个可以接受的解释清楚的答案。如果没有在解释你的,它可能会得到downvoted或删除。有了该解释将有助于证明您的答案在此问题上的位置。
Das_Geek

1
就这个清晰/基本的问题而言,我个人(我知道SO的建议是什么)我个人宁愿有一个纯代码答复。我不想阅读很多解释,我想快速制作有用的功能软件。+1
MemeDeveloper

2

懒惰/匆忙的替代方案(Dotnet Core 2.2):

[HttpGet("method1-{item}")]
public string Method1(var item) { 
return "hello" + item;}

[HttpGet("method2-{item}")]
public string Method2(var item) { 
return "world" + item;}

打电话给他们:

本地主机:5000 / api / controllername / method1-42

“ hello42”

本地主机:5000 / api / controllername / method2-99

“ world99”


0

以上示例均无法满足我的个人需求。以下是我最终要做的事情。

 public class ContainsConstraint : IHttpRouteConstraint
{       
    public string[] array { get; set; }
    public bool match { get; set; }

    /// <summary>
    /// Check if param contains any of values listed in array.
    /// </summary>
    /// <param name="param">The param to test.</param>
    /// <param name="array">The items to compare against.</param>
    /// <param name="match">Whether we are matching or NOT matching.</param>
    public ContainsConstraint(string[] array, bool match)
    {

        this.array = array;
        this.match = match;
    }

    public bool Match(System.Net.Http.HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
    {
        if (values == null) // shouldn't ever hit this.                   
            return true;

        if (!values.ContainsKey(parameterName)) // make sure the parameter is there.
            return true;

        if (string.IsNullOrEmpty(values[parameterName].ToString())) // if the param key is empty in this case "action" add the method so it doesn't hit other methods like "GetStatus"
            values[parameterName] = request.Method.ToString();

        bool contains = array.Contains(values[parameterName]); // this is an extension but all we are doing here is check if string array contains value you can create exten like this or use LINQ or whatever u like.

        if (contains == match) // checking if we want it to match or we don't want it to match
            return true;
        return false;             

    }

要在路线中使用以上内容,请使用:

config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { action = RouteParameter.Optional, id = RouteParameter.Optional}, new { action = new ContainsConstraint( new string[] { "GET", "PUT", "DELETE", "POST" }, true) });

发生的是方法中的约束种类伪造品,因此此路由将仅与默认的GET,POST,PUT和DELETE方法匹配。此处的“ true”表示我们要检查数组中各项的匹配情况。如果为假,则表示要排除str中的那些。然后,您可以在此默认方法之上使用路由,例如:

config.Routes.MapHttpRoute("GetStatus", "{controller}/status/{status}", new { action = "GetStatus" });

在上面,它实际上是在寻找以下URL => http://www.domain.com/Account/Status/Active或类似的东西。

除了以上所述,我不确定我会变得太疯狂了。在一天结束时,应该针对每个资源。但是我确实出于各种原因需要映射友好的URL。随着Web Api的发展,我可以肯定会有一些规定。如果有时间的话,我将建立一个更永久的解决方案并发布。


您可以new System.Web.Http.Routing.HttpMethodConstraint(HttpMethod.Get, HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete) 改用。
abatishchev 2014年

0

上面的任何路由解决方案都无法正常工作-有些语法似乎已经更改,并且我仍然对MVC还是陌生的-尽管我整理了一下这个非常糟糕(简单)的技巧,但我仍会感到困惑到目前为止,请注意,这将替换“ public MyObject GetMyObjects(long id)”方法-我们将“ id”的类型更改为字符串,并将返回类型更改为object。

// GET api/MyObjects/5
// GET api/MyObjects/function
public object GetMyObjects(string id)
{
    id = (id ?? "").Trim();

    // Check to see if "id" is equal to a "command" we support
    // and return alternate data.

    if (string.Equals(id, "count", StringComparison.OrdinalIgnoreCase))
    {
        return db.MyObjects.LongCount();
    }

    // We now return you back to your regularly scheduled
    // web service handler (more or less)

    var myObject = db.MyObjects.Find(long.Parse(id));
    if (myObject == null)
    {
        throw new HttpResponseException
        (
            Request.CreateResponse(HttpStatusCode.NotFound)
        );
    }

    return myObject;
}

0

如果您在同一文件中有多个动作,则将相同的参数(例如,Id)传递给所有动作。这是因为操作只能识别Id,所以不能给参数指定任何名称,而只能这样声明Id。


[httpget]
[ActionName("firstAction")] firstAction(string Id)
{.....
.....
}
[httpget]
[ActionName("secondAction")] secondAction(Int Id)
{.....
.....
}
//Now go to webroute.config file under App-start folder and add following
routes.MapHttpRoute(
name: "firstAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

routes.MapHttpRoute(
name: "secondAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Url在浏览器中如何查看每个功能?
Si8

0

简单的选择

只需使用查询字符串即可。

路由

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

控制者

public class TestController : ApiController
{
    public IEnumerable<SomeViewModel> Get()
    {
    }

    public SomeViewModel GetById(int objectId)
    {
    }
}

要求

GET /Test
GET /Test?objectId=1

注意

请记住,查询字符串参数不应为“ id”或已配置路由中的任何参数。


-1

修改WebApiConfig并在末尾添加另一个Routes.MapHttpRoute,如下所示:

config.Routes.MapHttpRoute(
                name: "ServiceApi",
                routeTemplate: "api/Service/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

然后创建一个像这样的控制器:

public class ServiceController : ApiController
{
        [HttpGet]
        public string Get(int id)
        {
            return "object of id id";
        }
        [HttpGet]
        public IQueryable<DropDownModel> DropDowEmpresa()
        {
            return db.Empresa.Where(x => x.Activo == true).Select(y =>
                  new DropDownModel
                  {
                      Id = y.Id,
                      Value = y.Nombre,
                  });
        }

        [HttpGet]
        public IQueryable<DropDownModel> DropDowTipoContacto()
        {
            return db.TipoContacto.Select(y =>
                  new DropDownModel
                  {
                      Id = y.Id,
                      Value = y.Nombre,
                  });
        }

        [HttpGet]
        public string FindProductsByName()
        {
            return "FindProductsByName";
        }
}

这就是我解决的方法。希望对您有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.