C# MVC - Routing Issue / Confusion - c#

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.

Related

Read action parameters from JSON properties

How can I get the parameters of an ASP.NET Core MVC controller action from the properties of a JSON object that was sent in the HTTP request body?
If the request body contains form data and the content type is appropriate, the action method parameters are populated automatically as expected.
But nothing works if the POST data format is JSON and the content type is "application/json". I think this is quite common for API requests? I tried adding the [FromBody] attribute to all parameters, but the documentation says I can only apply that once. Well, I can write it multiple times and nobody complains, but neither helps. Even if I want to bind all the JSON to a single string parameter, it remains null.
Can ASP.NET Core actually handle JSON POST data? In the usual parameter binding comfort? Or are we down to the feature level of PHP (or below) when it comes to JSON requests? Should I not use such advanced technology and revert my client-side code to plain old HTTP form data instead?
I think I used this before and saw it working. But can't find out what the difference is here. Maybe it only works in controllers with the [ApiController] attribute? This case is a regular web page controller, not a separate API. But it needs to provide functions to JavaScript as well, so it does both.
I tried adding the [FromBody] attribute to all parameters
This sounds fishy to me. Are you trying to send your params as a json object and expecting them to get unwrapped into individual action params?
Consider the following data type and MVC controller endpoint:
public class Sample
{
public int Id { get; set; }
public string Name { get; set; }
}
[HttpPost]
public IActionResult Post([FromBody] Sample sample)
=> new JsonResult(sample);
This is all you need for a typical POST to an MVC controller. The key point is probably the type that I'm using to bind to the body. As you can see, I create a matching json object in Postman and it binds correctly and returns what I sent.
To get what you want, I think you'd have to rely on query params. (or some other technique I'm unaware of) Here's an example if you need it.
[HttpPost]
public IActionResult PostQuery([FromQuery] int id, [FromQuery] string name)
=> new JsonResult(new Sample {Id = id, Name = name});

ASP.NET Core API Controller ambiguous method issue

I came across this issue, and couldn't find a quick guide to is, so here I am.
I've created and ASP.NET Core API.
On one controller, i've defined 2 get methods. Each accepts different parameter, but both are Strings. This creates following issue.
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
Airbnb.Controllers.ListingsAndReviewsController.GetById (Airbnb)
Airbnb.Controllers.ListingsAndReviewsController.GetByName (Airbnb)
My methods were looking like this.
[HttpGet("{id}")]
public IEnumerable<ListingsAndReviews> GetById(String id)
{
}
[HttpGet("{name}")]
public IEnumerable<ListingsAndReviews> GetByName(String name)
{
}
As you can see, the name of these methods is different, but they have same parameters, which causes issue.
To solve this issue, you have to set new Property "Order" to the HttpGet method attribute.
[HttpGet("id/{id}", Order = 0)]
public IEnumerable<ListingsAndReviews> GetById(String id)
{
}
[HttpGet("name/{name}", Order = 1)]
public IEnumerable<ListingsAndReviews> GetByName(String name)
{
}
The Order property differentiates between the 2 methods.
The best practice might be adding the order property to every method.
Hope this can help someone, if not, feel free to suggest better solution.
[Edit]
I ended up with following structure for the template
"id/{id}"
"name/{name}"
this forces the route to first set the filtered parameter, then the value.
Following structure is now established
localhost/ListingsAndReviews
localhost/ListingsAndReviews/id/1
localhost/ListingsAndReviews/name/SomeName
I have to ask : Are you sure the "ID" will not be an integer or a guid or something like that ?
If it's the case you can directly use the expected type into the action:
public IEnumerable<ListingsAndReviews> GetById(Guid id)
It's a really a string (as the name) you can use route parameter constraint :
That way you can define what the "ID" will looks like and what the name will looks like.
By example if you know that ID will only have 5 characters max you could do:
[HttpGet("{id:maxlength(5)}")]
I just gave you the mechanism but the specific answer to you issue will depend on : What are the IDs looks like ? (I guess the name can be anything...).
If you cannot differentiate ID and name you shouldn't have 2 endpoints (but maybe in one endpoint you can search once for the ID and once for the name as a fallback).

How to Validate Request Body by allowing only attributes assigned in the Model of an asp.net core web api app

I have an ASP.NET CORE Web API app and placed a Model as:
public class SamplePayload
{
public string Attribute1 { get; set; }
public string Attribute2 { get; set; }
}
then my controller looks like:
[ApiController]
public class SampleController : ControllerBase
{
[HttpPost]
public async Task<string> Add(SamplePayload samplePayload)
{
if (!ModelState.IsValid)
{
//Throw Error
}
return "Hello";
}
}
However, the Action still accepts the payload if the Request Body had its payload like this (with additional attributes):
{
"Attribute1":"Value1",
"Attribute2":"Value2",
"EvilAttribute":"EvilValue"
}
As you see, EvilAttribute is not a valid attribute according to the Model, but still, the controller accepts it, and Model.IsValid also returns true despite the fact that I have [ApiController] assigned on top of the controller.
My question is how can I do validation to check that only attributes defined in the Model need to be passed in the Request body? Doesn't ASP.NET core offer simpler handling?
Note:
The reason for this requirement is, Under a Vulnerability assessment done by an independent assessor, highlighted that my Endpoint
accepts additional parameters in request body
The Assessment quoted as:
Test Category: Mass Assignment;
Test Conducted: Adding additional parameters to the requests
Findings: Additional parameters accepted with a request body
Recommendations:
If possible, avoid using functions that automatically bind a client's input into code variables or internal objects.
Whitelist only the properties that should be updated by the client.
If applicable, explicitly dene and enforce schemas for the input data payloads.
I see not much benefits from rejecting the requests that have more data in it.
If that is some sort of requirement, here is what you can try:
I think you may need to implement your own ModelBinder as per example described here:
https://learn.microsoft.com/en-US/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-5.0
The way default model binding works - is going from Model perspective and search for matching fields in the request and assigns values to related model properties.
Once the model that is required can be constructed and passes all of it's internal constraints - it is Valid.
Please check this article: Model Binding in ASP.NET Core
Model binding tries to find values for the following kinds of targets:
Parameters of the controller action method that a request is routed to.
Parameters of the Razor Pages handler method that a request is routed to.
Public properties of a controller or PageModel class, if specified by attributes.
For your scenario, if you set a break point in the Add method, you can see that the Model Binding will just find values based on the SamplePayload's properties.
So, I think there is no need to check whether the request body contains more data or not.
An [ApiController] or a parameter marked as [FromBody] will be serialised in MVC via an input formatter, rather than binding from a value provider.
Working backwards from the mvc source code. It looks like you can control MVC's serialisation settings if you are using System.Text.Json, though I don't see a way to reject extra values;
services.Configure<JsonOptions>(o =>
{
});
Or newtonsoft, which does seem to allow rejecting extra values;
services.Configure<MvcNewtonsoftJsonOptions>(o =>
{
o.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
});
(I haven't tested this myself though...)
I would still push back on this "security" assessment. If you don't have any extra fields available to bind, then rejecting requests doesn't improve security. Though it may help consumers to understand if they have mis-spelled an attribute name.

Can I have multiple POST methods in Web API with different complex parameter types?

I'm new to Web API...
Here's my basic route:
config.Routes.MapHttpRoute(
name: "Workitems",
routeTemplate: "api/{controller}/{workitemID}",
defaults: new { controller = "workitems", workitemID = RouteParameter.Optional }
);
Here's what I'd like to have:
public HttpResponseMessage Post( [FromBody] FolderModel theModel )
public HttpResponseMessage Post( [FromBody] DocumentModel theModel )
But, Web API doesn't find my second Post method. I've done lots of searching here and in Google but haven't found anything that works for me (well). I know I could add a 2nd unused parameter to the 2nd method - but that's too much of a hack. If this were normal C# code, the compiler would have no problem knowing which to choose b/c the methods have different signatures. But Web API is not smart enough.
I looked at custom constraints but that didn't seem appropriate. I also cannot use different {actions} as that violates RESTful constraints (no RPC, just resources) for my API. I also cannot put the 2nd Post on a different controller.
The only way I've gotten this to work is to wrap both FolderModel and DocumentModel in a parent object like this:
public class WorkitemCreateModel
{
public DocumentModel Document { get; set; }
public FolderModel Folder { get; set; }
}
public HttpResponseMessage Post( [FromBody] WorkitemCreateModel theModel )
Then have a single Post method that takes WorkitemCreateModel. But then it's the responsibility of the developer using my API that they must pass in WorkitemCreateModel but they must only pass in a DocumentModel object OR a FolderModel object. It's annoying too b/c my GET API can return either a DocumentModel object or a FolderModel object. So, it would be nice to just pass the object you get from the GET into the POST. But that doesn't work and they must wrap it in a WorkitemCreateModel object first.
Any other suggestions?
BTW: this website is the best! I've found SO many answers here!
This might be an old post but I am adding this info just in case for people like me who came here looking for answers.
The short answer here is NO, its not possible.
The problem is with the way the routing works, especially the part about choosing what action method to use. Here is an extract from ASP .NET article (https://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection),
The goal of the selection algorithm is to select an action from the static description, before invoking any bindings. Therefore, complex types are excluded from the matching algorithm.
So while matching action methods to the path, Web API disregards all Complex types in the parameter list for that method and when you do that both of your methods have 0 parameters and that's why you are facing this problem.
Hope this helps...
Have you considered changing the method names and trying using the [HttpPost] attribute?
Source : http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
This might answer your question:
Multiple HttpPost method in Web API controller

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