1

I ran into an issue that I thought was interesting.

Take an input form that has an input field for an integer value (cost, in my case). I do not want "$100" or "Twenty Dollars" to be sent to the database as it will likely complain. I want to add an error to the model and send it back to the user. Seemed simple ;).

I am using a custom model binder because I am using some inheritance in my domain model. I have multiple types of events, each type implementing IEvent - thus, I needed a custom model binder.

The issue I have is, when I am trying to bind the cost field, and the conversion from string to int fails, I am really not sure how best to handle this.

My Custom Model Binder public class EventModelBinder : IModelBinder { #region IModelBinder Members

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // Trivial code ... binding other fields...

        _event.ProjectedCost = GetA<int>(bindingContext, "ProjectedCost").GetValueOrDefault(-1);

        return _event;
    }

    #endregion

    // From Scott Hanselman's blog :)
    private Nullable<T> GetA<T>(ModelBindingContext bindingContext, string key) where T : struct
    {
        if (String.IsNullOrEmpty(key)) return null;
        ValueProviderResult valueResult;
        Nullable<T> ret;
        //Try it with the prefix...
        bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName + "." + key, out valueResult);
        //Didn't work? Try without the prefix if needed...
        if (valueResult == null && bindingContext.FallbackToEmptyPrefix == true)
        {
            bindingContext.ValueProvider.TryGetValue(key, out valueResult);
        }
        if (valueResult == null)
        {
            return null;
        }

        try
        {
            ret = (Nullable<T>)valueResult.ConvertTo(typeof(T));
        }
        catch
        {
            return null;
        }

        return ret;
    }

This works really well - if Enums are null or not selected, I can validate against them and let the user know it is required. For the cost field, I get -1 if the conversion failed. But, when I go to validate the data on the server and go back to the UI, the Model.Event.ProjectedCost field is null

My EventService

// ...
protected bool ValidateEvent(IEvent eventToValidate)
    {            
        if (eventToValidate.ProjectedCost < 0)
            _validationDictionary.AddError("ProjectedCost", "Incorrect format");

        return _validationDictionary.IsValid;
    }

    public bool SaveEvent(IEvent _event)
    {
        if (!ValidateEvent(_event))
            return false;
        try
        {
            _repository.SaveEvent(_event);
            return true;
        }
        catch
        {
            return false;
        }

My Edit.aspx View

<h2>Edit</h2>

<%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>

<% using (Html.BeginForm()) {%>

    <fieldset>
        <legend>Fields</legend>
        <p>
            <label for="ProjectedCost">ProjectedCost:</label>
            <%= Html.TextBox("ProjectedCost", Model.Event.ProjectedCost) %>
            <%= Html.ValidationMessage("ProjectedCost", "*") %>
        </p>

        <% Html.RenderPartial(String.Format("~/Views/Shared/{0}Form.ascx", Model.Event.Type), Model.Event, ViewData); %>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>

<% } %>

<div>
    <%=Html.ActionLink("Back to List", "Index") %>
</div>

In my view, Model.Event.ProjectedCost is null if it doesn't validate, giving me this (Line 54 is the culprit):

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:

Line 52:             <p>
Line 53:                 <label for="ProjectedCost">ProjectedCost:</label>
Line 54:                 <%= Html.TextBox("ProjectedCost", (Model.Event.ProjectedCost == null ? 0 : Model.Event.ProjectedCost)) %>
Line 55:                 <%= Html.ValidationMessage("ProjectedCost", "*") %>
Line 56:             </p>

What I'd kind of like to do is send the value the user entered back to the user, but my custom model binder and/or validation logic seems to be setting something to null?

I realize this may not be the easiest question to read, so let me know if I can clarify in any way!

2
  • Are you sure you can't use the default model binder? Coz i suspect that the binder is not doing something the default one does and that's why your value is not being returned on validation Commented Mar 3, 2010 at 14:38
  • I tried, but my views inherit from IEvent - so the default model binder tried to create an instance of IEvent, which isn't possible as it is an interface. Commented Mar 3, 2010 at 15:44

2 Answers 2

1

You could use javascript to restrict the input of the the textbox to just integers.

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

2 Comments

+1 Very true - I'd like to not rely soley on JS, however. I want to have a solid fall-back in case the user disables JS
Yea, definitely. I would never have just front end validation, because you never know how the user is set up
0

Seems that if you call AddModelError without SetModelValue first, you'll get a shiny-new NullReferenceExceptions sometimes:

http://www.crankingoutcode.com/?aspxerrorpath=/2009/02/01/IssuesWithAddModelErrorSetModelValueWithMVCRC1.aspx

1 Comment

That blog post fixed it, but now I get "-1" for the value, and not the value the user entered. Hmm..

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.