2

Here is my model:

public sealed class UserModel
{
    [Key]
    [Required]
    [StringLength(20, MinimumLength = 4)]
    public string Username { get; set; }

    [Required]
    [EmailAddress]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [StringLength(20, MinimumLength = 4)]
    public string Password { get; set; }

    public bool IsValid { get; set; }
}

Username, Email, and Password are provided by the user, however IsValid should only be modified by the server, in other words if the user provides true the server should return BAD REQUEST or not take this value into account.

So, I could set the property to false on model creation, but it is not clean. Is there a way to make a property private or "server only"?

[2018-04-01]: I found a nice article where [BindNever] was used, sadly it does not work with data coming [FromBody] as pointed here. So for the moment I just set the property to false in the POST handler by myself but it is very ugly therefore I'm still looking for other ways to do it!

[2018-04-02]: Finally found ! Check this out !

Alexandre Daubricourt
  • 3,323
  • 1
  • 34
  • 33

2 Answers2

1

Create a view model that the user will interact with directly in the view. Try not to use the model directly in the view as that is basically a leaky design.

public class UserViewModel {
    [Required]
    [StringLength(20, MinimumLength = 4)]
    public string Username { get; set; }

    [Required]
    [EmailAddress]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [StringLength(20, MinimumLength = 4)]
    public string Password { get; set; }
}

On the server side the properties will be copied over to the model when persisting data. Any additional properties that need to be set can safely be done on the server because the model is not being leaked to the client.

For example

public ActionResult Post([FromBody] UserViewModel viewModel) {
    if(ModelState.IsValid) {
        //create new model
        var model = new UserModel {
            Username = viewModel.Username,
            Email = viewModel.Email,
            Password = viewModel.Password
        };

        //or model retrieved from data storage

        //...some other code...

        model.IsValid = true; //only the server can modify this value

        //...
    }

    //...
}

The client side should not even be aware of the IsValid property if it is not suppose to be interacting with it.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Are you sure this is a leaky design ? I'm so disappointed ! It was so clean an beautiful, is this the way recommended by Microsoft ? – Alexandre Daubricourt Apr 01 '18 at 18:40
  • @AlexandreDaubricourt think about it. Does the client side have a reason to manipulate that property? if the answer is no then why send it to the client. Communication should only be *need to know*. – Nkosi Apr 01 '18 at 18:43
  • Of course, the client must not manipulate `IsValid`, this is why there is / should be a way to prevent the binding of some properties. I mean this is done with `[NeverBind]` in some cases, so I think this is a good pattern. – Alexandre Daubricourt Apr 01 '18 at 18:45
1

Finaly found the clean way !

Here is how to achieve it:

The request handler (in your controller):

[HttpPost]
public async Task<IActionResult> PostUserModel([FromBody] UserModel userModel)
{
    if (!ModelState.IsValid) return BadRequest(ModelState);

    _context.Users.Add(userModel);
    await _context.SaveChangesAsync();

    return CreatedAtAction(nameof(GetUserModel), new {id = userModel.Username}, userModel);
}

And the model (UserModel in our case):

[DataContract]
public sealed class UserModel
{
    [Key]
    [DataMember(IsRequired = true)]
    [StringLength(20, MinimumLength = 4)]
    public string Username { get; set; }

    [EmailAddress]
    [DataMember(IsRequired = true)]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [DataType(DataType.Password)]
    [DataMember(IsRequired = true)]
    [StringLength(20, MinimumLength = 4)]
    public string Password { get; set; }

    public bool IsValid { get; set; }
}

We define our model as a data contract, thus, only the [DataMember]s will be bind and serialized ! It is very clean & nice, as the non [DataMember] properties are stored in the database, they are not private or any thing like that, they are just not affected by serialization which means that if you send your model to the client for instance, he'll not see the non [DataMember] fields.

You could also do it this way:

public sealed class UserModel
{
    [Key]
    [Required]
    [StringLength(20, MinimumLength = 4)]
    public string Username { get; set; }

    [EmailAddress]
    [Required]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [DataType(DataType.Password)]
    [Required]
    [StringLength(20, MinimumLength = 4)]
    public string Password { get; set; }

    [JsonIgnore]
    public bool IsValid { get; set; }
}

Of course it is still better than setting the property as false on creation by yourself, but I find the first way better as you don't require Json.Net and it prevents you from forgetting to protect the "private" properties.

Anyway hope this'll help you guys !

You might want to add an [NeverBind] attribute too if you create your models from other sources than JSON [FromBody]

From: prevent property from being serialized in web api

Alexandre Daubricourt
  • 3,323
  • 1
  • 34
  • 33