Bind complex object from query string in post request - c#

I have a class for holding both raw and encrypted values from query parameters:
public class SID
{
[FromQuery(Name = "sidInt")]
public int RawValue { get; set; }
[FromQuery(Name = "sid")]
public string EncryptedValue { get; set; }
public static implicit operator int(SID model)
{
return model.RawValue;
}
}
Having this I can successfully use it as a parameter for get requests:
/// GET: /{controller}/index?sid=my-encrypted-string
[HttpGet]
public IActionResult Index(SID id){
int resourceId = id;
//rest of the action code
}
Only encrypted value is provided in query string, raw integer value is automatically decrypted and added to the query string in my custom middleware. Notice that thanks to named [FromQuery] attributes I can use any parameter name in the action.
So far so good. But now I want to use the same SID class as a property in model for post request:
public class MyPostModel {
[Required]
[FromQuery]
public SID Id { get; set; }
public string Name { get; set; } // posted in body
// rest of the properties
}
/// POST: /{controller}/index?sid=my-encrypted-string + other fields in the body
[HttpPost]
public IActionResult Index(MyPostModel model){
int resourceId = model.Id;//model.Id is null here
//rest of the action code
}
But unfortunately I cannot make it working, Id in the model is not bound. I feel like I'm missing something obvious here. Is it possible? Do I need a custom model binder for this(I hope not, I want to use SID class as a property for several models and for different actions)?
Of course I can add sid parameter to the post action and reassign it in the action body explicitly but it looks wrong and too verbose:
[HttpPost]
public IActionResult Index(SID id, MyPostModel model){
model.Id = id;//id is correctly populated but model.Id is not
//rest of the action code
}

I think that your problem is that parameter is called "sid" and property in MyPostModel is "Id". Aside from that, I don't see any reason why it would not work. Can you change your MyPostModel:
public class MyPostModel {
[Required]
[FromQuery(Name = "sid")]
public SID Id { get; set; }
public string Name { get; set; } // posted in body
// rest of the properties
}

Related

How to pass a file and json object from a postman to asp.net core webapi

I have a post method with below signature,
[HttpPost]
public ActionResult SavePriorAuthorization(MainPriorAuthorization priorAuthorization, IFormFile file)
Now I want to pass the object along with the file from a postman. I have tried the following option which doesn't work.
This gives an error, System.ArgumentNullException: Value cannot be null.Parameter name: header
Header Type : multipart/form-data
Any help would be appreciated.
What I usually do is create a ViewModel like this one:
public class MainPriorAuthorizationViewModel
{
public IFormFile File { get; set; }
public string TestName { get; set; }
}
Then create an action with [FromForm] attribute so that it knows from where it needs to mapped:
[HttpPost]
public void Post([FromForm]MainPriorAuthorizationViewModel priorAuthorization)
{
//do logic
}
Then in my postman its look like this:
Hope this helps
Try to change the settings of the key in the MainPriorAuthorization model, you could directly set properties name of the model as the key in Postman.
The following is the example code that I tested and worked well:
Guest Model
public class Guest
{
public int Id { get; set; }
public string Name { get; set; }
}
Add the [FromForm] attribute to the parameter in action
[HttpPost]
public void SaveGuest([FromForm]Guest guest,IFormFile file)
{ }
The screenshot of the Postman

Why is [FromQuery] attribute required?

I created an api with the following action:
[HttpGet("GetStuff/{Name}")]
public ActionResult<string> GetStuff([FromRoute]GetStuffModel requestModel)
{
if (requestModel == null) return BadRequest();
var result = doStuff();
return Ok(result);
}
The model looks like this:
public class GetStuffModel
{
public string Name { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public double MyNumber { get; set; }
}
Now I am using swagger to test this and basically the above doesn't work as I think it should. What happens is when I submit a get request with swagger and look at the values in the model, I find that only Name is captured. Latitude and Longitude have a value of 0. However, if I change the model to this:
public class GetStuffModel
{
public string Name { get; set; }
[FromQuery]
public double Latitude { get; set; }
[FromQuery]
public double Longitude { get; set; }
public double MyNumber { get; set; }
}
Then everything gets captured. My question is why do I have to specify [FromQuery] when I have already declared [FromRoute] in the controller?
Edit: I also added MyNumber variable and that one also picks up without the need of [FromQuery]
For [FromRoute] attribute model binder tries to bind values from request route data. With the current setup route data contains only 1 value for Name (well, there also action and controller values there, but it doesn't matter now) so it's possible to bind only Name property of GetStuffModel (if you add string Action property to model you'll see it will be binded as well). When you add [FromQuery] attribute to model properties it overrides model binding behavior and allows to bind certain properties from query string. In this case swagger adds Latitude and Longitude as query parameters and binding works fine.

ASP.NET Core: [FromQuery] usage and URL format

I am trying to use [FromQuery] in my web api and I am not sure how to use it.
Here's the GetAllBooks() method in the controller:
[HttpGet]
[Route("api/v1/ShelfID/{shelfID}/BookCollection")]
public async Task<IActionResult> GetAllBooks(string shelfID, [FromQuery] Book bookinfo)
{
//do something
}
Here's the Book model class:
public class Book
{
public string ID{ get; set; }
public string Name{ get; set; }
public string Author { get; set; }
public string PublishDate { get; set; }
}
I am confused about whether it is the correct way to use [FromQuery]. I thought the URL is
https://localhost:xxxxx/api/v1/ShelfID/{shelfID}/BookCollection/IActionResult?ID="123"&Name="HarryPotter"
But the break point is not hitting my controller method, so I was thinking maybe the URL is not correct. Any suggestions? Thanks!
The name of the method and return type are completely ignored when you define your route explicitly via attributes like that. IActionResult shouldn't be there.
The correct URL would be: https://localhost:xxxxx/api/v1/ShelfID/{shelfID}/BookCollection?ID="123"&Name="HarryPotter"
Furthermore, query string binding only works out of the box for primitive types (string, int, etc). To bind a class to a query string you'd need a custom modelbinder, which is quite involved.
It would be better to just explicitly declare the properties you want to pass in:
public async Task<IActionResult> GetAllBooks(string shelfID,
[FromQuery] string ID,
[FromQuery] string Name)

Asp.net core model doesn't bind from form

I catch post request from 3rd-side static page (generated by Adobe Muse) and handle it with MVC action.
<form method="post" enctype="multipart/form-data">
<input type="text" name="Name">
...
</form>
Routing for empty form action:
app.UseMvc(routes => routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}"));
But in according action I have model with every property is empty
Action:
[HttpPost]
public void Index(EmailModel email)
{
Debug.WriteLine("Sending email");
}
Model:
public class EmailModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Company { get; set; }
public string Phone { get; set; }
public string Additional { get; set; }
}
Request.Form has all values from form, but model is empty
[0] {[Name, Example]}
[1] {[Email, Example#example.com]}
[2] {[Company, Hello]}
[3] {[Phone, Hello]}
[4] {[Additional, Hello]}
Be careful not to give an action parameter a name that is the same as a model property or the binder will attempt to bind to the parameter and fail.
public async Task<IActionResult> Index( EmailModel email ){ ... }
public class EmailModel{ public string Email { get; set; } }
Change the actions parameter 'email' to a different name and it will bind as expected.
public async Task<IActionResult> Index( EmailModel uniqueName ){ ... }
I'm not sure it is same case, but I had same problem and nothing really looks to work for me.
The problem in my case was that I had a property called Model in my view model class
public string Model { get; set; }
When I renamed the property to ModelName everything was working fine again, even without FromForm attribute.
Looks like some special property names could be a bit of a problem for asp.net mvc model binding.
So, my advice is to check your model properties and maybe try renaming them one by one in order to check if the problem is there.
Hope this helps.
ASP.Net Core 3.1
In my case, I was using a complex model (a model that contains other models, like a shared model) posted by Ajax, so the inputs in the view were automatically named like this "ChildModel.PropertyName" (see code)
[HttpPost]
[ValidateAntiForgeryToken] // ("AUVM.PropertyName")
public async Task<JsonResult> AddUser([FromForm]AUVM aUVM) //Parameter name must match the first part of the input name in order to bind
{
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<JsonResult> AddUser([FromForm]AUVM someUniqueName) //This is wrong and won't bind
{
}
I'm having the same problem
this docs helps me to understand Model Binding
https://docs.asp.net/en/latest/mvc/models/model-binding.html
I solved my problem by making sure that the property name is exact match in form field name
and I also add [FromForm] attribute to specify exactly the binding source.
I ran into this today, and though in hindsight it seems obvious, just thought I'd add that you need to make sure your access modifiers for the Properties on the model you're binding are correct. I had public MyProperty { get; internal set; } on some and they would not bind. Removed internal and they worked just fine.
Change void to ActionResult.
[HttpPost]
public ActionResult Index(EmailModel email)
And don't forget verifying AntiForgeryToken from your view and action.
// to your form in view
#Html.AntiForgeryToken()
// ------------
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(EmailModel email)
This issue can also happen if one or more of your properties in the request model fail to bind into an acceptable on the request model.
In my case, I was supposed to pass a List<string> type to a property, but accidentally passed a string. This resulted in the entire request model becoming null
In my case, I was using the MVC 4.0 convention of naming the object you are posting. E.g.,
js: $http.post("SaveAnswer", { answer: answerValues })
C#: public ActionResult SaveAnswer([FromBody] AnswerVM answer) {...}
When I changed the js to $http.post("SaveAnswer", answerValues), everything works.
I have run in to this issue where the model does not bind when I have more than one constructor on the underlying model like:
public class EmailModel
{
public EmailModel()
{}
public EmailModel(string _name, string _company)
{
Name = _name;
Company = _company;
}
public string Name { get; set; }
public string Email { get; set; }
public string Company { get; set; }
public string Phone { get; set; }
public string Additional { get; set; }
}
Fixed by removing all but one constructor like this:
public class EmailModel
{
public EmailModel()
{}
public string Name { get; set; }
public string Email { get; set; }
public string Company { get; set; }
public string Phone { get; set; }
public string Additional { get; set; }
}
I had the same problem and I want to share what happened in my case and how I got the solution. Perhaps, someone may find it useful to know.
My context was ASP.NET Core 3.1 Razor pages.
I had a handler as
public async Task<JsonResult> OnPostLogin([FromForm] LoginInput loginData)
{
}
and also I had a property in my ViewModel as
public LoginInput LoginInput { get; set; }
And in my Razor page, I have used the form like this:
<form asp-page-handler="Login" method="post">
<input asp-for="LoginData.UserNameOrEmail">
.....
So, notice that, in my form, I used the property LoginInput.UserNameOrEmail, but in my handler's parameter, I used the parameter name loginData. How will the ASP.NET Core know that, this LoginInput.UserNameOrEmail is actually loginData.UserNameOrEmail. It can't, right? Therefore, it did not bind my form data.
Then, when I renamed my ViewModel property "LoginInput" to the same name as the handler parameter, like this:
public LoginInput LoginData { get; set; }
The ASP.NET Core then found that the form data was matching the parameter name and then it started to bind properly. My problem was solved.

Model parameter not recognized by controller method

I have a model called PostUserAccount, and I'm trying to use it in an ApiController as a parameter the way it normally is when you generate a controller with read/write actions, using Entity Framework
Example generated by controller generator:
// POST api/Profile
public HttpResponseMessage PostUserProfile(UserProfile userprofile)
{
if (ModelState.IsValid)
{
db.UserProfiles.Add(userprofile);
...etc
code that I'm working with:
// POST gamer/User?email&password&role
public HttpResponseMessage PostUserAccount(PostAccountModel postaccountmodel)
{
if (!ModelState.IsValid)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, ModelState);
}
if (postaccountmodel == null) return Request.CreateResponse(HttpStatusCode.BadRequest, "model is null");
...etc
for whatever reason, the postaccountmodel is null in this case, and running this api command returns "model is null". any ideas?
Here is the model in question
public class PostAccountModel
{
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
public string Role { get; set; }
public string Avatar { get; set; }
public string DisplayName { get; set; }
}
You're trying to send the model in the URI query string. Problems:
Your query string is not well formed - should be ?Email=xxx&Password=xxx& ...
You need to decorate the postaccountmodel parameter with the [FromUri] attribute, to tell Web API to bind the model from the URI
Another option is to send the model in the request body as JSON or XML. I'd recommend that, especially if you're really sending a password in the request. (And use SSL!)
This topic describes parameter binding in Web API: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Try putting the [FromBody] attribute in front of your postaccountmodel parameter.
http://msdn.microsoft.com/en-us/library/system.web.http.frombodyattribute(v=vs.108).aspx

Categories

Resources