Web-Api Route resolution Array Model Binding - c#

I'm trying to understand how Web-Api Resolves Routes. I have two routes which use the same base path
[WriteRoute(DivisionAPIRoutes.PAYROLL_IMPORT_PTO)]
[HttpPost]
public void ImportPTOByIds(GlobalEntityKey<IDivision> parentId, GlobalEntityKey<IDivisionPayCalendar> id,
ImportPTORequestDTO importPTORequest, [FromUri] GlobalEntityKey<IPTORequest>[] ptoRequestIds)
{
GlobalFactory<IEmployeePTOListService>.Instance.ImportPTOByIds(parentId, id, ptoRequestIds, importPTORequest);
}
[WriteRoute(DivisionAPIRoutes.PAYROLL_IMPORT_PTO)]
[HttpPost]
public void ImportPTOByFilter(GlobalEntityKey<IDivision> parentId, GlobalEntityKey<IDivisionPayCalendar> id, ImportPTORequestDTO importPTORequest, string filterOptions,
[FromUri] GlobalEntityKey<IPTORequest>[] excludedPTORequestIds)
{
var filterOptionsDTO = JsonConvert.DeserializeObject<FilterOptionsDTO>(filterOptions);
GlobalFactory<IEmployeePTOListService>.Instance.ImportPTOByFilter(parentId, id, filterOptionsDTO, excludedPTORequestIds, importPTORequest);
}
NOTE: By default, builting CLR types string, int are retrieved from the URI
I'm making a post request which work fine but I'm trying to understand the underlying logic for how the route resolves to the method:
(Decoded for Convenience)
https://localhost/api/paystream/v1/divisions/1af4edea-d442-4fda-b29d-02c42951c0d0/payrolls/cd2ed43d-0f3d-48fb-8d00-15294a8fa06e/_actions/import-pto?filterOptions={"query":"","filterParameters":[{"fieldName":"RequestStartDate","parsebleValue":"2016-01-01","filterType":"GreaterThanOrEqual"},{"fieldName":"RequestStartDate","parsebleValue":"2017-12-31","filterType":"LessThanOrEqual"}]}
PostBody: {
AlwaysCreateNewCheck: false,
PayBatchType: 'Checks',
PayBatchId: '1903771'
}
If I Omit the excludedPTORequestIds from the request. This will still resolve to ImportPTOByFilter but if I include the excludedPTORequestIds and Omit the filterOptions the ImportPTOByIds is Selected.
I'm inclined to think that Lists, and Array which are handled by the model binder have different behaviors than other Default CLR Types which model bind (string, int, Guid, etc).
While string are required and will throw a 404 or resolve to other routes, Array's are not required to be explicitly defined in the request.
Is it safe to assume that, What are the other rules for route resolution from WebApi.

It's resolving to ImportPTOByFilter when you include filterOptions because the other route doesn't have a filter option parameter.
If you remove filter option it resolves to ImportPTOByIds because the parameters match the method signature. Route resolution is going to go by route name and parameters. If you have
RouteA(string myString, int myInt) {...}
and
RouteA(string myString) {...}
and
RouteA(int myInt) {...}
It's going to resolve the route based on if you pass a string, int, or both.
Others more knowledgeable than me can probably add more information but I think you want to make an actual model the encapsulates the entire request that way you have one method and in that method you inspect the model to determine if it should be filtered or not.

Related

Is it possible to bind Route Value to a Custom Attribute's Property in ASP.NET Core Web API?

I've a Custom Attribute which I'm using to authorize the request before it hits the Controller Action, in ASP.NET Core Web API. In the image below, "SecureMethod" is the custom attribute.
I want to add a property in the Custom Attribute, which I want to bind to one of the Route Values, please refer to the image below:
I want to bind "userIDs" route-value to one of the Custom Attribute's property ("UserIDs", this is an example attribute).
Is there any way I can bind the route-value to a custom-attribute's property?
TIA
One way you can do this is by passing the name of the route parameter to the attribute, and examining the route data in the attribute itself. For example:
[SecureMethod(PermissionRequired = AccessPermissionEnum.DeleteUsers, UserIdsKey = "userIds")]
Then something like:
public AccessPermissionEnum PermissionRequired { get; set; }
public string UserIdsKey { get; set; }
public override void OnActionExecuting(ActionExecutingContext context)
{
// Depending on how you pass your userIds to the action, you
// may need to use something else from context to get the data,
// but it's all available in there.
var userIds = context.ActionArguments[UserIdsKey];
// Do something with the ids.
base.OnActionExecuting(context);
}
Whilst this works, and in certain places it works really well, you should be aware that the filter now has intimate knowledge of the parameter being passed in. For the most part, this usually doesn't matter so much, but it's an approach you shouldn't use everywhere because it will end up adding a lot of coupling if not used well. It's a useful tool to use every now and then.
No, it is not possible.
Attribute parameters are restricted to constant values of the following types:
Simple types (bool, byte, char, short, int, long, float, and double)
string
System.Type
enums
object (The argument to an attribute parameter of type object must be
a constant value of one of the above types.)
One-dimensional arrays of any of the above types
You cannot nest attributes and you cannot pass non-constant values to attribute parameter. Even though you can declare an attribute parameter as type object, the actual argument you pass in must still be a constant string, bool, etc (declaring it as an object just means you can use any of these types each time you declare the attribute).

C# MVC - Routing Issue / Confusion

I am developing one application in the ASP.NET MVC C# on the .NET 4 framework.
I confused in routing and I do the research and developed the one demo version It works as I want but I want to know is which method is best practice for developing application.
First I register the route like this:
routes.MapRoute(
name: "RoutesTesting",
url: "{controller}/{action}/{a}/{b}/{c}/{d}/{e}",
defaults: new { controller = "Home", action = "Test", e = UrlParameter.Optional }
);
I have one class that have the properties and it's name same as the route's parameter.
class MyClass{
public string a{get;set;}
public string b{get;set;}
public string c{get;set;}
public string d{get;set;}
public string e{get;set;}
}
Now I created the tow methods that works find and get the data from the URL successfully.
Method 1:
public ActionResult Test(MyClass objMyClass){
}
Method 2:
public ActionResult Test(string a,string b,string c,string d,string e=String.Empty){
}
My question is:
Is routing doing that conversation in my action method? Like it convert the parameter values in the `MyClass' object's properties?
Which method is best practice to use?
Is method 1 will throw any error or exception when the conversation is not possible ?
Thanks in advance...
The behavior you are seeing is a part of ASP.NET's Model Binding. It's the magic that lets you send across a JSON object of {"firstName":"Jonathon","lastName":"Chase"} and have to automagically be mapped to a model Person that looks like so:
public class Person {
public string FirstName {get;set;}
public string LastName {get;set;}
}
The fact that you can create a route like that is merely a consequence of this. Model Binding is a complex subject, but I can touch on some aspects of how you're forming your route, especially if the action you're creating is going to have a side-effect, such as writing to a database.
Typically if you're going to have a method that will effect state, you should use an Http verb other than Get, and send the model across in the body of the request, rather than in the query/url string. The Model Binding will take care of the mapping for you either way.
You should prefer to use a strong model rather than multiple primitives as parameters, especially in cases where the information will be sent in the body of a request over the query string.
These points are debatable, however, and shouldn't be considered hard or fast rules for the most part.
As to your last point, if the parameters are incorrect enough that the Route can't identifier the action or controller, you should get a 404. However, if you have a valuetype that isn't nullable as an expected routed property that isn't properly sent across, you should expect a 500 with an InvalidOperationException.
Take a look at How model binding works
Is routing doing that conversation in my action method? Like it
convert the parameter values in the `MyClass' object's properties?
The framework model binder is doing the conversion based on the actions parameter.
Which method is best practice to use?
That is an opinionated question. Depends on which one suits your needs. The framework handles both.
Is method 1 will throw any error or exception when the conversation is
not possible ?
Model binder will pass null to the action parameter for the properties that don't match.

WebApi - UrlHelper.Route to GET with [FromUri] object parameter

I have an API action defined as the following:
[Route(Name="GetMembersTest"), HttpGet, ResponseType(typeof(MemberHeadersDto))]
public IHttpActionResult GetMembers[FromUri]MemberFilterDto filter, [FromUri]PagingOptionsDto paging)
This method works as expected, routing and all, requests are flowing through just fine. However, I'd like to supply a "NextUri" for paging so that the caller can just keep following NextUri until it is null to get all the results. I have to send back a uri to the same action, 1 page ahead, if that makes sense.
So I tried using UrlHelper.Route. This route is named "GetMembers" for the purpose of this example.
NextUri = Url.Route("GetMembers", new { filter, paging });
The problem is that instead of getting something like
/v1/members?filter.q=&filter.otherproperty=&paging.count=10&paging.startRow=11
I get
/v1/members?filter=WebApi.Models.MemberFilterDto&paging=WebApi.Models.PagingOptionsDto
It looks like UrlHelper.Route doesn't support complex types in the [FromUri] parameter of a GET Request. Is there anything I can do to get this functionality? My workaround right now is to take in all the Dto properties as individual parameters then build my Dtos from them on the server. This isn't ideal because if I ever add any more options I'd have to add more parameters to the action, which also makes the route value dictionary more fragile as well because it has to match with the method signature in UrlHelper.Route(routeName,routeValues).
Unfortunately, there is no way to pass in complex object to routing. Instead, you will need to pass in the simple properties individually.
I was not able to find a way to extend Url.Route, but that would be/have been your best option.

Loose binding of parameter names in WebApi routing

Currently we use the below routing rules to cater for all our controllers; the advantage being that we don't have to define a route for each action in each controller:
routes.MapHttpRoute("3", "{controller}/{action}/{arg1}/{arg2}/{arg3}");
routes.MapHttpRoute("2", "{controller}/{action}/{arg1}/{arg2}");
routes.MapHttpRoute("1", "{controller}/{action}/{arg1}");
routes.MapHttpRoute("0", "{controller}/{action}");
However due to this the parameter names in the methods must match; like so:
// Example method signature
public ResponseDto GetResponse(int arg1, int arg2)
If the parameter names are changed to something more friendly (eg: a name that would show the intention of each parameter instead of an ambiguous "arg1" name) like so:
// Better example method signature
public ResponseDto GetResponse(int userId, int itemId)
The binding would break unless:
The routes and parameter names are explicitly defined
The arguments are passed in using a query string
Is there are way to set up WebApi routing so it will automatically use the correct action based on the number of parameters; rather than the parameter names?
It seems you're trying to do something similar to what is described in this good blog post on custom parameter binding. As you've found out, there is nothing out-of-the-box to support what you're trying to do.
At a conceptual level, HTTP is built around the concept of resources. A URI should identify a specific resource. Using generic positional "parameters" seems to blur the specificity of a resource. It seems that your use case might be better served by using the query string of the URI. That way, your HTTP API expresses a clear intent of what parameters are expected for a given resource.

Routing by posted content type in ASP.NET MVC

I have a fixedURL to which I'd like to post different types of xml message, deserialized using DataContracts. Depending on the type of the deserialized message, I'd like to route to:
overloaded methods, e.g.
void Process(ContractType1 request) {}
void Process(ContractType2 request) {}
So at some point I need to deserialize this message and hopefully allow the default routing rules to match the correct method. Which extensibility point should I use for this? Or even better, can I make this work out of the box?!
If it makes any difference, I'm using MVC 3.
ASP NET MVC does not respect the overload if they are not decorated for different HTTP methods - e.g. one for POST, other for GET.
You need to use [ActionName(Name = "Process2")] to change the route name. And you will have to use different routes to access (if the HTTP methods are the same)
Have a look here.
Apart from the technical workaround, passing different contracts to the same URL is against the REST principles. Data could be in different format (XML, JSON, ...) but it must be the same. The URI defines a unique intention. Now it is possible to have a common dumpster where documents are all dumped to the same URI but then ASP NET MVC default model binder would not be able to help you and you need to create your own model binder.
Contrary to the other answer I say this is possible
Asp.net MVC is a great platform that can be easily extended. And so basically I've written a special action method selector that makes it possible to write overloads that can serve the same HTTP method but defer in parameters. By default you'd get runtime error that action method can't be resolved. But when you use this action method selector you get rid of this error.
Basically if your parameter classes have distinct parameter names, you can actually select methods by that.
Action method selector is called RequiresRouteValuesAttribute and a typical usage scenario would be with default route where id is optional in:
{controller}/{action}/{id}
This means that you either have to write
public ActionResult Index(int? id)
{
if (id.HasValue)
{
// display details view
}
else
{
// display master view
}
}
but by using my action method selector you can easily write two action methods:
public ActionResult Index()
{
// display master view
}
[RequiresRouteValues("id")]
public ActionResult Index(int id)
{
// display details view
}
The same could be applied to your action methods as long as your custom types have distinct property names or methods use different parameter names. So in your case it could be something like:
[RequiresRouteValues("first.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType1.Distict.Property.Name")]
public ActionResult Process(ContractType1 first)
{
// do what's appropriate
}
[RequiresRouteValues("second.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType2.Distict.Property.Name")]
public ActionResult Process(ContractType2 second)
{
// do what's appropriate
}
Read all the details about this action method selector and get the code as well.

Categories