12

I do not want do bind the Id property on my CustomerViewModel so I added a [BindNever] attribute but it is not working. What could be the solution?

I have the following:

CustomerController.cs

// PUT api/customers/5
[HttpPut("{id}")]
public async Task<IActionResult> Put([FromUri] int id, [FromBody]CustomerViewModel customer)
{
  //Implementation
}

CustomerViewModel

public class CustomerViewModel
{
    [BindNever]
    public int Id { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string Email { get; set; }
}

If I input the following json . The id property still gets binded

{
  "id": 100,
  "lastName": "Bruce",
  "firstName": "Wayne",
  "email": "bruce@gothamcity.com"
}
Set
  • 47,577
  • 22
  • 132
  • 150
bman
  • 3,740
  • 9
  • 34
  • 40

4 Answers4

16

This Blog post is an interesting read and concludes that the [FromBody] annotation "overrides" the BindBehaviourAttribute (BindNever is a simple specialization). The model is populated by all data available from the body (your JSON data in this case).

I do not consider this as intuitive, and the issue has a nice statement about this:

[BindRequired] customizes the MVC model binding system . That's its purpose and it's working as designed.

[FromBody] switches the affected property or parameter into the different world of input formatting. Each input formatter (e.g. Json.NET and a small MVC-specific wrapper) can be considered a separate system with its own customization. The model binding system has no knowledge the details of JSON (or any other) deserialization.

Lesson learned: BindNever does not work in this scenario.

What are alternatives ?

Solution 1: Writing some custom model binding code. I have not done it myself, but What is the correct way to create custom model binders in MVC6? may help.

Solution 2: Rather pragmatic one

Perhaps this simple (but not very nice) workaround helps you out:

[HttpPut("{id}")]
public async Task<IActionResult> Put([FromUri] int id, [FromBody]CustomerViewModel customer)
{
    customer.Id = 0;
    //Implementation
}
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Ralf Bönning
  • 14,515
  • 5
  • 49
  • 67
  • 2
    Having a private setter seems to do the trick. I can't validate my solution tho. I tried looking around to find something similar but with no luck. – Jin Dec 05 '17 at 07:23
1

also you could do this

public class CustomerViewModel
{
    public int Id { get; private set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string Email { get; set; }
}
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
0

I add a note.

Now it's officially explained by Microsoft.

https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#attributes-for-complex-type-targets

https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#input-formatters

https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#frombody-attribute

In summary,

If we use the “FromBody attribute (including defaults such as HttpPost attribute)”, it depends on the input formatter and the BindNever attribute etc. will not work.

Instead, we can do so by specifying the attribute that corresponds to the input formatter. For example, for the default json It can be ignored using "System.Text.Json.Serialization.JsonIgnoreAttribute".

-4

Try NotMapped attribute.

Body must be at least 30 characters; you entered 24.

huang
  • 919
  • 11
  • 22