Web Api routing with parameter option? - asp.net-web-api2

I would like to provide a ASP.Net Web API that can be called by the following formats:
http
://myApiServer/API/MyLookupMethod/GetForIVR/PhoneNumber/8005551212
or
http
://myApiServer/API/MyLookupMethod/GetForIVR?LookupType=PhoneNumber&LookUpValue=8005551212
Is it possible to set up a route that works with either call?
My current route is
config.Routes.MapHttpRoute(
name:"MyRoute",
routeTemplate:"api/{controller}/{action}/{Lookuptype}/{lookupvalue}"
);

Try to use attribute routing
[Route("api/user/YourMethodName/{id}/{param2}")]
[HttpGet]
public HttpResponseMessage YourMethodName(int id = 0, bool param2= true)
Hope it works!!

You can use Attribute based routing i.e. specific routing on controllers and methods. Further you should pass your 2 parameters as query-string like in this URL MyLookupMethod/GetForIVR?LookupType=PhoneNumber&LookUpValue=8005551212 and they will be automatically parsed as method parameters.
public class SomeController : ApiController
{
// GET MyLookupMethod/GetForIVR?LookupType=PhoneNumber&LookUpValue
[Route("MyLookupMethod/GetForIVR")]
public Book Get(int LookupType, int LookUpValue) { ... }
}

Related

ASP.NET Core: Generate URL to the base of a controller (NOT TO AN ACTION)

I need to use UrlHelper to generate the URL to the base of a controller. If you must know, the action is Statements, but that is generated by JavaScript provided by a third party that is out of my control. Read: If the name of ANY action, including Statements or Index, is in the generated URL, then my application will fail. Thank you!!!
Stuart Simon
You can do this using a combination of RouteAttribute and Url.RouteUrl.
Controller:
[Route("test", Name = "test-route")]
public class TestController : Controller
{
[HttpGet]
public IActionResult Index()
{
return View();
}
}
This associates the name test-route with /test. Calling Url.RouteUrl("test-route") will return /test.

Using ApiExplorerSettings GroupName in Route in ASPNET Core

In ASP.NET Core - Web API project
Using [ApiExplorerSettings(GroupName = "<group-name>")] decorated on ApiController
and in [Route] attribute I want to refer above GroupName property value.
Also note I do have [ApiVersion("<some-version>")] on same controller to classify further.
Here are some samples to explain:
Example 1:
Attribute on LeadController: [ApiVersion("1.0"), ApiExplorerSettings(GroupName = "sales"), [Route("api/{groupName}/v{version:apiVersion}/leads"]
Expected translated route format: /api/sales/v1/leads
Attribute on AccountsController: [ApiVersion("2.1"), ApiExplorerSettings(GroupName = "finance"), [Route("api/{groupName}/v{version:apiVersion}/accounts"]
Expected translated route format: /api/finance/v2.1/leads
In above {version:apiVersion} gives me ApiVersion value (I assume because that attribute has ToString set to version value). But when I try {groupName} or {grp:groupName} or {grp:ApiExplorerSettings.GroupName} - none of them works. How can access this group name in route attribute?
Do you have any special settings somewhere else, it works fine on my side.
LeadController:
[ApiVersion("1.0"), ApiExplorerSettings(GroupName = "sales"),Route("api/{groupName}/v{version:apiVersion}/leads")]
[ApiController]
public class LeadController : ControllerBase
{
public string Get()
{
return "sales group";
}
}
AccountsController:
[ApiVersion("2.1"), ApiExplorerSettings(GroupName = "finance"), Route("api/{groupName}/v{version:apiVersion}/accounts")]
[ApiController]
public class AccountsController : ControllerBase
{
public string Get()
{
return "finance group";
}
}
Result:
ApiExplorerSettings.GroupName is only for the logical group name used in API descriptions. It is not used or evaluated in route templates. There is no way to make that work. That is why it never expands. It is possible to use a {groupName} route parameter, but it will be just that - a route parameter.
It's not entirely clear if you are trying to control grouping for the API Explorer or use a route parameter. Grouping is typically used by the API Explorer. API Versioning will assign or override the group name so that APIs are bucketized by their version. It is possible to change this behavior, but you'll need an API Explorer (via IApiDescriptionProvider) or OpenAPI/Swagger document generator extension to do it.

What's causing the parameter to be null?

I am using Asp.Net Core 2.0 and web api to build a rest service. All works fine except for HTTPPost.
[HttpPost("LoginUser")]
public IActionResult LoginUser(LoginUser loginUser)
{
return Ok(loginUser);
}
loginUser is always null. I am testing with fiddler and my route is http://localhost:53250/api/User/LoginUser
and the body is
{"EmailAddress":"xx#xx.com","Password":"123456789"}
Fiddler hits the link just fine, but payload is always null.
I have also tried
[HttpPost("LoginUser")]
public IActionResult LoginUser([FromBody] LoginUser loginUser)
{
return Ok(loginUser);
}
In this case, it doesn't hit the function.
This is the LoginUser definition:
public class LoginUser
{
public string EmailAddress { get; set; }
public string Password { get; set; }
}
Any Ideas?
Your action should be:
[Route("api/[controller]")]
public class UserController : Controller
{
[HttpPost("LoginUser")]
public IActionResult LoginUser([FromBody] LoginUser loginUser)
{
return Ok(loginUser);
}
}
See, [HttpPost("LoginUser")] affects only route and doesn't relate to LoginUser object type.
Update: you need [FromBody] as ASP.NET Core model binding by default looks into [FromForm] binding source. And [FromBody] attribute indicates that you want to bind a parameter to data in the request body.
Update 2: you also should add Content-Type: application/json header to request. ASP.NET Core selects input formatters based on the this header.
Update 3: if you really need to get body data as raw string, look into ASP.NET Core MVC : How to get raw JSON bound to a string without a type?. It suggests using [FromBody] dynamic data
JSON parsing is case sensitive. Your JSON is in the wrong case.
Should be: {"EmailAddress":"xx#xx.com","Password":"123456789"}.
Issue has been solved. When I added my UserController, I did so as a class and derived from controller. I deleted it and added it as a new item and picked web api core controller. Now all is working just fine. Thanks for your help.
If you have properties in your request model that are set to {get;
private set;}, the values will not get populated. Make them public by removing private. Also constructors
aren't utilized.
If you're reading plain text from the body, see if [FromForm]
works.

ASP.Net Web API Route based on HTTP Method

Is there any way to route based on the HTTP method specified in the request? I'm looking to have a GET and PUT at the same URI, but I can't seem to find the option to set the route between the two. The [HttpGet] and [HttpPut] attributes merely act as filters, so a PUT request is hitting the first action and erroring out with a 405 since it hits the GEt handler first.
What I want to do
~/User/PP GET -> UserController.GetPrivacyPolicy
~/User/PP PUT -> UserController.UpdateUserPrivacyPolicy
Whats currently happening
~/User/PP GET -> UserController.GetPrivacyPolicy
~/User/PP PUT -> UserController.GetPrivacyPolicy
(this errors out because i have a [HttpGet] filter on the GetPrivacyPolicy method)
Update:
Just to compliment what was posted below, it looks like I misunderstood how the [HttpGet] and [HttpPut] attributes work, they ARE part of the routing process. I was able to achieve my desired result with the following
[HttpGet]
[Route("~/User/PP")]
public string GetPrivacyPolicy()
{
return "Get PP";
}
[HttpPut]
[Route("~/User/PP")]
public void UpdatePrivacyPolicy()
{
return "Put PP";
}
What you'll need to do is create your controller with identically named actions but decorated with different Http method attributes
public class UserController : Controller {
[HttpGet]
public ActionResult PrivacyPolicy(int id) {
// Put your code for GetPrivacyPolicy here
}
[HttpPut]
public ActionResult PrivacyPolicy(int id, YourViewModel model) {
// Put your code for UpdatePrivacyPolicy here
}
}
Of course there are appropriate actions for the other methods e.g. HttpPost, HttpDelete, HttpPatch.

Understanding routes, how do I create two 'identical' routes that can be both GET and POST?

Previously, I had two methods and I labelled one with [WebGet] and one with [WebInvoke(Method = "POST"]
when I did a GET or a POST to the URL that I specified, it would always call the correct method.
The URLs were:
POST: fish-length
GET: fish-length?start-date={startDate}&pondId={pondId}
Now that I'm using web api, I have to define my routes seperately, like this:
RouteTable.Routes.MapHttpRoute(
name: "AddFishLength",
routeTemplate: "fish-length",
defaults: new
{
controller = "FishApi",
action = "AddFishLength"
});
RouteTable.Routes.MapHttpRoute(
name: "GetFishLength",
routeTemplate: "fish-length?start-date={startDate}&pondId={pondId}",
defaults: new
{
controller = "FishApi",
action = "GetFishLength"
});
However the second route doesn't work, because you're not allowed a ? in the routeTemplate.
I can change the URL format to something like fish-length/{startDate}/{pondId} but it's really not a very nice way to expose the service.
Is there a better way to do this? Also because I was doing a POST and GET to the same url before, I'd need to ensure my routing method still allowed this. Assuming the above worked, I'm still not sure how it would route correctly.
No, you don't need to define separate routes. All you need is a single route:
RouteTable.Routes.MapHttpRoute(
name: "AddFishLength",
routeTemplate: "fish-length",
defaults: new
{
controller = "FishApi",
}
);
and then follow the RESTful naming conventions of your ApiController's actions:
public class FishApiController: ApiController
{
// will be called for GET /fish-length
public HttpResponseMessage Get()
{
// of course this action could take a view model
// and of course that this view model properties
// will automatically be bound from the query string parameters
}
// will be called for POST /fish-length
public HttpResponseMessage Post()
{
// of course this action could take a view model
// and of course that this view model properties
// will automatically be bound from the POST body payload
}
}
So assuming you have a view model:
public class FishViewModel
{
public int PondId { get; set; }
public DateTime StartDate { get; set; }
}
go ahead and modify your controller actions to take this parameter:
public class FishApiController: ApiController
{
// will be called for GET /fish-length
public HttpResponseMessage Get(FishViewModel model)
{
}
// will be called for POST /fish-length
public HttpResponseMessage Post(FishViewModel model)
{
}
}
You could obviously have different view models for the different actions.
You can't specify the query string parameters in the route template - but as long as you have a method that matches the names of the parameters WebApi should be clever enough to figure it out on its own.
public HttpResponseMessage Get(string id) would correspond to a request for {controller}?id=xxx
However, it's hard to tell without seeing the actual objects how you should go about solving your case. E.g. WebApi doesn't like complex types in a Get request, as well as it supports url-encoded content in post data in a particular way only.
As for differentiating between Get and Post that's quite simple - WebApi knows which method you used when sending the request and then it looks for a method name starting with Get/Post or decorated with the HttpGet/Post attribute.
I recommend taking a look at the following articles - they helped me understand how it works:
http://www.west-wind.com/weblog/posts/2012/Aug/16/Mapping-UrlEncoded-POST-Values-in-ASPNET-Web-API
http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx