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

时间:2012-02-29 13:12:06

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

在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的{​​{1}}方法来解决此问题,但到目前为止还没有运气。有关这个问题的任何建议吗?

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

19 个答案:

答案 0 :(得分:239)

这是我发现支持额外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属性。

答案 1 :(得分:54)

从这里开始:

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服务应用程序中尝试了这个,它就像一个魅力:)

答案 2 :(得分:29)

我发现使用的属性比通过代码手动添加更清晰。这是一个简单的例子。

[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 :(得分: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 }
);

答案 4 :(得分:5)

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

如果传递给GET方法的参数与属性路由系统区分其类型的情况不同,就像intGuid一样,您可以指定预期输入[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 :(得分: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 }
);

答案 6 :(得分:3)

您是否尝试过切换到WebInvokeAttribute并将Method设置为“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 应该处理它但我已经看到它有多个问题多次获取相同返回类型的多次获取。

[编辑:对于WCF WebAPI的落后以及在MVC堆栈上迁移到ASP.Net WebAPI,这一切都无效]

答案 7 :(得分:3)

在VS 2019中,这很容易工作:

[Route("api/[controller]/[action]")] //above the controller class

在代码中:

[HttpGet]
[ActionName("GetSample1")]
public Ilist<Sample1> GetSample1()
{
    return getSample1();
}
[HttpGet]
[ActionName("GetSample2")]
public Ilist<Sample2> GetSample2()
{
    return getSample2();
}
[HttpGet]
[ActionName("GetSample3")]
public Ilist<Sample3> GetSample3()
{
    return getSample3();
}
[HttpGet]
[ActionName("GetSample4")]
public Ilist<Sample4> GetSample4()
{
    return getSample4();
}

您可以像上面提到的那样获取多个信息。

答案 8 :(得分:3)

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

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

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

答案 9 :(得分:2)

我试图使用Web Api 2属性路由来允许多个Get方法,并且我已经结合了以前答案的有用建议,但在Controller中我只装饰了“特殊”方法(示例):

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

...还没有将[RoutePrefix]放在Controller的顶部:

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

我收到错误,指出找不到与提交的URI匹配的路由。一旦我将[Route]装饰方法和[RoutePrefix]装饰控制器作为一个整体,它就起作用了。

答案 10 :(得分:1)

默认情况下[.Route(“ api / [controller]”)将由.Net Core / Asp.Net Web API生成。您需要进行一些修改,只需添加[Action],如[Route(“ api / [controller]” ]/[行动]”)]。 我提到了一个虚拟解决方案:

// Default generated controller
//
[Route("api/[controller]")
public class myApiController : Controller
{
    [HttpGet]
    public string GetInfo()
    {
        return "Information";
    }
}

//
//A little change would do the magic
//

[Route("api/[controller]/[action]")]
public class ServicesController : Controller
{
    [HttpGet]
    [ActionName("Get01")]
    public string Get01()
    {
        return "GET 1";
    }

    [HttpGet]
    [ActionName("Get02")]
    public string Get02()
    {
        return "Get 2";
    }
    
    [HttpPost]
    [ActionName("Post01")]
    public HttpResponseMessage Post01(MyCustomModel01 model)
    {
        if (!ModelState.IsValid)
            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        
        //.. DO Something ..
        return Request.CreateResonse(HttpStatusCode.OK, "Optional Message");
    }
    
    [HttpPost]
    [ActionName("Post02")]
    public HttpResponseMessage Post02(MyCustomModel02 model)
    {
        if (!ModelState.IsValid)
            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        
        //.. DO Something ..
        return Request.CreateResonse(HttpStatusCode.OK, "Optional Message");
    }


}

答案 11 :(得分:0)

如果您在同一个文件中有多个Action,则传递相同的参数,例如Id to all Action。这是因为action只能识别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 }
);

答案 12 :(得分: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

注意

请记住,查询字符串参数不应该是&#34; id&#34;或者配置的路由中的参数。

答案 13 :(得分:0)

**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) { ... }
    }

答案 14 :(得分:0)

懒惰/匆忙的替代方案(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;}

给他们打电话:

  

localhost:5000 / api / controllername / method1-42

“ hello42”

  

localhost:5000 / api / controllername / method2-99

“ world99”

答案 15 :(得分:0)

[Route]属性中指定基本路径,然后将其添加到[HttpGet]中的基本路径为我工作。您可以尝试:

    [Route("api/TestApi")]      //this will be the base path
    public class TestController : ApiController
    {
        [HttpGet]  //example call: 'api/TestApi'
        public string Get()
        {
            return string.Empty;
        }
    
        [HttpGet("{id}")]  //example call: 'api/TestApi/4'
        public string GetById(int id) //method name won't matter
        {
            return string.Empty;
        }
    
        //....

花点时间计算一下,因为我不想多次使用[Route]

答案 16 :(得分: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;
}

答案 17 :(得分: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方法。那里的“真实”说我们要检查数组中项目的匹配。如果它是假的你会说在strWou中排除那些然后可以使用这个默认方法之上的路由,如:

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

在上面它主要是寻找以下URL =&gt; http://www.domain.com/Account/Status/Active或类似的东西。

除了上述内容,我不确定自己会不会太疯狂。在一天结束时,它应该是每个资源。但我确实认为有必要出于各种原因来映射友好的网址。随着Web Api的发展,我会非常肯定会有某种规定。如果时间我会建立一个更永久的解决方案并发布。

答案 18 :(得分:-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";
        }
}

这就是我解决它的方式。我希望它会帮助别人。