Routing with Multiple Get Methods in ASP.NET Web API

Single controller with multiple GET methods in ASP.NET Web API

This is the best way I have found to support extra GET methods and support the normal REST methods as well. Add the following routes to your 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)});

I verified this solution with the test class below. I was able to successfully hit each method in my controller below:

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

I verified that it supports the following requests:

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

Note That if your extra GET actions do not begin with 'Get' you may want to add an HttpGet attribute to the method.

Routing with multiple Get methods in ASP.NET Web API

From here Routing in Asp.net Mvc 4 and Web Api

Darin Dimitrov has posted a very good answer which is working for me.

It says...

You could have a couple of routes:

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ApiById",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = @"^[0-9]+$" }
);

config.Routes.MapHttpRoute(
name: "ApiByName",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: null,
constraints: new { name = @"^[a-z]+$" }
);

config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = "Get" }
);
}
}

How to use multiple GET action in a WebApi?

Either change template to routeTemplate: "api/{controller}/{action}/{studentName}", and leave methods as it

public static void Register(HttpConfiguration config) {
config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{studentName}"
);
}

OR

leave template as is, ie: "api/{controller}/{action}/{id}" and change method parameter to (string id,.......)

[HttpGet]
public List<Student> UpdateEmail(string id, string Email) { ... }

OR

You could also forego convention-based routing and use attribute routing

[RoutePrefix("api/EmployeeApi")]
public class EmployeeApiController : ApiController
{
//GET api/EmployeeApi
[HttpGet]
[Route("")]
public List<Student> GetAllStudents() { ... }

//GET api/EmployeeApi/EmailChange/foo/foo@email.com
[HttpGet]
[Route("EmailChange/{studentName}/{email}")]
public List<Student> EmailChange(string studentName, string email) { ... }

//GET api/EmployeeApi/AddressChange/foo/China
[HttpGet]
[Route("AddressChange/{studentName}/{address}")]
public List<Student> AddressChange(string studentName, string Address) { ... }
}

How to put multiple GET methods in Web API2 controller?

Enable attribute routing in WebApiConfig.cs before convention-based routes.

config.MapHttpAttributeRoutes();

Next update controller to use routing attributes. (note the route prefix)

[RoutePrefix("api/NCT_ProcessSettings")]
public class NCT_ProcessSettingsController : ApiController {

//GET api/NCT_ProcessSettings
[HttpGet]
[Route("")]
public IEnumerable<Process_Settings> Get() { ... }

//GET api/NCT_ProcessSettings/5
[HttpGet]
[Route("{id:int}")]
public HttpResponseMessage Get(int id) { ... }

//GET api/NCT_ProcessSettings/GetGlobalSettings
[HttpGet]
[Route("GetGlobalSettings")]
public IEnumerable<NCT_Process_Settings> GetGlobalSettings() { ... }

}

Read up more documentation here Attribute Routing in ASP.NET Web API 2

Multiple GET methods in a Web API Controller

I would need to see the calling code and route config to be certain but my guess is that you may be using restful routing. Switch to using a query string with named parameters and all of your methods should work:

http://api/yourcontroller?id=something

http://api/yourcontroller?mode=somethingelse

http://api/yourcontroller?department=adepartment&location=alocation

The default route template configuration understands id. You may see this in the App_Start folder in the WebApiConfig static class method Register.

This is the default:

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

Based on this default, the action method parameter (id) is set as part of the route data which is why the second action method in the controller code you listed above would work. You would not be able to use template routing or attribute routing to set the value in get for multiple single paramter get methods in the same controller because it would create an ambiguous condition.

You may want to review the details on parameter binding at the following link. Binding can be a little tricky at times in Web Api 2 because the model binders and formatters included by default do a lot of work behind the scenes.

http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

ASP.Net Web API routing for multiple GET methods

Add Route attribute to your methods, like

[Route("MethodA")]
[Route("MethodB")]

Multiple get methods in a .Net WebApi OData Controller

OData Routes do not support overloads OOTB which is effectively what you are describing here, two GET Collection methods, but you want one of them to have the same route as the GET Resource method.

The first question is why do you want to have the same route pointing to two different methods, especially when one of the routes returns a single resource, and the other will return a collection.

Built-In OData Routing Conventions

The built in conventions will route known URLs to methods that match the expected signatures. All other routes need to be registered with the Edm Model either as discrete Functions or Actions. You could also implement your own routes

The arguments are more or less the same if you had intended the Get(string key) to return a single item or a collection. There is still a clash between a default route and 2 different methods to handle the request for that route. There is a direct duplicate isssue on SO for this type of issue: OData route exception

In OData we make a distinction between the key of a resource, that fits into the default ~/Controller(key) route and effectively all other routes. For all other non-default routes, we simply need to declare them in the OData EdmModel to make them valid

There are other possible solutions like Implementing your own version of the DefaultODataPathHandler as explained here or a custom IODataRoutingConvention as explained here.

However I find it best to try and stick to the OData standard conventions as much as possible, so when faced with a routing issue where there can be defined a simple mapping between the OData Conventional way and our business need, AND you want to support this syntax in a global sense, then you can use a simple url rewrite module.

  1. Declare your custom method endpoint as a separate Function in the Edm Model.
  2. Create a Url Rewrite to map between legacy routes and the OData routes.

Declare a Custom Function in the Edm Model:

To access your custom function via the Edm Model, there are two changes we need to make:

  1. change the method name to something other than Get. In this example we will use the term Search

     [HttpGet]
    [EnableQuery]
    public IQueryable<Product> Search(string key)
    {
    return _service.GetByFilter(key);
    }
  2. modify your builder fluent notation:

    ODataModelBuilder builder = new ODataConventionModelBuilder();
    var products = builder.EntitySet<Product>("Products");
    products.EntityType.Collection.Function(nameof(ProductsController.Search))
    .ReturnsCollectionFromEntitySet<Facility>("Products")
    .Parameter<string>("key");
    config.Routes.MapODataServiceRoute("odata", "api/odata", builder.GetEdmModel());

    NOTE: The name of the route must match the name of the method for this configuration to work, to enforce this nameof(ProductsController.Search) was used however, "Search" is all that is necessary.

The final URL that will match this template:

GET: ~/api/odata/Products/Search(key='Foo')

Url Rewrite Module

public class ODataConventionUrlRewriter : OwinMiddleware
{
/// <summary>
/// Create a Url Rewriter to manipulate incoming OData requests and rewrite or redirect to the correct request syntax
/// </summary>
/// <param name="next"></param>
public ODataConventionUrlRewriter(OwinMiddleware next)
: base(next)
{
}
/// <summary>
/// Process the incoming request, if it matches a known path, rewrite accordingly or redirect.
/// </summary>
/// <param name="context">OWin Request context to process</param>
/// <returns>Chains the next processor if the url is OK or rewritten, otherwise the response is to redirect</returns>
public override async Task Invoke(IOwinContext context)
{
// Match ANY /Controller(NonNumeric)
// Rewrite to /Controller/Search(key='NonNumeric')
var regex = new System.Text.RegularExpressions.Regex(@"\(([^\d=\'\(]+)\)$");
match = regex.Match(context.Request.Path.Value);
if (match != null && match.Success)
{
// We have to use redirect here, we can't affect the query inflight
context.Response.Redirect($"{context.Request.Uri.GetLeftPart(UriPartial.Authority)}{regex.Replace(context.Request.Path.Value, $"/Search(key='{match.Groups[1].Value}')")}");
}
else
await Next.Invoke(context);
}
}

Finally, in the StartUp.cs or where your OWin context is configured add this module into the pipeline before the OData config:

public void Configuration(IAppBuilder app)
{
...
// Rewrite URLs
app.Use(typeof(ODataConventionUrlRewriter));
...
// Register routes
config.MapHttpAttributeRoutes();

ODataModelBuilder builder = new ODataConventionModelBuilder();
var products = builder.EntitySet<Product>("Products");
products.EntityType.Collection.Function(nameof(ProductsController.Search))
.ReturnsCollectionFromEntitySet<Facility>("Products")
.Parameter<string>("key");
config.Routes.MapODataServiceRoute("odata", "api/odata", builder.GetEdmModel());

...
// Start Web API
app.UseWebApi(config);

}

Now the final Url that is supported for our custom Function is this:

GET: ~/api/odata/Products(Foo)

Try to use the conventions when you can, you have chosen OData for a reason, if you do use a rewrite module, try to be as specific in your conditional logic as you can be to avoid breaking other standard routes that might be in use.

I try to reserve rewrite solutions for common legacy query routes.



Related Topics



Leave a reply



Submit