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 !
Related
I have the following URL:
http://localhost/api/values/100/some+string+here
In the WebAPI app ValuesController, I have this:
[HttpGet]
[Route("api/values/{p1}/{p2}")]
public HttpResponseMessage Get (string p1, string p2) {
...
}
For the caller, it never hits the web api. Instead, it comes back with a 404.
Any idea what is wrong?
You are using Attribute Routing in ASP.NET Web API 2. Make sure you configure your web api to use Attribute routing with MapHttpAttributeRoutes.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Next make sure you defined your controller properly
public class ValuesController : ApiController {
//GET api/values/100/some-string-here
[HttpGet]
[Route("api/values/{p1}/{p2}")]
public HttpResponseMessage Get (string p1, string p2) {
...
}
}
You could even use RoutePrefix
[RoutePrefix("api/values")]
public class ValuesController : ApiController {
//GET api/values/100/some-string-here
[HttpGet]
[Route("{p1}/{p2}")]
public HttpResponseMessage Get (string p1, string p2) {
...
}
}
Also if like in your example you want the first parameter to be an integer. then you can use a route constraint and update method.
[RoutePrefix("api/values")]
public class ValuesController : ApiController {
//GET api/values/100/some-string-here
[HttpGet]
[Route("{p1:int}/{p2}")]
public HttpResponseMessage Get (int p1, string p2) {
...
}
}
UPDATE:
Created an integration test for the Values Controller and was able to confirm that the action was called
[TestMethod]
public async Task HttpClient_Should_Get_OKStatus_From_Action_With_Multiple_Parameters() {
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
using (var server = new HttpServer(config)) {
var client = new HttpClient(server);
string url = "http://localhost/api/values/100/some+string+here";
using (var response = await client.GetAsync(url)) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
}
I am trying to write a self hosted WebAPI server. I want all routes to go to a single controller. This controller can pick out the controller part of the url and use this to decide an appropriate response.
I have the following route configuration:
_configuration.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: string.Concat("api/Home", "/{id}"),
defaults: new { id = RouteParameter.Optional, controllerName="Home" }
);
My controller class is called "HomeController". I'm trying to redirect all URLs to it.
Here is the code for HomeController. For now I have commented out the calls to external logic (remote controller). It should just be returning a string on the Get action.
public class HomeController : ApiController
{
private IExternalController<string> remoteController;
public HomeController()
{
remoteController = GlobalKernelConfiguration.GetStandardKernel().Get<IExternalController<string>>();
}
public string Get()
{
return "HELLO FROM INTERNAL"; //remoteController.Get();
}
public string Get(int id)
{
return remoteController.Get(id);
}
public void Delete(int id)
{
remoteController.Delete(id);
}
public void Post(string value)
{
remoteController.Post(value);
}
public void Put(int id, string value)
{
remoteController.Put(id, value);
}
}
I would expect http://localhost:9000/api/[AnythingHere] to route to the home controller but I get the following error when trying the following url: http://localhost:9000/api/Home
{"Message":"No HTTP resource was found that matches the request URI 'http://loca
lhost:9000/api/Home'.","MessageDetail":"No route providing a controller name was
found to match request URI 'http://localhost:9000/api/Home'"}
As #CodeCaster suggested in the comments the problem was caused by not using the correct parameter in the routing options.
This is what I had before
_configuration.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: string.Concat("api/Home", "/{id}"),
defaults: new { id = RouteParameter.Optional, controllerName="Home" }
);
this is what I have now:
public static void AddControllerRoute(string controllerName)
{
_configuration.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: string.Concat("api/Home", "/{id}"),
defaults: new { id = RouteParameter.Optional, controller ="Home" }
);
}
notice that the defaults parameter was changed and now uses "controller" instead of "controllerName" this solved the problem and it's now working.
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")]
I'm trying to add a special route to the default WebApi sample:
The regular ones are
/api/values (retrieves all values)
/api/values/{id} (retrieves a specific value (by numeric id))
Now I want to add this api:
/api/values/special
The previous api (/api/values/{id}) should serve all requests with a numeric id, but /api/values/special should serve requests that call that specific url.
So far I got this for routing:
config.Routes.MapHttpRoute("SpecialValues", "api/values/special", new { controller = "values", action = "SpecialValues" });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And this for implementation:
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
// GET api/values/special
public IEnumerable<string> SpecialValues()
{
return new string[] { "special1", "special2" };
}
}
But it will render: The requested resource does not support http method 'GET'.
If I call /api/values/special and I add [HttpGet] to the SpecialValues method it will work
but /api/values will stop working saying that there are multiple matching actions.
The changes to WebApiConfig is not needed. Attach a Route attribute and a HttpGet attribute. You can read more about it here.
[HttpGet]
[Route("api/{controller}/special")]
public IEnumerable<string> SpecialValues()
{
return new string[] { "special1", "special2" };
}
WebApiConfig -
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
yes, as mentioned above, attribute based routing is the only way to go here...
this post may also interests you
Overload web api action method based on parameter type
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)
{
...
}