Web Api route with multiple params 404ing - c#

I have the following route in my WebApiConfig
config.Routes.MapHttpRoute(
name: "PaginateMessages",
routeTemplate: "api/Message/PaginateMessages/{conversationId}/{lastMessageId}",
defaults: new { controller = "Message", action = "PaginateMessages", conversationId = RouteParameter.Optional, lastMessageId = RouteParameter.Optional }
);
And I have the corresponding action in the Message controller:
[HttpGet]
public async Task<List<MessageDTO>> PaginateMessages(int conversationId, int lastMessageId)
{
return null;
}
However when I try to hit the endpoint I get a 404:
http://localhost:60162/api/Message/PaginateMessages/71/150
Is it not possible to have multiple route parameters as shown above?

You will need to add {controller} and {action} in route template.
config.Routes.MapHttpRoute(
name: "PaginateMessages",
routeTemplate: "api/{controller}/{action}/{conversationId}/{lastMessageId}",
defaults: new {controller = "Conversation", action = "GetConversation", conversationId = RouteParameter.Optional, lastMessageId = RouteParameter.Optional}
);
Alternative way in Web API 2 is to remove custom route configuration in WebApiConfig, and use Route attribute.
public class MessageController : ApiController
{
[HttpGet]
[Route("api/Message/PaginateMessages/{conversationId}/{lastMessageId}")]
public async Task<List<MessageDTO>> PaginateMessages(
int conversationId, int lastMessageId)
{
return null;
}
}

Related

web api routing with action and id

I am new in using web api and I am trying to call a specific method in my controller.
I have
global.asax
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
the WebApiConfig class with these routings
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
action="DefaultAction",
id = RouteParameter.Optional
}
);
and my controller
[HttpGet]
public HttpResponseMessage GetPatSummary(string PatId)
{
PatientSummary Pat = new PatientSummary();
HttpResponseMessage Response = new HttpResponseMessage();
string yourJson = Pat.GetPatient(PatId);
Response = this.Request.CreateResponse(HttpStatusCode.OK, yourJson);
return Response;
}
[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public IHttpActionResult GetPatient(int id)
{
Object Obj = new object();
if (Obj!=null)
{
return NotFound();
}
return Ok(Obj);
}
the URL I am using is
http://localhost/mdmwapi/api/MdmwPatientController/GetPatSummary/sgdgdgddhdhd1334254
but I get this error
A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
I am getting nut :-)
Use attribute routing
[HttpGet]
[Route("api/MdmwPatientController/GetPatSummary/{PatId}")]
public HttpResponseMessage GetPatSummary(string PatId)
{
PatientSummary Pat = new PatientSummary();
HttpResponseMessage Response = new HttpResponseMessage();
string yourJson = Pat.GetPatient(PatId);
Response = this.Request.CreateResponse(HttpStatusCode.OK, yourJson);
return Response;
}
then you can request it using
http://localhost/api/MdmwPatientController/GetPatSummary/yourpatid
also you can map any url using attribute routing this way
the solution is a combination of a new route and a mistake in the URL
the new routes are now these ones
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
config.Routes.MapHttpRoute(
name: "ApiMethodCall",
routeTemplate: "api/{controller}/{action}/{PatId}",
defaults: new
{
controller= "MdmwPatient",
action= "GetPatSummary"
}
);
and the error in the URL was that although the controller class name is MdmwPatientController I have to omit the "controller" suffix when calling from the test client, so the correct url is
http://localhost/mdmwapi/api/MdmwPatient/GetPatSummary/sgdgdgddhdhd1334254

How define a WebApi route to access method

I'd like have access to a method other than "GET", "PUSH", "PATCH", ....
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Employee",
routeTemplate: "api/employee/{employeeid}",
defaults: new { controller = "employee", employeeid = RouteParameter.Optional }
);
//for test : not work
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
//JSON Formatting
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
I have access to the employee controller :
[RoutePrefix("api/employee")]
public class EmployeeController : ApiController
{
public HttpResponseMessage Get() { }
public HttpResponseMessage Get(int employeeid) {}
public HttpResponseMessage Post([FromBody] EmployeeModel model){}
[HttpPut]
[HttpPatch]
public HttpResponseMessage Patch([FromBody] EmployeeModel model){}
[Route("initialisation")]
public HttpResponseMessage Initialisation() {}
}
I have access without any problem :
http://localhost/employee
http://localhost/employee/1
I'd like have access to the "Initialisation" method :
http://localhost/employee/initialisation
I added the route "DefaultApi" but when I try I get this error :
{
"$id": "1",
"message": "The request is invalid.",
"messageDetail": "The parameters dictionary contains a null entry for parameter 'employeeid' of non-nullable type 'System.Int32'
for method 'System.Net.Http.HttpResponseMessage Get(Int32)' in 'Pme.WebApi.Controllers.EmployeeController'.
An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}
Thanks,
Try the following:
Change
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
To
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{employeeid}",
defaults: new { action = "get", employeeid= RouteParameter.Optional }
);
The only change the name of last ID parameter from "id" to "employeeid"
I think this should work out. You can try modifying your WebApiConfig like this :
config.Routes.MapHttpRoute(
name: "DefaultApi",
//routeTemplate: "api/{controller}/{id}",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
This way, you keep it open for both, the controller name and the action name to be something dynamic and try hitting the URL that would be generated as per this format.
UPDATE :
Comment the "Employee" route code. Just keep the "DefaultApi" route active. Now try to hit this URL :
http://localhost:1955/api/Employee/Create/parameters
You obviously need to keep the host name as per yours and the parameter too.
Hope this helps.
You need to register attribute routing in your WebApiConfig
config.MapHttpAttributeRoutes();
See Attribute Routing in ASP.NET Web API 2
Alternatively you can specify exactly where your route should go
config.Routes.MapHttpRoute("Initialisation", "api/employee/initialisation",
new {controller = "employee", action = "initialisation"});

Nested functions in controller

New to MVC and the API template, familiar with C#.
Plain new WebAPI project with a simple controller. Url path like /api/Clients/1, is there a way to sort of nest controllers? Or execute a function in the same controller by following a url path like this: /api/Clients/1/Sysinfo/typeOf?
I have a model Client which contains properties which are sysinfo items. /api/Clients/1 returns all the properties of an object Client with the Id of 1. I want only specific items returned with /api/Clients/1/Sysinfo/RAM for example.
#Joachim Rosskopf
I have tried that approach. It result in a 404. /clients/1/ works. /clients/1/sysinfo does not work.
Using the following routes:
routes.MapRoute(
name: "Sysinfo",
url: "Clients/{id}/Sysinfo/{type}",
defaults: new { controller = "Sysinfo", type = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Created a new controller SysinfoController:
public class SysinfoController : ApiController
{
public Sysinfo GetAllSysinfoItems()
{
return new Sysinfo { Id = 1, RAM = "1GB" };
}
public IHttpActionResult GetSysinfoByType(int id)
{
return Ok();
}
}
You have to adjust your routing configuration and add special entries for the child routes. It is important to add the most specific route first:
routes.MapHttpRoute(
name: "SysInfoApi",
routeTemplate: "api/Clients/{id}/Sysinfo/{param}",
defaults: new { controller = "SysInfo", param = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
In the Above example there is a specific route for a SysInfo controller, which handles the nested resource. As well as the default route.
The SysInfo controller with actions for GET-requests could look the following
public class SysInfoController : ApiController
{
// GET api/client/{id}/sysinfo/
public string Get(int id)
{
return "value";
}
// GET api/client/{id}/sysinfo/{param}
public string Get(int id, string param)
{
return "value";
}
}
I don't know of a method to handle hierarchical ressources in WebApi automatically.

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

Is there a way to tell routing that my default action name is equal to the HTTP Action Verb?

I've looked through this document on MSDN and can't come up with the answer.
Considering that I have a route defined like this:
config.Routes.MapHttpRoute(
name: "DefaultWithActionAndID",
routeTemplate: "v{version}/{controller}/{action}/{id}",
defaults: null,
constraints: new {
action = #"[a-zA-Z]+",
id = #"\d+"
}
);
config.Routes.MapHttpRoute(
name: "DefaultWithID",
routeTemplate: "v{version}/{controller}/{id}",
defaults: null,
constraints: new {
id = #"\d+"
}
);
config.Routes.MapHttpRoute(
name: "DefaultWithoutActionOrId",
routeTemplate: "v{version}/{controller}",
defaults: null,
);
Now I have two controllers that looks like this:
public class ItemController:ApiController{
[HttpGet]
public Item Get(int id){}
[HttpGet]
public Item GetSomething(int id){}
[HttpPut]
public Item Put(Item newItem){}
}
public class AnotherController:ApiController{
[HttpPut]
public HttpResponseMessage Put(Something item){}
}
I'd like to be able to call all of these endpoints like this:
GET /api/Item/344
GET /api/Item?id=344
GET /api/Item/Something/2334
GET /api/Item/Something?id=2334
PUT /api/Item body={newItem}
PUT /api/Another body={newSomething}
This will work, but only if I add "Get" as the default action name. If I do not specify a default action name in my route, then it complains about multiple matching action names. If I do add the default action name, then I cannot call PUT to the Put() method without an error because the action name doesn't match the default and isn't found.
// Will work in some cases, but not all
config.Routes.MapHttpRoute(
name: "DefaultWithID",
routeTemplate: "v{version}/{controller}/{id}",
defaults: new {
action="Get",
id=RouteParameters.Optional
},
constraints: new {
id = #"\d+"
}
);
// Works
GET /api/Item/344
GET /api/Item?id=344
GET /api/Item/Something/2334
GET /api/Item/Something?id=2334
// Doesn't work
PUT /api/Item body={newItem}
PUT /api/Another body={newSomething}
How can I tell Routing to use the Action with the name that matches my HTTP Verb, if one exists before trying to use
If you define your routes as follows:
config.Routes.MapHttpRoute(
name: "DefaultWithActionAndID",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new {action = #"[a-zA-Z]+", id = #"\d*" }
);
config.Routes.MapHttpRoute(
name: "DefaultWithID",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GET", id = RouteParameter.Optional },
constraints: new { id = #"\d*", httpMethod = new HttpMethodConstraint(new string[] { "GET" }) }
);
config.Routes.MapHttpRoute(
name: "DefaultWithoutActionOrId",
routeTemplate: "api/{controller}",
defaults: new { action = "PUT" },
constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "PUT" }) }
);
And also place the ActionName attribute on your GetSomething method as so:
[ActionName("Something")]
public Item GetSomething(int id){}
You should then be able to hit all the endpoints mentioned above.
The way I see it you'd need the following setup:
1.
/api/Item/344
{controller}/{id}
2.
/api/Item/Something/2334
{controller}/{action}/{id}
and decorate the 'GetSomething' method as follows:
[ActionName("Something")]
public Item GetSomething(int id){}
3.
/api/Item?id=344
/api/Item/Something?id=2334
I'm not entirely sure about these - have you tried adding a default to the routes above:
defaults: new { id = RouteParameter.Optional }
4.
I'd expect PUT to just work if #3 is applied
Let me know if that changes anything.

Categories

Resources