WebAPI routing: Combining Attributes and HttpConfiguration settings - c#

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

Related

Route Config with multiple custom actions in web api

So I have GET-methods in two different controllers that does not seem to work at the same time. My route config looks like this:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "GetGroupsFromSection",
routeTemplate: "api/{controller}/{action}/{sectionId}"
);
config.Routes.MapHttpRoute(
name: "ReturnCountForGroup",
routeTemplate: "api/{controller}/{action}/{groupIdCount}"
);
}
With this config, the first route(GetGroupsFromSection) works, but not the other one. If I switch them up so the ReturnCountForGroup is first, then that one works but not the other.
This is the methods
In the GroupController:
[HttpGet]
public IEnumerable<Group> GetGroupsFromSection(int sectionId)
{
var allGroups = groupRepository.SearchFor(s => s.SectionId == sectionId).ToList();
return (IEnumerable<Group>)allGroups;
}
And here is the other one from the ActivationCode-controller:
[HttpGet]
public int ReturnCountForGroup(int groupIdCount)
{
var count = dataContext.ActivationCode.Count(c => c.GroupId == groupIdCount);
return count;
}
GetGroupsFromSection is getting a 200 ok. But the ReturnCountForGroup get this error-message:
"MessageDetail": "No action was found on the controller 'ActivationCode' that matches the request."
There are conflicting routes which need to be made more specific for a route match to be made. Also the order of how routes are added to the route table is important. More generic routes need to be added after more specific/targeted routes.
config.Routes.MapHttpRoute(
name: "GetGroupsFromSection",
routeTemplate: "api/Group/{action}/{sectionId}",
defaults: new { controller = "Group" }
);
config.Routes.MapHttpRoute(
name: "ReturnCountForGroup",
routeTemplate: "api/ActivationCode/{action}/{groupIdCount}",
defaults: new { controller = "ActivationCode" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Call Api without custom route

I can call Api with custom route.
(url: 'api/WorkItem/1/GetOne')
[HttpGet]
[Route("api/WorkItem/{id}/GetOne")]
public async Task<IEnumerable<WorkItemDto>> GetOne(int id)
{
//...
}
But I cant call same api if I remove custom route:
This is my route definsion:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { action = RouteParameter.Optional, id = RouteParameter.Optional }
);
I expect this to work without custom route but it does not.
I get 404-NotFound.
What am I doing wrong?
Map route two times. first with action and second time without
config.Routes.MapHttpRoute(
name: "WithAction",
routeTemplate: "api/{controller}/{id}/{action}"
);
config.Routes.MapHttpRoute(
name: "DefaultId",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

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"});

How to access web api method by using WebapiConfig?

i would like to access below methods but i can not access "http://www.test.com:46707/rpc/RealmStatus/RealmByPopulationName/2/Vindication"
[ActionName("RealmByPopulationName")]
public IEnumerable<MyRealmStatus> GetRealmsByBattleGroupName(int regionid, string battlegroupname)
{
//dosomething...
}
My webApiConfig.cs below
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{regionid}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "GetData",
routeTemplate: "api/{controller}/{regionid}/{id}"
);
config.Routes.MapHttpRoute(
name: "RpcApi",
routeTemplate: "rpc/{controller}/{action}/{regionid}",
defaults: new { action = "Get" }
);
config.Routes.MapHttpRoute(
name: "RpcApi2",
routeTemplate: "rpc/{controller}/{action}/{regionid}/{quality}/",
defaults: new { action = "Get" }
);
config.Routes.MapHttpRoute(
name: "RpcApi3",
routeTemplate: "rpc/{controller}/{action}/{regionid}/{battlegroupname}/",
defaults: new { action = "Get" }
);
}
}
Error occurs i can not access GetRealmsByBattleGroupName
Result of "http://www.test.com:46707/rpc/RealmStatus/RealmByPopulationName/2/Vindication" :
<Error>
No HTTP resource was found that matches the request URI 'http://www.test.com:46707/rpc/RealmStatus/RealmByPopulationName/2/Vindication'.
No action was found on the controller 'RealmStatus' that matches the request.
Your call is going to match this route first:
config.Routes.MapHttpRoute(
name: "RpcApi2",
routeTemplate: "rpc/{controller}/{action}/{regionid}/{quality}/",
defaults: new { action = "Get" }
);
Therefore your API is looking for a method on the RealmStatus controller that matches the following signature:
[ActionName("RealmByPopulationName")]
GetRealmsByBattleGroupName(int regionid, string quality)
If quality is a number you could differentiate the routes by adding the following constraint:
config.Routes.MapHttpRoute(
name: "RpcApi2",
routeTemplate: "rpc/{controller}/{action}/{regionid}/{quality}/",
defaults: new { action = "Get" },
constraints: new { id = #"(^\d+$)" }
);

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