Comma-seperated model binding - c#

After reading https://www.strathweb.com/2017/07/customizing-query-string-parameter-binding-in-asp-net-core-mvc/
Binding on string array with comma-seperated attribute is a must for us. However, this article only describes how to do this on the method, with simple types. I would prefer to put a attribute on the property on some complex model.
Is this possible?
I can't get it to work otherwise then on the method.
---after edit---
I would like to get this
public class GetCatalogVehiclesRequest
{
[commaseparated]
public string[] Sorting { get; set; }
.....
}
instead of (on controller):
public async Task<IActionResult> Get([commaseperated]GetCatalogVehiclesRequest request, CancellationToken cancellationToken){...}
So on my model instead of an attribute on the parameter.
Any advice?
Thanks!

Related

Post FromBody Always Null

I've got a new API that I'm building with ASP.NET Core, and I can't get any data POST'ed to an endpoint.
Here's what the endpoint looks like:
[HttpPost]
[Route("StudentResults")]
public async Task<IActionResult> GetStudentResults([FromBody]List<string> userSocs, [FromBody]int collegeId)
{
var college = await _collegeService.GetCollegeByID(collegeId);
// var occupations = await _laborMarketService.GetOccupationProgramsBySocsAndCollege(userSocs, college);
return Ok();
}
And here's what my payload that I'm sending through Postman looks like:
{
"userSocs": [
"291123",
"291171",
"312021",
"291071",
"152031",
"533011"
],
"collegeId": 1
}
I'm making sure that I have postman set as a POST, with Content-Type application/json. What am I doing wrong?
You get always null because you need to encapsulate all your post variables inside only one object. Like this:
public class MyPostModel {
public List<string> userSocs {get; set;}
public int collegeId {get; set;}
}
and then
public async Task<IActionResult> GetStudentResults([FromBody] MyPostModel postModel)
If the model is null, check:
1) Where the data is sent: body, form? and based on that add the decorator to the action. For ex:
[HttpPost]
public JsonResult SaveX([FromBody]MyVM vm) { ... }
2) Check ModelState: if it's invalid the vm will not be bound so it will be null.
if (ModelState.IsValid) { ... }
Another reason for the model binding to fail (always null) is if the data type for a property doesn't match. For example here is a simple model:
public class MyService {
public string JobId { get; set; }
public int ServiceType {get; set;}
}
And here is some json that doesn't match:
{"JobId":1, "ServiceType":1}
I got caught with this when I was retrieving the JobId using jquery's .data function, it was automatically converting it to an int. Fixed it by using .attr function instead.
Also, make sure those variables inside your parameter class are declared as Public, (or they'll just keep returning as null)..
If you want to send two or more models, you should use this example:
[HttpPost]
public async Task<ActionResult> addUsuario([FromBody] Newtonsoft.Json.Linq.JObject datos)
{
Usuarios user = datos["usuario"].ToObject<Usuarios>();
Empresas empresa = datos["empresa"].ToObject<Empresas>();
return Json(await _srv.addUsuario(user, empresa));
}
I know it is not related to your case, still, I am posting my answer here.
It is a silly mistake that I had done in my code. I just copied one of my Get requests and changed it to a Post request, and forgot to decorate the parameter with [FromBody]. If anyone else is having the same problem, please make sure that you are decorating the parameter with [FromBody].
[HttpPost]
public IApiResponse Update([FromBody] User user) {
if (user == null) return new ApiBadRequestResponse(ModelState);
return _userService.Post(user) ? new ApiOkResponse(user) : new ApiResponse(500);
}
Assuming the [FromBody] class is made up of primitive data types;
[FromBody] is public
[FromBody] has an empty constructor ()
[FromBody] is serializable.
Make sure that your data transfer object has "Public" as Access modifier and also your oject properties have getter and setter methods.
For me, all values were coming in as null because my object had an enum that was not being parsed. If you have having this problem, I'd recommend turning on enum parsing at the Startup.cs per this SO
In my case it was using Newtonsoft.Json in my body object with JsonProperty attributes for some reason. Changing in to System.Text.Json.Serialization and JsonPropertyName attribute solved the problem

DataMember's Name property is ignored with [FromUri] property in WebApi service

We are creating RestService with Asp.Net WebApi. But for some reason Name property is ignored in DataMember attribute when trying to deserialize complex property with [FromURI] attribute.
For example we might have:
Method:
public IHttpActionResult Get([FromUri]User user)
Model:
[DataContract]
public class User
{
[DataMember(Name = "username")]
public string Username{ get; set; }
[DataMember(Name = "isActive", IsRequired = false)]
public bool? Active { get; set; }
}
When deserializing user we get username as expected, but null for Active. On the other hand when serializing data we get both isActive and username as expected. If we send request with active in query string it works as expected.
It's obviously problem with IModelBinder. It doesn't use DataMember's Name property for some reason. I checked what formaters are included and 4 default ones are registered:
System.Net.Http.Formatting.JsonMediaTypeFormatter
System.Net.Http.Formatting.XmlMediaTypeFormatter
System.Net.Http.Formatting.FormUrlEncodedMediaTypeFormatter
System.Net.Http.Formatting.JQueryMvcFormUrlEncodedFormatter
I don't have a way to check which one is used on request. I would assume that its FormUrlEncodedMediaTypeFormatter but I can't be sure. Also, I am not sure if it even supports Name property.
I already checked for a solution and closest topic I could find was WebAPI DataMember Name not used when de/serializing via application/x-www-form-urlencoded but it doesn't use [FromUri] but application/x-www-form-urlencoded property and it wasn't really solved.
Any ideas, pointers or suggestions would be much appreciated.
Use [FromQuery] instead other attributes.
And model for your request
http://localhost:8080/api/users?username=John&isActive=true
[Route("api/users")]
public class UsersController : Controller
{
[HttpGet]
public IHttpActionResult Get(User user)
{
//...
}
}
Will looks like
public class User
{
[FromQuery(Name = "username")]
public string Username{ get; set; }
[FromQuery(Name = "isActive")]
public bool? Active { get; set; }
}
Anyway best practice is to keep names in model as it parameters names in query.
In this case you dont have to provide "Name" parameter, only keep [FromQuery] on queryClass, and lower casing .Net provide automaticly.
You must check your "get" request. Your get request must be like this;
GET api/foo?username=fooname&active=false
You don't have to DataContract and DataMember attribute to just achieve this. These attribute just for another thing, its not the main reason to use for.
After get valid hit on your get method, in your method you can check modelstate like;
if (ModelState.IsValid) {
/// your code goes here
}

How do I use WebAPI/Rest correctly when other params are needed

I am new to WebAPI and rest and am trying to do things correctly. By default if I were to access something such as User I would call api/user/5 if I wanted user 5. This would go to my User controller to Get(int num) I think. But I know I will often need other params passed as well. Currently I have Get(JObject data), but that data param is for other parameters. I will need other optional params whether I am sending an ID or wanting a list of everything. How do I go about organizing methods properly with WebAPI? Am I misunderstanding something?
To clarify:
This question is more about REST than dynamic objects, though they play a part:
How do I get a single resource vs a list of resources when I need additional params. I see those concepts as two separate methods, but the additional params complicate it in my mind when routing is involved.
Use attribute routing
For example -
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
or
[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }
if you need to return a list, create a method that returns a list, otherwise return the specific item requested
Look into using JToken or the even more dynamic 'dynamic' (Taken from here)
"
JSON and JavaScript is really dynamic, though, and often it's a hassle to try to "deserialize" really dynamic JSON objects into strongly-typed .NET structures. JSON.NET and ASP.NET Web API's model binding offer a happy medium - a middle ground - called JToken.
public class ContactController : ApiController
{
public JToken Post(JToken contact)
{
return contact;
}
}
Using JToken gives a dynamic container but also a DOM-like navigation model. But if that's not dynamic enough for me, why can't my method's parameter just take a "dynamic."
C# is statically typed, sure, but that doesn't mean I can't statically type something dynamic. ;)
Again, note the watch window.
Using dynamic to catch JSON post payloads
public class ContactController : ApiController
{
public dynamic Post(dynamic contact)
{
return contact;
}
}
"
I think you should make a new object for each WebAPI function that will handle the request. You can make the parameters optional with nullable properties.
[HttpPost]
public void SampleFunction(SampleFunctionModel model)
{
}
where SampleFunctionModel is:
public class SampleFunctionModel
{
public int? Id { get; set; }
public string Name { get; set; }
}

Decoration on ViewModel property to use a different name for binding

On MVC3, is there a way to decorate a ViewModel property in order to get the DefaultModelBinder to use a different name for it in the request?
For example, suppose you have the following view model:
public class SomeModel
{
public string Direction {get;set;}
}
But the parameter coming in is Dir from an external source (such as some third-party component, for example).
I know a custom model binder could handle that, but I assume there must be a way to decorate the property, similar to the way action parameters can use Bind(Prefix="...") in order to define that mapping.
You could always create another Property:
public class SomeModel
{
public string Direction {get;set;}
public string Dir
{
get { return this.Direction; }
set { this.Direction = value; }
}
}
I'd also mention that the ViewModel used in a view (cshtml/vbhtml) does not have to be the same ViewModel used on the Post Method.
OK, so after more research looking at similar questions and seeing the feedback here as well, it seems that the answer to my question is basically "NO".
There is no out-of-the-box way, so either custom binders must be used or or the properties should be renamed.
A similar question with a more detailed answer can be found here: How to bind URL parameters to model properties with different names
I was able to accomplish this in ASP.NET MVC Core using the FromForm attribute.
public class DataTableOrder
{
public int Column { get; set; }
[FromForm(Name = "Dir")]
public string Direction { get; set; }
}
Documentation: https://docs.asp.net/en/latest/mvc/models/model-binding.html#customize-model-binding-behavior-with-attributes
However, depending if you do a GET or a POST, you might want to use [FromQuery] instead of [FromForm] I suppose.

MVC 4 Dynamic action method argument

I have the following class definition:
public class CallGroupViewModel
{
public string Name { get; set; }
public string BulkEmails { get; set; }
public List<dynamic> Members {get;set;}
}
The Members property is some dynamic knockout code and the contents change depending on UI stuff. I am wanting to take a short cut since the UI is so up in the air right now.
My problem is that when it get to my action method, everything gets populate, including Members but its a array of objects and I can't get at the individual properties on them.
public ActionResult SaveGroup(CallGroupViewModel group)
group.Members //this has a count
group.Members[0].email //this pukes as 'email' is not valid for 'object'
I'm probably just missing something, any help is appreciated.
Thanks!
The model binding system which populates MVC action method arguments isn't intended to work with dynamic objects. The system requires type information to know which information to bind. Consider changing your model to use strong types instead.
For accessing dynamic properties from the object, in your case you can use :
var email = group.Members[0].GetType()
.GetProperty("Email")
.GetValue(group.Members[0], null);

Categories

Resources