Model [FromBody] and Guid mapping in Web Api 2 - c#

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.

Related

C# Receiving empty object in controller

I'm trying to get some data from the request body in a POST Controller, but the console shows empty props:
The Post Controller:
[HttpPost("{id}/features")]
public ActionResult<bool> AddFeatureAsync(Guid Id, [FromBody] AddRoleFeatureRequest request)
{
Console.WriteLine(request.Name);
Console.WriteLine(request.Description);
Console.WriteLine(request.Id);
return true;
}
The AddRoleFeatureRequest class:
public class AddRoleFeatureRequest
{
public Guid Id;
public string? Name;
public string? Description;
}
The JSON data from Postman (Using body raw as Json):
{
"name": "Feature ABC",
"description": "description",
"id": "7e12b0ad-2c82-46f0-a69e-8538efb0aa60"
}
What am I doing wrong?
I'm trying to get some data from the request body in a POST
Controller, but the console shows empty props:
Your reason for getting null data on your console or in controller is pretty obvious because you have defined your AddRoleFeatureRequest class field only which doesn't allow to set any value on it. For instance, public string? Name; is a field not property. To set value, you must implement valid setter. Thus, it can be treated as valid property and able to assign value into it.
Solution:
public class AddRoleFeatureRequest
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
}
Note: Property without getter and setter will always consider as field, it will not allow you to assign value from outside.
Output:
Note: Modifying your class defination would completely resolve your issue. No other changes required.
Your "AddRoleFeatureRequest" class has capitals and your json data does not. This could be the source of your problems.
the attribute names might be the reasons because they ar different then the json keys, retry it while considering letters cases,

Asp.Net Core MVC - Complex Model not binding on Get Controller action

Got a mismatch somewhere between my View and Controller which is causing the latter to receive a complex object, full of null values.
[HttpGet("find")]
[ProducesResponseType(typeof(PagableResults<UserDetails>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[SwaggerOperation("FindUsers")]
public async Task<IActionResult> FindUsers([FromQuery]FindUsersSearchFilter searchFilters)
And the searchFilters object is defined like this:
public class FindUsersSearchFilter
{
public int? Page { get; set; }
public string Username { get; set; }
public string Firstname { get; set; }
public string Surname { get; set; }
}
The View is sending the data in a querystring (because it's a get method) like so:
/find?SearchFilters.Page=1&SearchFilters.Firstname=foo&SearchFilters.Surname=bar&SearchFilters.Username=
However, if you debug the controller action the breakpoint is hit but the FindUsersSearchFilter received by the method has a null value for every property.
Things I've tried:
Binding(Prefix="SearchFilters") on the controller action.
Binding("Page,Firstname,Surname,Username") on the controller action
Manually changing the URL to remove the prefix and change the capitalisation
Removing [FromQuery]
At a loss as to where to go next. Any suggestions as to what I've got wrong?
The request is wrong. It should be:
/find?Page=1&Firstname=foo&Surname=bar&Username=
When you prefix all your properties with SearchFilters the binding engine is most likely looking for a nested property like searchFilters.SearchFilters.FirstName.
So removing the prefix should make it work.
If you really need to use that syntax in the query; then create another class like this:
public class SearchFilterContainer
{
public FindUsersSearchFilter SearchFilters { get; set; } = new FindUsersSearchFilter();
}
And pass that in the action as the parameter instead like this:
[HttpGet("find")]
[ProducesResponseType(typeof(PagableResults<UserDetails>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[SwaggerOperation("FindUsers")]
public async Task<IActionResult> FindUsers([FromQuery]SearchFilterContainer searchFilters)
Then inside your controller you can access the model like this searchFilters.SearchFilters.FirstName

Model Binding ignoring properties that have the JsonIgnore attribute

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!

Specify a unique identifier attribute for an object across webapi Models

In a POST call to a WebApi I am trying to return a Created(newobject) thing. But there is no signature for Created in ApiController that can only take the object and do the rest.
It works fine if I return something like:
return Created(newobject.blahid.ToString(), newobject);
or if I do a
return CreatedAtRoute("DefaultApi", new { controller = ControllerContext.ControllerDescriptor.ControllerName, id = newobject.blahid.ToString()}, newobject);
I want to simplify this to:
return Created(newobject);
I would need to implement a method in a BaseController
public class BaseController : ApiController
{
protected new CreatedNegotiatedContentResult<T> Created<T>(T content)
{
var id = GetId(content);//need help here
return base.Created(id, content);
}
}
I don't want to worry about the Unique Identifier for an object being called differently in different models e.g. myobjguid, someblahguid etc. I would just want to find it out and mark it as "id".
say if my model is
public class Model_A
{
public List<Model_A> ChildModels { get; set; }
[LookForThisAttribute]//I want something like this
public Guid Model_AGuid { set; get; }
public Guid ? ParentGuid { set; get; }
public List<SomeOtherObject> OtherObjects { set; get; }
}
Is there an attribute([LookForThisAttribute]) or something I can set on all my models to specify that this is the guy to be assumed as THE unique identifier if I ever look for it.
Just like the [Key] attribute in Entity Framework. No matter what you call it, Entity Framework know its going to be the primary key.
So the GetId(T content) method can take the object and return the value of the property that has a [LookForThisAttribute] set?
I ended up writing my own Attribute and then looking up for it in the BaseController.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class UniqueIdAttribute: Attribute
{
}
And in the BaseController Created method:
protected CreatedNegotiatedContentResult<T> Created<T>(T content)
{
var props =typeof(T).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UniqueIdAttribute)));
if (props.Count() == 0)
{
//log this
return base.Created(Request.RequestUri.ToString(), content);
}
var id = props.FirstOrDefault().GetValue(content).ToString();
return base.Created(new Uri(Request.RequestUri + id), content);
}
Mark Gravell's post here helped me with getting the value of the property that has my custom attribute:
How to get a list of properties with a given attribute?
Along with a corresponding unit test for the controllers works fine for me.
Now I can just call Created(anyobject); from all ApiControllers without bothering about the different names people put for their IDs as long as they decorate it with my custom attribute.

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
}

Categories

Resources