4

First of all, sorry about my English. Not my natural language.

I have a class named Category like the code below. Note that in this code I also have a Category property, in which I can reference a father Category. It's just the same class declared inside the class. Like Inception. So, this Father Category object has the same properties of "the class that declared him."

The property Name is Required. Remember this property.

public class Category
{
    public int? Id{ get; set; }

    [DisplayName("Father Category")] //NOT REQUIRED
    public Category Father { get; set; }

    [DisplayName("Category")]
    [Required(ErrorMessage = "Name is required")] //REMEMBER THIS REQUIRED PROPERTY!!
    public string Name { get; set; }

    [DisplayName("Status")]
    public bool Status { get; set; }

    [DisplayName("Description")]
    public string Description { get; set; }
}

That's my Class.

So, in the Category View, I can do something like this:

Note: CompleteEditorFor and CompleteDropDownListFor are extended methods which add some extra html in each field, just for adjusting the layout.

@using (Html.BeginForm(null, null, FormMethod.Post))
{

    @Html.CompleteEditorFor(x => x.Name)
    @Html.CompleteEditorFor(x => x.Description)
    @Html.CompleteEditorFor(x => x.Status)

    @Html.CompleteDropDownListFor(x => x.Father.Id, ViewData["Categories"], "Id", "Name", "Select...")

    @Html.SubmitButton()
}

The code above runs just fine.

Now there's the problem:

When I click the Save button, it makes a HttpPost, and this is the Action for that:

(The code below has some modified message strings and extended methods.) (CategoriesBLL is the class that gets the categories from Database.)

    [HttpPost]
    public ActionResult New(Category item)
    {
        ViewData.Add("Categories", CategoriesBLL.select());
        try
        {
            if (ModelState.IsValid)//PROBLEM IS HERE!!!!!!!!!
            {
                if (CategoryBLL.insert(item) != 0)
                {

                    ViewData.AddSuccess("Some Success Message");


                    return View(new Category());
                }
                else
                {
                    ModelState.AddError("Some error message");
                    return View(item);
                }
            }
            else
            {
                ModelState.AddError("Some SERIOUS error message");
                return View(item);
            }
        }
        catch (Exception ex)
        {
            ModelState.AddError("Some EVEN MORE SERIOUS message");
            return View(item);
        }

    }

The problem is at the if (ModelState.IsValid) line.

Why?

Because my Category class has a required property called Name. I don't need this property to do what I'm doing, I just need the Id of the Father property. I can get this Id in this line on the View:

@Html.CompleteDropDownListFor(x => x.Father.Id, ViewData["Categories"], "Id", "Name", "Select...")

And that works.

But the Name property is null, and is Required, but it's only Required when I'm informing the Child class. Not the Father class.

I don't even know how to search for it on Google or StackOverflow...

Can anyone help me?

2
  • have you tried to remove the child entities before validating or not posting them with the form? Commented Mar 22, 2013 at 20:09
  • I need to post the father entity, but I just need 1 property, the Id property. Name is also required but not in this father entity. Commented Mar 22, 2013 at 20:20

4 Answers 4

2

You need to use View Models. Your view model should contain all the fields that you need on the view, minus Father property. Then in your controller you'll need to map view model to your model. You can easily do this with Automapper. (However, direct mapping from view to domain model is not recommended, but that you'll sort out later)

I know, at first this might look like a drag, but trust me. Your view is not the same as your domain model. Next thing you'll know, you'll need some sort of drop-down on your view and some other extra checkbox that you don't have in your domain model. Also, if you use view model, your security will be improved. I'll get you some info about that if interested.

Sign up to request clarification or add additional context in comments.

5 Comments

Security Improvement? I'm interested!
Read this codetunnel.com/blog/post/aspnet-mvc-model-binding-security First 2 paragraphs will tell you what can go wrong. The rest of the article is recommended to read as well. I have seen a video with Troy Hunt where he elaborated in details about that, but can't find it now -(
Yeah, if your project is short-lived and not going to be developed in the future, then switching everything to the view models might not be worth it. Yes, you can email me: trailmax1 at gmail dot com. I'll try answering your questions.
Actually i've tried to implement view models, now it's working fine! Doesn't take much time, and Automapper is cool :D
4 years later, this is still very valuable. However, that link is not working anymore. So here's the new one: codetunnel.io/aspnet-mvc-model-binding-security
2

You may want to consider making the class IValidatableObject interface on your model instead of property decorators. If you really want to use a property decorator then you might hav eto right a custom one.

1 Comment

Will it change the way I validate this model, or the ModelState validator will automatically know what to do?
1
public class Category : IValidatableObject
{
    public int? Id{ get; set; }

    [DisplayName("Father Category")] //NOT REQUIRED
    public Category Father { get; set; }

    [DisplayName("Category")]
    [Required(ErrorMessage = "Name is required")] //REMEMBER THIS REQUIRED PROPERTY!!
    public string Name { get; set; }

    [DisplayName("Status")]
    public bool Status { get; set; }

    [DisplayName("Description")]
    public string Description { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var results = new List<ValidationResult>();
        if (this.Enable)
        {
            Validator.TryValidateProperty(this.Name ,
                new ValidationContext(this, null, null) { MemberName = "Name" },
                results);
            Validator.TryValidateProperty(this.Status,
                new ValidationContext(this, null, null) { MemberName = "Status" },
                results);
        }
        return results;
    }
}

public void Validate()
{
        var toValidate = new Category()
        {
            Name = "Just a name",
            Status = true
        };

        bool validateAllProperties = false;

        var results = new List<ValidationResult>();

        bool isValid = Validator.TryValidateObject(
            toValidate,
            new ValidationContext(toValidate, null, null),
            results,
            validateAllProperties);
}

3 Comments

no, you don't. Controller MVC stack checks for IValidatableObject automatically and executes the validation for you.
@IamStalker what's with public void Validate() outside of the class? Should it be inside of the class? if inside of the class, I don't see it being called anywhere.
I think this method is just for testing.
1
ModelState.Remove("PropertyNameInModel");

or

ModelState.Remove<ViewModel>(x => x.SomeProperty);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.