Jaylen Jaylen - 2 months ago 13
C# Question

How can I do client validation to form with ASP.NET MVC 5?

I am new to ASP.NET MVC 5. I like to learn the correct way to build a two way binding between the view and the view model. And take advantage of the client side validation script.

Here is what I have done.


  1. I loading jQuery Library v 1.10.1

  2. I loading Jquery-Validation

  3. I loaded jQuery.Unobtrusive.Validation



I created a ViewModel like so

public class RequestFormViewModel
{
[Required]
[Display(Name = "Day Of")]
public DateTime LocalFrom { get; set; }

[Required]
[Display(Name = "Does not matter since this will be hidden and I use javascript to populate the value here when before the for is submitted")]
public DateTime From { get; set; }

public RequestFormViewModel()
{
}

public RequestFormViewModel(DateTime localFrom, DateTime from)
{
this.LocalFrom = localFrom;

this.From = from;
}
}


And this is how I created my view. Note that I pass a presenter or a business layer (i.e.
DefaultViewPresenter
) to my view and not the ViewModel. The class
DefaultViewPresenter
has a property nammed
Request
. (Below I will show how my presenter looks like)

@model Proj.Presenters.DefaultViewPresenter

@using (Html.BeginForm("Index", "Track", FormMethod.Post, new { @class="form-inline", Id = "TrackActionForm" }))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(m => m.Request.LocalFrom, new { Id = "TrackFrom", Name = "From" })

<div class="input-group">

@Html.TextBoxFor(m => m.Request.LocalFrom, new { Value = Model.Request.LocalFrom.ToString("MM/dd/yyyy"), @class = "form-control small", Id = "TrackLocalFrom", Name = "LocalFrom" })
@Html.ValidationMessage("LocalFrom")

<span class="input-group-btn">
<button class="btn btn-info" type="button" id="TrackSubmit">View</button>
</span>

</div>

}


Here is how my presenter looks like

public class DefaultViewPresenter
{
public RequestFormViewModel Request { get; set; }
.... // some other propertied that I need for the view that are not related to my form
....
....
}


Problem

When I submit the form, the form does not get submitted! I don't I get any errors. Its like the submit button has
return;
function on the click event.

How can I get the script to validate correctly and when the form is valid processed the post request?

What am I missing here? How can I correct this problem?

After running my app this is the HTML markup that is razor is generating

<form novalidate="novalidate" id="TrackActionForm" action="/Track" class="form-inline" method="post">
<input name="__RequestVerificationToken" value="thmJX-Mlj5WjM3e7WMbgtb8KiEf4vuUKGzon4zO18fHDDY3cWpm2M1Lks8HbZDxX2qz7UxpRsoYvz2njNwYS_D8zclTvu9pdsJlSO0ckNLQ1" type="hidden">
<input id="TrackFrom" name="From" data-val="true" data-val-date="The field Day Of must be a date." data-val-required="The Day Of field is required." value="9/14/2016 12:00:00 AM" type="hidden">
<div class="input-group">

<input id="TrackLocalFrom" name="LocalFrom" value="09/14/2016" class="form-control small" type="text">
<span class="field-validation-valid" data-valmsg-for="LocalFrom" data-valmsg-replace="true"></span>

<span class="input-group-btn">
<button class="btn btn-info" type="button" id="TrackSubmit">View</button>
</span>

</div>
</form>

Answer

You have a few issues with your implementation.

All the HtmlHelper methods that generate form controls generate the correct name and value attributes necessary for 2 way model binding. You are overriding those values so that the controls now have no relationship to your model.

You also have a hidden input for the Request.LocalFrom property before the textbox, so that when you submit, only the value of the hidden input (the original value of the property) will be bound and the edited value in the textbox will be ignored. In addition, because of that hidden input, the data-val-* attributes generated for client side validation have been applied to the hidden input, not the textbox.

Its not clear why you need 2 view models, and ideally your DefaultViewPresenter view model should contain properties for LocalFrom and From, however with your current models, your view needs to be

@model Proj.Presenters.DefaultViewPresenter

@using (Html.BeginForm("Index", "Track", FormMethod.Post, new { @class="form-inline", Id = "TrackActionForm" }))
{ 
    @Html.AntiForgeryToken() 
    <div class="input-group">          
        @Html.TextBoxFor(m => m.Request.LocalFrom, "{0:MM/dd/yyyy}", new { @class = "form-control small" })
        @Html.ValidationMessageFor(m => m.Request.LocalFrom)

        <span class="input-group-btn">
            <button class="btn btn-info" type="submit" id="TrackSubmit">View</button> // change to a submit button
        </span>
    </div>
}

Note the 2nd parameter in TextBoxFor() is the format string, but that can be omitted if you use the [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] applies to the property. Note also the HtmlHelper methods generate an id attribute based on the property name and there should generally be no reason to overwrite it.

As a side note, it is recommended that in your view models, you make value type properties nullable with the [Required] attribute to protect against under-posting attacks (where a malicious user posts backs and omits a name/value pair for the property, in which case it will be initialized to its default value (DateTime.MinValue).

[Required(ErrorMessage = "Please enter ...")]
[Display(Name = "Day Of")]
public DateTime? LocalFrom { get; set; }
Comments