Multiple actions were found that match the request [duplicate] - c#

This question already has answers here:
Multiple actions were found that match the request in Web Api
(18 answers)
Closed 5 years ago.
I have read a lot of questions about routing and controllers, but I simply can't find what I'm looking for. I have this controller which has this structure:
Update: Included full class source.
public class LocationsController : ApiController
{
private readonly IUnitOfWork _unitOfWork;
public LocationsController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
// GET /api/locations/id
public Location Get(Guid id)
{
return this.QueryById<Location>(id, _unitOfWork);
}
// GET /api/locations
public IQueryable<Location> Get()
{
return this.Query<Location>(_unitOfWork);
}
// POST /api/locations
public HttpResponseMessage Post(Location location)
{
var id = _unitOfWork.CurrentSession.Save(location);
_unitOfWork.Commit();
var response = Request.CreateResponse<Location>(HttpStatusCode.Created, location);
response.Headers.Location = new Uri(Request.RequestUri, Url.Route(null, new { id }));
return response;
}
// PUT /api/locations
public Location Put(Location location)
{
var existingLocation = _unitOfWork.CurrentSession.Query<Location>().SingleOrDefault(x => x.Id == location.Id);
//check to ensure update can occur
if (existingLocation == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//merge detached entity into session
_unitOfWork.CurrentSession.Merge(location);
_unitOfWork.Commit();
return location;
}
// DELETE /api/locations/5
public HttpResponseMessage Delete(Guid id)
{
var existingLocation = _unitOfWork.CurrentSession.Query<Location>().SingleOrDefault(x => x.Id == id);
//check to ensure delete can occur
if (existingLocation != null)
{
_unitOfWork.CurrentSession.Delete(existingLocation);
_unitOfWork.Commit();
}
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
// rpc/locations
public HttpResponseMessage Dummy()
{
// I use it to generate some random data to fill the database in a easy fashion
Location location = new Location();
location.Latitude = RandomData.Number.GetRandomDouble(-90, 90);
location.Longitude = RandomData.Number.GetRandomDouble(-180, 180);
location.Name = RandomData.LoremIpsum.GetSentence(4, false);
var id = _unitOfWork.CurrentSession.Save(location);
_unitOfWork.Commit();
var response = Request.CreateResponse<Location>(HttpStatusCode.Created, location);
response.Headers.Location = new Uri(Request.RequestUri, Url.Route(null, new { id }));
return response;
}
}
And my routes definition (Global.asax):
public static void RegisterRoutes(RouteCollection routes)
{
// Default route
routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// A route that enables RPC requests
routes.MapHttpRoute(
name: "RpcApi",
routeTemplate: "rpc/{controller}/{action}",
defaults: new { action = "Get" }
);
}
So far if I hit the browser with:
[baseaddress]/locations/s0m3-gu1d-g0e5-hee5eeeee // It works
[baseaddress]/locations/ // Multiple results found
[baseaddress]/rpc/locations/dummy // It works
The strangest thing is that this used to work, until I messed up with my NuGet while performing some updates. What am I missing here?
Verbs starting with GET, POST, PUT or delete would be automapped to the first route and my dummy test method would be called via rpc, which would fall into the second route.
The error that is thrown is InvalidOperationException with message
Multiple actions were found that match the request:
System.Linq.IQueryable`1[Myproject.Domain.Location] Get() on type Myproject.Webservices.Controllers.LocationsController
System.Net.Http.HttpResponseMessage Dummy() on type Myproject.Webservices.Controllers.LocationsController
Any ideas?

The problem is in the order that the routes are loaded. If they are like this:
// A route that enables RPC requests
routes.MapHttpRoute(
name: "RpcApi",
routeTemplate: "rpc/{controller}/{action}",
defaults: new { action = "Get" }
);
// Default route
routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
It will work fine. First the request will be mapped against the RPC, then against the controller (thaty may have or not an Id).

We can also create custom action selector for Api Controllers as follows, so that that can freely work with complex types with traditional "GET,POST,PUT,DELETE":
class ApiActionSelector : IHttpActionSelector
{
private readonly IHttpActionSelector defaultSelector;
public ApiActionSelector(IHttpActionSelector defaultSelector)
{
this.defaultSelector = defaultSelector;
}
public ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor)
{
return defaultSelector.GetActionMapping(controllerDescriptor);
}
public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
// Get HttpMethod from current context
HttpMethod httpMethod = controllerContext.Request.Method;
// Set route values for API
controllerContext.RouteData.Values.Add("action", httpMethod.Method);
// Invoke Action
return defaultSelector.SelectAction(controllerContext);
}
}
And we can register the same in WebApiConfig as :
config.Services.Replace(typeof(IHttpActionSelector), new
ApiActionSelector(config.Services.GetActionSelector()));
May help users who are running this kind of issue.

Related

WebApi ActionName delete route not working

my project contains several WebApi controllers and each of them provides usually three actions: get(guid), post(data) and delete(guid),
A default route is described in the WebApiconfig for this requirement. (name: ControllerAndId)
Now I have to implement a controller which has to handle different post actions. For this requirement I tried to map another route with ActionNames. (name: ControllerAndActionAndId)
Since I have mapped the ControllerAndActionAndId route it is not possible to call the delete route of the "normal" controller (example: Contactscontroller). All routes are working except the delete routes.
StatusCode: 404, ReasonPhrase: 'Not Found'
There is an example of an usually ApiController:
public class ContactsController : ApiController
{
public IEnumerable<Contact> Get()
{
return GetContacts();
}
public HttpResponseMessage Post(Contact contact)
{
SaveContact(contact);
return Request.CreateResponse<Guid>(_code, contact.Id);
}
public void Delete(Guid id)
{
DeleteContact(id);
}
}
Controller with ActionName-Route:
public class AttachmentsController : ApiController
{
[HttpGet]
public Attachment Get(Guid attachmentId)
{
return GetAttachment(attachmentId);
}
[HttpPost]
[ActionName("save")]
public HttpResponseMessage Save(AttachmentSaveData saveData)
{
SaveAttachment(saveData);
}
[HttpPost]
[ActionName("remove")]
public HttpResponseMessage Remove(AttachmentDeleteData deleteData)
{
DeleteAttachment(deleteData);
}
}
WebApiConfig:
// Web API routes
config.MapHttpAttributeRoutes();
// Controller with ID
// To handle routes like `/api/VTRouting/route/1`
config.Routes.MapHttpRoute(
name: "ControllerAndActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new
{
id = RouteParameter.Optional,
action = RouteParameter.Optional
}
);
// Controller with ID
// To handle routes like `/api/VTRouting/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
ClientAction delete function:
private void Delete(string uri, int id)
{
using (HttpClient _client = new HttpClient())
{
_client.BaseAddress = BaseAddress;
string _url = string.Format("{0}/{1}", uri, id);
var _response = _client.DeleteAsync(_url).Result;
if (!_response.IsSuccessStatusCode)
{
throw new Exception();
}
}
}
I currently have no further idea how to solve this problem.
If you use Web API, you need add HTTP verb to action.
For example, your code must be as below:
public class ContactsController : ApiController
{
[HttpGet]
public IEnumerable<Contact> Get()
{
return GetContacts();
}
[HttpPost]
public HttpResponseMessage Post(Contact contact)
{
SaveContact(contact);
return Request.CreateResponse<Guid>(_code, contact.Id);
}
[HttpDelete]
public void Delete(Guid id)
{
DeleteContact(id);
}
}
Pay attention to Delete action.
If you use HttpDelete verb on action, you must send delete request from your client httpClient.DeleteAsync(...).
If you use HttpPost verb on action, you must send post request from your client httpClient.PostAsync(...).
AttachmentsController is similar to ContactsController.
I was focused to much on actions and routes of the controller.
But the solution was easy to find at client side:
private void Delete<T>(string uri, T value)
{
using (HttpClient _client = new HttpClient())
{
_client.BaseAddress = BaseAddress;
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string _url = string.Format("{0}/{1}", uri, value);
var _response = _client.DeleteAsync(_url).Result;
}
}
This solution requires only one route in WebApiConfig:
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
Soo easy.. thanks a lot !

web api not finding the Get method - 500 error

When i try to make a call to the web api to get a single vendor. It is return a 500 error.
I try to break inside the Get(Guid id) but it never gets inside of it.
I know it is getting to the controller but looks like it can't find the Get(Guid id) function.
Any ideas why? Am I missing something?
web api route
config.Routes.MapHttpRoute(
name: "Module",
routeTemplate: "api/module/{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional }
);
api controller
namespace App.WebUI.Controllers.api.Module
{
public class VendorsController : ApiController
{
private readonly UnitOfWork _repository = new UnitOfWork();
// GET api/Module/vendors
[HttpGet]
public HttpResponseMessage Get()
{
return all;
}
// GET api/Module/vendors/5
[HttpGet]
public HttpResponseMessage Get(Guid id)
{
return single
}
// POST api/Module/vendors
[HttpPost]
public HttpResponseMessage Post(Vendor vendor)
{
insert single
}
[HttpPut]
//PUT api/Module/Vendors
public HttpResponseMessage Put(Vendor vendor)
{
update single
}
}
}
This is what i had to do to make it work.
config.Routes.MapHttpRoute(
name: "Module",
routeTemplate: "api/module/{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional, action = "DefaultAction" }
);
Then at the default Action(GET(), GET(id), POST(), PUT(), DELETE()) I added the data annotation: [ActionName("DefaultAction")]

Routing in ASP NET Web API - For different versions of an API

I am reading about Attribute Routing in Web API 2 from here
The article says,
Here are some other patterns that attribute routing makes easy.
API versioning
In this example, “/api/v1/products” would be routed to a different controller than “/api/v2/products”.
/api/v1/products
/api/v2/products
How come?
EDIT: I would do this in Normal Routing:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v2/products",
defaults: new { controller = V2_Products }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/products",
defaults: new { controller = V1_Products }
);
}
}
Could anyone explain me how to do this in Attribute Routing way ? And how come using Attribute routing is easier and convenient for this example (according to the article) ?
There are many ways to implement versionning with attribute routing ; A really basic way is to use RoutePrefix attribute for each version of your ApiController
[RoutePrefix("v1")]
public class V1_ProductsController : ApiController
{
[Route("products")]
public IEnumerable<string> Get()
{
return new string[] { "v1-product1", "v1-product2" };
}
//....
}
[RoutePrefix("v2")]
public class V2_ProductsController : ApiController
{
[Route("products")]
public IEnumerable<string> Get()
{
return new string[] { "v2-product1", "v2-product2" };
}
//....
}
/v1/products goes to the first version of /v2/products goes to the second one.
You can do it by overriding DefaultHttpControllerSelector
there you override method to selectcontroller
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor controllerDescriptor = null;
IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
object apiVersion;
if (!routeData.Values.TryGetValue("Version", out apiVersion))
{
apiVersion = "1";
}
object controllerName;
if (!routeData.Values.TryGetValue("controller", out controllerName))
{
controllerName = string.Empty;
}
if (controllerName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
string newControllerName = String.Concat(controllerName.ToString(), "V", apiVersion);
if (controllers.TryGetValue(newControllerName, out controllerDescriptor))
{
return controllerDescriptor;
}
if (controllers.TryGetValue(controllerName.ToString(), out controllerDescriptor))
{
return controllerDescriptor;
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
Then you are adding routes webapiconfig
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and register controller selector in webapiconfig
config.Services.Replace(typeof(IHttpControllerSelector), new ApiVersioningSelector(config));
So from now if you name controller ProductsV1Controller it will reffer /api/v1/products. Also please note that my example also support routes without version so if v1 is not found it will try to check if ProductsController exists
PS. Code is update one bug was there :(
Another simple way is configuring your route as api/{folder}/{controller}/{action} where in to folder you can give name as V1 or V2.
A good way can be implementing your own Controller selector. You can use this link for more information.
The interface that Web API uses to select a controller is IHttpControllerSelector. The important method on this interface is SelectController, which selects a controller for a given HttpRequestMessage.

Trying to call a specific GET method on my WebAPI, but getting HTTP 404 Not Found

I'm making a call to http://localhost/AppTools.WebAPI/api/BulletinBoard/GetMessagesForApp/AppName, but it's returning a 404 error. I think this has to do with routing, but I'm not sure.
Here's the Web API method inside my BulletinBoard controller:
[HttpGet]
public HttpResponseMessage GetMessagesForApp(string id)
{
// get current, valid messages
var messages = (from i in db.BulletinBoards
where i.AppId == id &&
DateTime.Today >= i.DisplayFrom &&
DateTime.Today <= i.DisplayTo &&
i.IsActive == true
select new
{
Message = i.Message,
IntervalId = i.IntervalId,
Interval = i.Interval.IntervalDescription,
Timeout = i.Timout,
})
.ToList();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, messages);
return response;
}
Here's my RouteConfig.cs:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The standard Get() and Get(int id) work fine, I didn't change the method name or signatures. Get() returns a complete list of records, Get(int id) returns a specific record. I want GetMessagesByApp(string id) to return a list of records specific to a certain AppName. Can you tell why this isn't working?
Here's my RouteConfig.cs:
The RouteConfig.cs file is used to define the routes for your ASP.NET MVC controllers. Those have absolutely nothing to do with the routes used by your Web API controllers. They are defined in the WebApiConfig.cs file.
So make sure you have declared your routes in the proper place:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ApiWithActionName",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Notice that I have added a custom route before the default one which will allow you to achieve the desired url pattern.
And then you could have the following controller action which will work fine:
// GET /api/controllername
// GET /api/controllername/get
[HttpGet]
public HttpResponseMessage Get()
{
...
}
// GET /api/controllername/get/123
[HttpGet]
public HttpResponseMessage Get(int id)
{
...
}
// GET /api/controllername/GetMessagesForApp/abc
[HttpGet]
public HttpResponseMessage GetMessagesForApp(string id)
{
...
}

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

In Web API I had a class of similar structure:
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) { ... }
}
Since we could map individual methods, it was very simple to get the right request at the right place. For similar class which had only a single GET method but also had an Object parameter, I successfully used IActionValueBinder. However, in the case described above I get the following error:
Multiple actions were found that match the request:
SomeValue GetItems(CustomParam parameter) on type SomeType
SomeValue GetChildItems(CustomParam parameter, SomeObject parent) on type SomeType
I am trying to approach this problem by overriding the ExecuteAsync method of ApiController but with no luck so far. Any advice on this issue?
Edit: I forgot to mention that now I am trying to move this code on ASP.NET Web API which has a different approach to routing. The question is, how do I make the code work on 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.
Go from this:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
To this:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
Hence, you can now specify which action (method) you want to send your HTTP request to.
posting to "http://localhost:8383/api/Command/PostCreateUser" invokes:
public bool PostCreateUser(CreateUserCommand command)
{
//* ... *//
return true;
}
and posting to "http://localhost:8383/api/Command/PostMakeBooking" invokes:
public bool PostMakeBooking(MakeBookingCommand command)
{
//* ... *//
return true;
}
I tried this in a self hosted WEB API service application and it works like a charm :)
I find attributes to be cleaner to use than manually adding them via code. Here is a simple example.
[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);
}
}
You also need this in your 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 }
);
Some Good Links
http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
This one explains routing better.
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
In VS 2019, this works with ease:
[Route("api/[controller]/[action]")] //above the controller class
And in the code:
[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();
}
You can have multiple gets like above mentioned.
You need to define further routes in global.asax.cs like this:
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 }
);
With the newer Web Api 2 it has become easier to have multiple get methods.
If the parameter passed to the GET methods are different enough for the attribute routing system to distinguish their types as is the case with ints and Guids you can specify the expected type in the [Route...] attribute
For example -
[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}";
}
}
For more details about this approach, see here http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/
Another options is to give the GET methods different routes.
[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";
}
}
See here for more details - http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/
In ASP.NET Core 2.0 you can add Route attribute to the controller:
[Route("api/[controller]/[action]")]
public class SomeController : Controller
{
public SomeValue GetItems(CustomParam parameter) { ... }
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
The lazy/hurry alternative (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;}
Calling them :
localhost:5000/api/controllername/method1-42
"hello42"
localhost:5000/api/controllername/method2-99
"world99"
I was trying to use Web Api 2 attribute routing to allow for multiple Get methods, and I had incorporated the helpful suggestions from previous answers, but in the Controller I had only decorated the "special" method (example):
[Route( "special/{id}" )]
public IHttpActionResult GetSomethingSpecial( string id ) {
...without also also placing a [RoutePrefix] at the top of the Controller:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
I was getting errors stating that no Route was found matching the submitted URI. Once I had both the [Route] decorating the method as well as [RoutePrefix] decorating the Controller as a whole, it worked.
By default [Route("api/[controller]") will generated by .Net Core/Asp.Net Web API.You need to modify little bit,just add [Action] like [Route("api/[controller]/[action]")].
I have mentioned a dummy solution:
// 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");
}
}
I am not sure if u have found the answer, but I did this and it works
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";
}
Now in 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 }
);
Have you tried switching over to WebInvokeAttribute and setting the Method to "GET"?
I believe I had a similar problem and switched to explicitly telling which Method (GET/PUT/POST/DELETE) is expected on most, if not all, my methods.
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) { ... }
}
The WebGet should handle it but I've seen it have some issues with multiple Get much less multiple Get of the same return type.
[Edit: none of this is valid with the sunset of WCF WebAPI and the migration to ASP.Net WebAPI on the MVC stack]
**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) { ... }
}
Specifying the base path in the [Route] attribute and then adding to the base path in the [HttpGet] worked for me. You can try:
[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;
}
//....
Took me a while to figure since I didn't want to use [Route] multiple times.
None of the above examples worked for my personal needs. The below is what I ended up doing.
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;
}
To use the above in your route use:
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) });
What happens is the constraint kind of fakes in the method so that this route will only match the default GET, POST, PUT and DELETE methods. The "true" there says we want to check for a match of the items in array. If it were false you'd be saying exclude those in the strYou can then use routes above this default method like:
config.Routes.MapHttpRoute("GetStatus", "{controller}/status/{status}", new { action = "GetStatus" });
In the above it is essentially looking for the following URL => http://www.domain.com/Account/Status/Active or something like that.
Beyond the above I'm not sure I'd get too crazy. At the end of the day it should be per resource. But I do see a need to map friendly urls for various reasons. I'm feeling pretty certain as Web Api evolves there will be some sort of provision. If time I'll build a more permanent solution and post.
Couldn't make any of the above routing solutions work -- some of the syntax seems to have changed and I'm still new to MVC -- in a pinch though I put together this really awful (and simple) hack which will get me by for now -- note, this replaces the "public MyObject GetMyObjects(long id)" method -- we change "id"'s type to a string, and change the return type to 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;
}
If you have multiple Action within same file then pass the same argument e.g. Id to all Action. This is because action only can identify Id, So instead of giving any name to argument only declare Id like this.
[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 }
);
Simple Alternative
Just use a query string.
Routing
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Controller
public class TestController : ApiController
{
public IEnumerable<SomeViewModel> Get()
{
}
public SomeViewModel GetById(int objectId)
{
}
}
Requests
GET /Test
GET /Test?objectId=1
Note
Keep in mind that the query string param should not be "id" or whatever the parameter is in the configured route.
The concept of multiple methods in a single asp.net web api controller makes it easier to have more than 1 method in code.
I was able to implement following the steps in the above solutions and came up with this final code
In the WebApiConfig.cs ,set up the following Route config, in this order
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.Routes.MapHttpRoute(
name: "DefaultApiAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.MapHttpAttributeRoutes();
}
}
Then in your controller reference the [HttpGet] for GET or [HttpPost] for POST with [ActionName] see sample code below
namespace WebRESTApi.Controllers
{
//[RoutePrefix("api/Test")]
public class TestController : ApiController
{
[HttpGet]
[ActionName("AllEmailWithDisplayname")]
public string AllEmailWithDisplayname()
{
return "values";
}
[HttpPost]
[ActionName("Authenticate")]
// POST: api/Authenticate
public object Authenticate([FromBody()] object Loginvalues)
{
return true;
}
[HttpPost]
[ActionName("ShowCredential")]
// POST: api/Showcredential
public object Showcredential([FromBody()] object Loginvalues)
{
return "Username: "
}
}
}
you can then consume the different methods via client or postman using the format
http://url/api/controller/actionname
Modify the WebApiConfig and add at the end another Routes.MapHttpRoute like this:
config.Routes.MapHttpRoute(
name: "ServiceApi",
routeTemplate: "api/Service/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then create a controller like this:
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";
}
}
This is how I solved it. I hope it will help someone.

Categories

Resources