Model Binding ignoring properties that have the JsonIgnore attribute - c#

I'm building a web api microservice using Core 3. I have a class defined as follows:
public class UserSourceList
{
[JsonIgnore]
public string UserId { get; set; }
[JsonIgnore]
public string ListId { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public ListTypes ListType { get; set; }
public List<string> Ids { get; set; }
public DateTimeOffset CreationTime { get; set; }
}
When the framework attempts to bind the data provided by a HTTP PUT, it will not populate the UserId and ListId fields. As a result, model binding is failing during validation and returning a HTTP 400, stating that UserId and ListId are required.
The controller's action method is defined as follows:
[HttpPut("{userId:userid}/{listId:listid}", Name = "ReplaceUserList")]
public ActionResult Replace(string userId, string listId, UserSourceList model)
{
return Ok(_listManager.ReplaceUserList(model.UserId, model.ListId, model));
}
A typical call to the API would look similar to this:
PUT /api/v1/listmgmt/abc123def456/c788f2f7b7984424910726d4a290be26
PUT Body
{
"name": "Test",
"listType": "Eans",
"ids": ["97814571867716", "9781430257615", "9780982550670"],
"userId":"abc123def456",
"listId":"c788f2f7b7984424910726d4a290be26"
}
If I removed the JsonIgnore Attribute from the UserId and ListId properties of the model, everything binds as expected.
Is it expected behavior that model binding will ignore fields flagged with JsonIgnore?
I know I can work around it by changing how my validation code works or I can split my model. I would like to understand the current behavior as it is different from what I expected and experienced with ASP.NET MVC 4 and WebApi 2.
Thanks

Short answer, Newtonsoft Json.Net is being used to deserialize the post/put body when the content type is application/json. Therefore, the userId and listId parameters are being ignored during deserialization, but evaluated during model validation.
I removed the JsonIgnore Attribute as well as all the Data Annotations, and changed to the FluentValidation package which provided the ability at runtime to configure how the body should be validated based up the type of call made.

I think the reason is because of this:
[HttpPut("{userId:userid}/{listId:listid}", Name = "ReplaceUserList")]
userId and listId are required and cannot be ignored because they are defined in the annotation HttpPut. I think you need to remove them from HttpPut's parameters and find another way to get around this.
Hope this helps!

Related

How to ignore a property on Swagger body input but still show on responses?

I am using Swashbuckle and Swagger UI to automatically generate API documentation.
My person endpoint uses the following PersonViewmodel:
public int? ID { get; set; }
public string Name { get; set; }
My endpoint method takes in a PersonViewmodel as the body and returns a PersonViewmodel using an IActionResult. However, I don't want the user defining an ID as that is generated by the business logic. In the code if an ID is set it is ignored.
How can I change the Swagger UI to not show the ID in the Example value for the body input but still show the ID for the Example value for the responses?
I have found many ways to remove properties completely from Swagger UI such as [JsonIgnore] or setting the property to internal or private. But how can I remove a property from the input example in swagger but keep it in the output/responses example?
There is a better way now with now. There are two things you need to do
Annotate with SwaggerSchema found in Swashbuckle.AspNetCore.Annotations;
[SwaggerSchema(ReadOnly = true)]
public int Id { get; set; }
EnableAnnotations in AddSwaggerGen
builder.Services.AddSwaggerGen(options =>
{
options.EnableAnnotations();
});
See more about it in the Documentation
Please use the attribute [BindNever] above the property like
[BindNever]
public int? Id {get;set;}
Also, please Check if you're using Newtonsoft.Json to serialize, that could be the reason your System.Text's JsonIgnore attribute didn't work.

Dynamic type model binding in ASP.NET Core

I'm currently working on a content editor that can be used for multiple types of content, where a developer could specify their own model. For example, a model might look like this:
public class ImageWithCopyWidgetModel : WidgetModel, IWidgetModel
{
public string ImageUrl { get; set; }
public string ImageAltText { get; set; }
public string HeaderText { get; set; }
public string BodyContent { get; set; }
}
On the editor side, I have a view model that looks like:
public class EditContentViewModel<TModel> where TModel : IWidgetModel
{
public int Id { get; set; }
public string Name { get; set; }
public TModel WidgetModel { get; set; }
}
I have the binding on the GET/form display side working fine. My issue comes with getting the model binder to accept the data on the POST? I've tried the following, but each returns null for model.WidgetModel:
// Option 1
EditContent(int pageId, int id, EditContentViewModel<dynamic> model)
// Option 2
EditContent(int pageId, int id, EditContentViewModel<object> model)
// Option 3
EditContent(int pageId, int id, EditContentViewModel<IWidgetModel> model)
Note, for testing purposes, I tried explicitly setting the type of WidgetModel to a concrete class (the ImageWithCopyWidgetModel noted above) and that works.
I'm really trying to avoid having to use Request.Form here as its going to limit future plans for this implementation.
What you're wanting is not possible, at least out of the box. On post, all the modelbinder has is a bunch of key-value pair string. What informs its decision about how to bind those values to something useful is the action param(s). Specifically, it has no way of knowing that it should actually create an instance of ImageWithCopyWidgetModel when you're binding to EditContentViewModel.
Also, the modelbinder is designed to discard values it doesn't know what to do with. That means that it's unfortunately not even possible to cast to ImageWithCopyWidgetModel after the fact, because all properties not present on EditCopyViewModel would have been discarded by that point.
Your best bet is a custom model binder, but the implementation of that is too broad for the scope of Stack Overflow. I suggest you refer to the documentation.

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
}

Model [FromBody] and Guid mapping in Web Api 2

I'm implementing web api 2 and I found strange behaviour of the Guid mapping. Here is my problem definition
This is my model example
public class MyModel
{
public Guid Id { get; set; }
public string Name { get; set; }
}
I have following action on my controller where I have MyModel as input [FromBody]
[HttpPost, Route("create")]
public IHttpActionResult Create([FromBody]MyModel model)
{
// some implementation
}
Everything works fine instead of Guid mapping. When I post JSON of a new MyModel in the request body :
{
"Id":"1d93dfa2-sb34-403d-a766-bdcf1cf47a71",
"Name":"name"
}
Name is set correctly as "name" but the Guid is every time generated as a new Guid.
What can cause this issue please ? How can I set correct mapping of the Guid value?
The problem is that 1d93dfa2-sb34-403d-a766-bdcf1cf47a71 is not a valid Guid.
Guids only contain 0-9 and a-f, whereas your string has an s in it and is therefore not a valid Guid so the Model Binder does not bind anything to that property in your model.

WebApi POST not to include ID field

I am still just a couple days into ASP.NET and WebAPI frameworks so I must be missing out something really simple.
I have a model that has a couple properties and ID (as a property, which has a private setter but that didn't help).
public long ID { get; private set; }
[Required(ErrorMessage = "Location coordinate X is required.")]
public double X { get; set; }
[Required(ErrorMessage = "Location coordinate Y is required.")]
public double Y { get; set; }
And then I have a controller method post:
public HttpResponseMessage Post(MyModel model)
When I start the project and go to auto-generated API documentation, I can see that samples include ID as an input field. I want API to ignore ID input field. I could just ignore it myself but I don't like such must-remember-not-to-use things in my code.
One option would be to create a separate model just for the input but it would mean I have to maintain two classes instead of one.
Is there any data annotation to ignore this property entirely?
Try with:
[ScaffoldColumn(false)]
The ID property will no longer be seen by the html helpers. However, the model binder might still try to move a value into the ID property if it sees a matching value in the request.
So you decorate it with Exclude to avoid property to be binded:
[Exclude]
public long ID { get; set; }
You can also , (inside your Post function) remove the property from state:
ModelState.Remove("Id"); // Key removal
if (ModelState.IsValid)
{
}
}

Categories

Resources