4
votes

I have an ASP.NET MVC 4 app that seems to work fine. I write a custom ValidatorAttribute to make sure the value of one property is not smaller than another. Since there are two properties involved, I override IsValid(object, context).

I write unit tests using Validator.TryValidateObject and the Validate(object, context) member of the attribute, and they pass as expected. I include tests for the expected use with values that are valid and values that are invalid. I include tests where the attribute is applied to a property that is the right type, and get expected behavior (My design choice is to pass if either property type is wrong.)

I add the attribute to my model, hooking it in to the app. Something like:

public abstract class DataElement
{
    ...

    [Required]
    public string Version { get; set; }

    [StringLength(8, ErrorMessage = "8 characters or less")]
    [Required(ErrorMessage = "Required")]
    [DisplayName("ID")]
    public string DataElementNumber { get; set; }
    ...
}

public abstract class SimpleElement : DataElement
{
    [Required]
    [DisplayName("Minimum")]
    public int MinimumLength { get; set; }

    [Required]
    [DisplayName("Maximum")]
    [NotSmallerThan("MinimumLength")]
    public int MaximumLength { get; set; }
}

public class CodeList: SimpleElement
{
    public Collection<CodeValue> Values { get; set; }
}

I have a controller something like

    [HttpGet]
    public ActionResult Edit(string elementId, string version)
    {
        CodeList model = Store.GetCodeList(elementId, version);
        return View(model);
    }

    [HttpPost]
    public ActionResult Edit(CodeList model)
    {
        ActionResult result;
        if (ModelState.IsValid)
        {
            Store.Upsert(model);
            result = RedirectToAction("Index", "SomeOtherController");
        }
        else
        {
            result = View(model.DataElementNumber, model.Version);
        }

        return result;
    }

Simple, I think. If the model is valid, commit to the data store. If it's not valid, re-display the form, with a validation message. In cases where I enter valid values in the form, the validator behaves as expected, that is, the application commits values to the data store and move on.

In the case where I enter a value for Minimum that is smaller than Maximum, the case I am guarding against, instead of seeing my view, again, I see an error screen, something like this for the case where DataElementNumber="XML-25" and Version="201301"

The view 'XML-25' or its master was not found or no view engine supports the searched locations. The following locations were searched:

~/Views/CodeListBuilder/XML-25.aspx

~/Views/CodeListBuilder/XML-25.ascx

~/Views/Shared/XML-25.aspx

~/Views/Shared/XML-25.ascx

~/Views/CodeListBuilder/201301.master

~/Views/Shared/201301.master

~/Views/CodeListBuilder/XML-25.cshtml

~/Views/CodeListBuilder/XML-25.vbhtml

~/Views/Shared/XML-25.cshtml

~/Views/Shared/XML-25.vbhtml

~/Views/CodeListBuilder/201301.cshtml

~/Views/CodeListBuilder/201301.vbhtml

~/Views/Shared/201301.cshtml

~/Views/Shared/201301.vbhtml

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException:...

I can comment out the custom NotSmallerThanAttribute and the system behaves as I expect, apart form being able to enter number fo maximum that are smaller than minimum. I am not sure how to diagnose this. What kind of behavior in a validator can confuse the routing engine? How do I find it? TIA

1
Your problem has nothing to do with your custom validator but with the View(model.Id) statement... what is the type of the Id? what is the value? Do you have a view called Edit.csthml in the searched locations?nemesv
What if you change result = View(model.Id); to result = View("MyEditView", model); (also remove the .Id)?Marthijn
Why do you have a view named Bob, and if this is not the specific error message, please include the specific error message thrown, not an example.Erik Philips
@Erick, I didn't want to confuse the issue with a lot of internal stuff that I would have to explain away. The real value of the reported view is 'XML-25' That is not the name of a view, it is the part of the ID for the view. My "Id" is actually a compound one. I have [HttpGet]Edit(string elementId, string version){..} that invokes the view, and I the actual call to the view is result=View(model.DataElementNumber, model.Version). The error starts The view 'XML-25' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/V...Skip Saillors
@nenesv, I have the Edit.cshtml in my views. It is the same view as where the values were collected when the POST was handled and the validator was invoked.Skip Saillors

1 Answers

2
votes

Your problem has nothing to do with your validator.

With the result = View(model.DataElementNumber, model.Version); you are using the following overload of the View method:

protected internal ViewResult View(
    string viewName,
    string masterName
)

So the framework thinks that your model.DataElementNumber is your viewName and your model.Version your masterName that is why you get this strange view missing exception.

To fix this you just need to use the correct overload with passing in your model

result = View(model);

and MVC will take care of re-displaying your previously posted DataElementNumber and Version values.