0

I am trying to add custom data validation via data annotations. I am only concerned about server-side validation at this point. Everything I have seen here and elsewhere references client-side validation. I've stripped down all my code, ran a few test cases and I can get this working just fine on a regular view but as soon as the form is in a partial view, the code no longer breaks in the method to override IsValid.

In either case I can see the custom attribute being initialized. When the form is in a regular view I can see the override method being executed upon submitting the form, but when in a partial view the code never gets executed and it goes right to the HttpPost action.

I have spent the better parts of two days trying to figure this out and am at a loss. Any help would be GREATLY appreciated.

Note: The code below does return the same view when it enters the HttpPost action. I have it like this for testing purposes. I know my override is never getting called from the partial view and thus IsValid is always true.

View showing form where the validation works

@model eRecruitBoard.ViewModels.HomeIndexViewModel
@{
    ViewBag.Title = "eRecruitBoard";
}
@*<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>*@

<h2>Login/homepage</h2>

<br /><br />
 <div class="errorMessage">
 @Html.DisplayFor(m => m.LoginErrorMsg)
</div>
<br />

@using (Html.BeginForm("Index", "Home")) {
    <div id="loginControlBox">
        <fieldset>
            <legend>Welcome to eRecruitBoard</legend> 

            <div class="editor-label">
                @Html.LabelFor(m => m.UserName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.UserName)
                @Html.ValidationMessageFor(m => m.UserName)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.Password)
            </div>
            <div class="editor-field">
                @Html.PasswordFor(m => m.Password)
                @Html.ValidationMessageFor(m => m.Password)
            </div>

            <div class="editor-label">
                @Html.CheckBoxFor(m => m.RememberMe)
                @Html.LabelFor(m => m.RememberMe)
            </div>
            <div class="editor-label">
                @Html.EditorFor(m => m.Date)
                @Html.ValidationMessageFor(m => m.Date)
            </div>
            <p>
                <input type="submit" value="Log In" />
            </p>
        </fieldset>
    </div>
}
    <div>
        @Html.Action("BlankForm", "TestForm")
    </div>

Partial View (these script calls also come in via _Layout, but I had them here for testing as well)

@model eRecruitBoard.ViewModels.TestFormViewModel

    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>    
    <script src="@Url.Content("~/Scripts/MicrosoftMvcAjax.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/MicrosoftMvcValidation.js")" type="text/javascript"></script>

@using (Html.BeginForm("NewActivity2", "TestForm", FormMethod.Post))
{
<fieldset>
    <legend>Test Form</legend>
    <br />
    @Html.LabelFor(m => m.Date)
    @Html.EditorFor(m => m.Date)
    @Html.ValidationMessageFor(m => m.Date)
    <input id="activityTimelineSubmit" type="submit" value="Submit" />
</fieldset>
}

ViewModel (for partial view)

namespace eRecruitBoard.ViewModels
{
    public class TestFormViewModel
    {
        [Required]
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
        [NonFutureDate()]
        [Display(Name = "Date")]
        public DateTime Date { get; set; }
    }
}

Controller (for partial view)

namespace eRecruitBoard.Controllers
{
    public class TestFormController :BaseController
    {
        public ActionResult BlankForm()
        {
            var viewModel = new TestFormViewModel
            {
                Date = DateTime.Today
            };

            return PartialView("_TestForm", viewModel);
        }

        [HttpPost]
        public ActionResult NewActivity2(DateTime Date)
        {
            if (!ModelState.IsValid)
                return RedirectToAction("Index", "Home");
            else
                return RedirectToAction("Index", "Home");
        }
    }
}

Validation code

using System;
using System.ComponentModel.DataAnnotations;

namespace eRecruitBoard.WebLibrary.Validation
{
    [AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
    public class NonFutureDateAttribute : ValidationAttribute  //public sealed class 
    {
        public NonFutureDateAttribute(): base("Activity can only be saved for today or dates in the past.")
        {
        }
        public override bool IsValid(object value)
        {
            DateTime dateToCheck = (DateTime)value;
            return (dateToCheck <= DateTime.Today);
        }
    }
}
Tim S.
  • 85
  • 1
  • 11

1 Answers1

2

If you RedirectToAction you lose all validation. You have to return PartialView(model) in your POST action. That would require changing the parameter type of NewActivity2 to TestFormViewModel instead of DateTime.

Update your code with example of how you display the partial view (@Html.Partial() or javascript) if it still doesnt work.

Necros
  • 3,004
  • 23
  • 29