Why use ASP.NET Core modelbinding attributes? - asp.net-core

For what reason should we apply these attributes in ASP.NET Core model binding?
What are the consequences of not using them?
Isn't the model binding engine able to search through the incoming request and map them to Controller action method parameters without these attributes:
[FromQuery] - Gets values from the query string.
[FromRoute] - Gets values from route data.
[FromForm] - Gets values from posted form fields.
[FromBody] - Gets values from the request body.
[FromHeader] - Gets values from HTTP headers.
See this Controller action method examples:
public ActionResult<Pet> Create([FromBody] Pet pet)
public ActionResult<List<Pet>> Search([FromRoute] string breed, [FromQuery] string color, [FromQuery] int age)
We can also apply the attributes to the model class:
public class Pet
{
public string Name { get; set; }
[FromQuery]
public string Breed { get; set; }
}
Source: Microsoft Docs
Controller action method examples without attributes:
public ActionResult<Pet> Create(Pet pet)
public ActionResult<List<Pet>> Search(string breed, string color, int age)

You could check the Sources description:
By default, model binding gets data in the form of key-value pairs from the following sources in an HTTP request:
Form fields
The request body (For controllers that have the [ApiController] attribute.)
Route data
Query string parameters
Uploaded files
For each target parameter or property, the sources are scanned in the order indicated in the preceding list. If the default source is not correct, we can use one of the following attributes to specify the source:
[FromQuery] - Gets values from the query string.
[FromRoute] - Gets values from route data.
[FromForm] - Gets values from posted form fields.
[FromBody] - Gets values from the request body.
[FromHeader] - Gets values from HTTP headers.
For example:
When using the following method, it will get the pet data from the form fields:
public ActionResult<Pet> Create(Pet pet)
If using the following method, it will get the parameter from the default source. We can pass the parameter via the Form or Query string.
public ActionResult<List<Pet>> Search(string breed, string color, int age)
If adding attribute to above method, like this:
public ActionResult<List<Pet>> Search([FromQuery]string breed, [FromQuery]string color, [FromQuery]int age)
You could only pass the parameter via the Query string. In this scenario, if you pass the parameters via Form, the parameters in the action method will be Null.
So, by using these attributes we could specify the model binding source, without to scan the default source list.

Related

ASP.Net Model Binder - Ignore Not Mapped Properties

I am working with ASP.Net Core 5, Entity Framework Core 6, and using ODATA controllers with an EDM model. The model I am working with has several properties, including a [NotMapped] property. I have a React SPA that gets an entity from the controller, including the [NotMapped] property. When I post this entity back to my controller from the react app. The controller uses [FromBody] attribute to deserialize the JSON into a complex .NET object. However, the model fails to bind because of the [NotMapped]` property. If I remove this not mapped property in the React app on the client side before posting to the controller, the model binder works fine. However, this is not an acceptable solution, since this means my code will break anytime I add a not mapped property to my models without updating the React client to deal with this. The React client has no understanding of which properties are mapped or not mapped. Is there a way to get the model binder to just ignore properties that do not exist in the model, instead of having the model fail to bind completely?
Here is my model:
public class Person
{
public Person() { }
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string FullName => FirstName + " " + LastName;
}
And here is my controller Get action:
[ODataRoute("{id}")]
public Person Get([FromODataUri] int id)
{ return db.Persons.Find(id); }
The controller successfully gets the Person, including the [NotMapped] FullName property from the model and returns the model to the React client.
When I update the model on the React client and want to update Person, I call the Put method of this controller:
[ODataRoute("{id}")]
public async Task<IActionResult> Put([FromODataUri] int id, [FromBody] Person person)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
if (id != person.Id) return BadRequest();
db.Entry(person).State = EntityState.Modified;
await db.SaveChangesAsync();
return Updated(person);
}
However, the Person object is null in the controller because it does not recognize the [NotMapped] FullName property of Person. I presume this, because if I remove this property from the JSON before POST'ing the data to the controller the model binder properly populate the Person object.
Is there a simple way to tell the model binder to simple ignore extra properties that do not exist as mapped properties of the model it is trying to bind, or do I have to write a complete custom model binder to do this?
Edit
Here is an example JSON payload that results in the model not binding, and the Person entity in my controller is null:
{
"#odata.context": "https://localhost:44304/rest/$metadata#Persons/$entity",
"Id": 1,
"FirstName": "Jimmy",
"LastName": "Johnson",
"FullName": "Jimmy Johnson",
}
Note the FullName property is [NotMapped]. When included in the JSON payload in my controller, it will not deserserialize. The person object is Null. Also notice there #odata.context does not interfere with the deserialization, even though it is not in the model. I assume the Odata inputformatter knows to expect that and ignores it.
If I send the same payload as above, but omit the FullName property, the JSON deserializes and the person object is populated.
I would like to figure out how to make it so that FullName or any [NotMapped] property is ignored, rather than causing the deserialization to fail. I am hoping for a solution that doesn't make me have a custom inputformatter for each entity type, and one that does not require me to make changes for every new [NotMapped] property I add to any of my models. It seems like there should just be an option in System.Text.Json options that tells the deserializer to ignore properties that don't exists for the model, just like #Odata.context which is ignored. However, I have not seen such an option.

.Net Core API Endpoint not allowing QueryString parameters

Very possible this is a duplicate, but I've looked and can't find an answer. The first answer here looked promising: Query string not working while using attribute routing But I tried that and it didn't work.
[HttpGet, Route("api/machine/byid/{id=id}/{pageNumber=pageNumber}/{pageSize=pageSize}/{fields=fields}")]
public string ById(int id, int pageNumber, int pageSize, string fields)
// code removed
}
This works:
https://localhost:44303/api/machine/byid/1/2/3/a,b,c
This does not:
https://localhost:44303/api/machine/byid?id=1&pageNumber=2&pageSize=3&fields=a,b,c
The second url returns:
{"type":"https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"|bf12950b-472923d3a24062d1.","errors":{"id":["The value 'id' is not valid."],"pageSize":["The value 'pageSize' is not valid."],"pageNumber":["The value 'pageNumber' is not valid."]}}
You would need two routes:
[HttpGet("api/machine/byid")]
public string ById(
[FromQuery("id")] int id,
[FromQuery("pageNumber")] int pageNumber,
[FromQuery("pageSize")] int pageSize,
[FromQuery("fields")] string fields)
{
}
Follow this link for more informations
The example you provided demonstrates route parameters. There is a distinct difference between route parameters and query parameters.
To accomplish query parameters, you can the [FromQuery] attribute to your method parameters. This will allow for the query parameter example that you provided,
Example : https://localhost:5000/api/persons?firstName=bob&lastName=smith
You can also provide default values for these from within your method parameters. You can string multiple query parameters together in one action.
For route parameters, the parameters are provided via the route itself.
Example : https://localhost:5000/api/persons/23
These parameters are defined from within the [HttpGet("{id}")] attribute on your controller action. You can also constrain the parameter to a certain type, such as an int. This is achieved by adding a colon and specifying the type. Example [HttpGet("{id:int}")]. No further attributes are required to be added within your method parameters for route parameters.
Of course you must also declare these parameters in your method parameters, for both types.
// "/api/persons/23"
[HttpGet("{id}")]
public async Task<IActionResult> GetPersonById(int id)
{
// Code ...
}
// "/api/persons?firstName=bob&lastName=smith"
[HttpGet]
public async Task<IActionResult> GetPersonByName([FromQuery] string firstName = null, [FromQuery] string lastName = null)
{
// Code here... both firstName and lastName can now be optional or only one provided
}
The answer by sturcotte06 was close, but was not 100% Core compliant. This works:
[HttpGet, Route("api/machine/byid/{id=id}/{pageNumber=pageNumber}/{pageSize=pageSize}/{fields=fields}")]
public string ById([FromQuery] int id, [FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] string fields)
{
// code removed
}

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.

How to convert QueryString to a Model with Mvc4 not via controller

I would like to imitate after the controller binding process and bind the query string into a model but not via controller.
I have access to Request.QueryString from type NameValueCollection.
How can i force it to get bind the same as mvc4 binding the models via the controller.
For example i have this class:
public class Example
{
public string Name {get;set;}
public string LastName {get;set;}
}
and NameValueCollection(Request.QueryString) that created by the Request object from the url that is look like ?Name=James&Lastname=Bow.
Any suggestions?
Why not create an ActionFilter then override the OnActionExecuted method. ActionExecutedContext would have access to Request.QueryString. You can then populate the filterContext.Controller.ViewData.Model from the query string.

How to hide a Model class field based on custom logic in MVC Web Api RC

I am using Asp.Net Mvc Web api RC.
I wanted to hide the fields/properties of my model class using custom attribute. Below is my class:
public class Employee
{
public int EmpId { get; set; }
public string Name{ get; set; }
//Wanted to hide this attribute based on custom logic. Like for a certain role, i want to hide the designation
public string Designation{ get; set; }
public string Department{ get; set; }
}
How can we achieve using Data Annotations. I mean i wanted to create a separate attribute to use in this manner:
[HideForRoles(Roles="Admin,Writer")]
public string Designation{ get; set; }
UPDATE :
As i am developing web api. The response is serialized to either XML or Json format depend upon the formatter. So better question would be how not to allow the fields to be serialize while writing to the response.
However one option could be using IgnoreDataMember attribute. Like
[IgnoreDataMember]
public string Designation{ get; set; }
But the above is a compile time declaration where i cannot impose any condition.
Question: How to ignore the field/property while serializing based on some condition at runtime?
Totally missed on the first go-round that you were using Web Api, my apologies.
What you want to do is to create a custom formatter.
There's a good article here on the flow/differences between MVC and Web Api (which I'm getting that you already understand, still some valid points here):
http://lostechies.com/jimmybogard/2012/04/10/asp-net-web-api-mvc-viewmodels-and-formatters/
And here's a sample implementation of a custom formatter:
http://www.tugberkugurlu.com/archive/creating-custom-csvmediatypeformatter-in-asp-net-web-api-for-comma-separated-values-csv-format
Building from that, you would use reflection to read from the attributes, building on the custom ActionFilterAttribute you would have to write, where you evaluate the user's roles and determine which fields should be omitted/included. Here's a sample of an action filter:
https://github.com/MisterJames/MovieFu/blob/master/MovieFu/ActionFilters/UserNameFilter.cs
Hope this helps more.
Cheers.
Your best bet is to return a dynamic object. In this case you can say:
dynamic viewModel = new ExpandoObject();
viewModel.Id = 12;
if(role == "Admin")
{
viewModel.SecureStuff = "Others should not see it";
}
It won't be as straightforward as that, as you'll need to have the fields conditionally rendering in the view. But you can get most of the way there through the attribute.
You will need to make your custom attribute meta-data aware, then check the attribute in your view. A solution is posted here: Can't get Custom Attribute Value in MVC3 HTML Helper.
Cheers.
I have done the authorization checking in the model repository itself. Rather ideal way was to create custom formatters for hiding the certain fields based on some condition.
After getting the list of Employees from db and have them in list, i iterated again and place a NULL to the fields i don't want to display.
The code i have written as:
foreach (var employee in listEmployees)
{
//get all props. of Employees object using reflection
var props = employee .GetType().GetProperties();
//loop through each field to match with the field name to remove/place null
foreach (var propertyInfo in props)
{
var fieldName = propertyInfo.Name;
if (fieldsNamesToRemove .Contains(fieldName))
{
propertyInfo.SetValue(employee , null, null);
}
}
}
here fieldsNamesToRemove is a list that i created dynamically based on roles of current user.
This solution actually placing a NULL for the fields we do not want display. As a result in JSon format the fields are not displaying but in the XML the fields are displaying with syntax like lt; Designation i:nil="true"/ gt;, but manageable as we need to deal mostly with json response.
Thanks Ali and MisterJames for your valuable suggestions