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"});
Related
I am building a new webapi for use at work. Where I have to develop a webapi2 application that fits the following endpoint schema
/file
/file/[data id]
/file/[data id]/documents
/file/[data id]/conditions
In my controller I have the following code:
public class FileController : ApiController
{
[HttpPost]
public HttpResponseMessage ImportFile()
{
var act = Request.Headers.Accept.ToString();
// test content type for "application/vnd.exp"
return Request.CreateResponse(HttpStatusCode.OK, $"Successful import # {DateTime.Now}");
}
[HttpPatch]
public HttpResponseMessage UpdateDataByFile(string dataId)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful save");
}
[HttpPatch]
public HttpResponseMessage UpdateDataIntake(string dataId)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful save");
}
[HttpGet]
public HttpResponseMessage GetDataConditionsForUser(string dataid)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful get");
}
}
My route config looks like the following:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "Files1",
routeTemplate: "{controller}/{action}"
);
routes.MapHttpRoute(
name: "Files2",
routeTemplate: "{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
}
}
My question is how do I configure the routing so that it matches the endpoint configuration
eg: "https://something.com/file/123412/document"
and have it hit the correct controller method?
Really confused about how to set routing in an environment like this.
You can do something like this-
routes.MapHttpRoute(
name: "File",
routeTemplate: "file",
defaults: new { controller = "File", action = "ImportFile" }
);
routes.MapHttpRoute(
name: "FileUpdate",
routeTemplate: "file/{dataId}",
defaults: new { controller = "File", action = "UpdateDataByFile" }
);
routes.MapHttpRoute(
name: "FileDocuments",
routeTemplate: "file/{dataId}/documents",
defaults: new { controller = "File", action = "UpdateDataIntake" }
);
routes.MapHttpRoute(
name: "FileConditions",
routeTemplate: "file/{dataId}/conditions",
defaults: new { controller = "File", action = "GetDataConditionsForUser" }
);
routes.MapHttpRoute(
name: "Files1",
routeTemplate: "{controller}/{action}"
);
routes.MapHttpRoute(
name: "Files2",
routeTemplate: "{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
That means you are using a custom route for each Action. You are only using these routes for urls that start like 'file/'. You have a lot of different ways that you can do this.
If you use {controller} in these templates and remove default: controller = "File" then you will match for other controllers.
If you can rename your Controller Actions to match your routing you may be able to fit them into a pattern, and use the {action} in the template.
routes.MapHttpRoute(
name: "File",
routeTemplate: "{controller}/{dataId}/{action}",
defaults: new {}
);
If you rename your last two Actions this should match them.
public class FileController : ApiController
{
[HttpPatch]
public HttpResponseMessage Documents(string dataId)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful save");
}
[HttpGet]
public HttpResponseMessage Conditions(string dataid)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful get");
}
}
And if you set a default action in that route, you can also match your second Action
routes.MapHttpRoute(
name: "File",
routeTemplate: "{controller}/{dataId}/{action}",
defaults: new { action = "UpdateDataByFile" }
);
in a webapi project's WebAPIConfig.cs, 2 routes are added
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 }
);
I try to create an apiController contains below functions
[HttpGet]
public string Get(int id)
{
return "get";
}
[HttpGet]
[ActionName("ByWait")]
public string[] ByWait(int id)
{
return "bywait";
}
I expects that
requesting /api/controllername/1234 returns "get", and
requesting /api/controllername/bywait/1234 returns "bywait".
However, the actual result is
/api/controllername/1234 >> throw exception Multiple actions were found that match the request
/api/controllername/bywait/1234 >> "by wait"
However can fix the issue?
s.t how to restrict the function ByWait only accepts request containing action so that it only response to /api/controllername/bywait/1234 and ignore /api/controllername/1234
Or there is other better solution?
Thanks
First you can change WebApiConfig:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then controller:
[HttpGet]
public string Get()
{
return "get-default";
}
[HttpGet]
public string Get(int id)
{
return "get" + id;
}
[HttpGet]
[Route("api/values/bywait/{id}")]
public string ByWait(int id)
{
return "bywait";
}
I'm trying to have a GET and a POST for the same route:
I've registered the following:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "MyGetMethod",
routeTemplate: "api/v1/users/{user}",
defaults: new
{
controller = "Users",
action = "MyGetMethod"
},
constraints: null,
handler: HttpClientFactory.CreatePipeline(
new HttpControllerDispatcher(config),
routeHandlerFactory.Create()));
config.Routes.MapHttpRoute(
name: "MySetMethod",
routeTemplate: "api/v1/users/{user}",
defaults: new
{
controller = "Users",
action = "MySetMethod"
},
constraints: null,
handler: HttpClientFactory.CreatePipeline(
new HttpControllerDispatcher(config),
routeHandlerFactory.Create()));
My UsersController.cs contains:
[HttpPost]
public HttpResponseMessage MySetMethod(string user)
{
return new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
[HttpGet]
public HttpResponseMessage MyGetMethod(string user)
{
return new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
This doesn't work.
The GET works fine, but when I use POST the WebAPI still redirects to the GET method and I get the error:
"Message": "The requested resource does not support http method
'POST'."
If I comment out the registration of the GET method, then the POST works fine.
Is it because I'm using a combination between Attributes [HttpPost], [HttpGet] on the Controller methods instead of marking them as constraints?
How can I have a GET and POST on the same route?
if you use this will work
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
You only need to define one HttpRoute for that, please try this:
config.Routes.MapHttpRoute(
name: "UserController",
routeTemplate: "api/v1/users/{user}",
defaults: new { user= RouteParameter.Optional }
handler: HttpClientFactory.CreatePipeline(
new HttpControllerDispatcher(config),
routeHandlerFactory.Create()));
If this still doesn't work, please try the same code but change the routeTemplate to:
routeTemplate: "api/v1/{controller}/{user}"
And invoke the web method using a post to
https://YourServer/YourVirtualDirectory/api/v1/Users/myUser
I need to use multiple POST-requests in web API but get an error: "Multiple actions were found that match the request..."
I have 2 POST-requests in my controller:
public void PostStart([FromBody]string value)
{
CookieHeaderValue cookie = Request.Headers.GetCookies("user").FirstOrDefault();
...
}
public void PostLogin([FromBody]string value)
{
CookieHeaderValue cookie = Request.Headers.GetCookies("user").FirstOrDefault();
...
}
My route file looks like this currently:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "apistart",
routeTemplate: "Home/api/values/start/{id}",
defaults: new { action = "PostStart", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "apilogin",
routeTemplate: "Home/api/values/login/{id}",
defaults: new { action = "PostLogin", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
If I remove on of the requests from the controller, everything works fine, so my routes seem valid, but when both of requests are present, router can't find the right one.
Any thoughts?
I've tried alredy to use another default route but it changes nothing:
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
You can use [HttpPost] attribute to specify request method:
[HttpPost]
public void Start([FromBody]string value)
{
CookieHeaderValue cookie = Request.Headers.GetCookies("user").FirstOrDefault();
...
}
[HttpPost]
public void Login([FromBody]string value)
{
CookieHeaderValue cookie = Request.Headers.GetCookies("user").FirstOrDefault();
...
}
That will allows you to use as many post actions as you want with using default action-based route rule.
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
You should use RouteAttribute to make it work:
[Route("start")]
public void PostStart([FromBody]string value)
{
CookieHeaderValue cookie = Request.Headers.GetCookies("user").FirstOrDefault();
...
}
[Route("login")]
public void PostLogin([FromBody]string value)
{
CookieHeaderValue cookie = Request.Headers.GetCookies("user").FirstOrDefault();
...
}
WebApi doesn't take into consideration method name, only first word to resolve http method. Thats why you have error which says about "Multiple actions..." - there are two actions which can handle POST request.
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.