0

When I first load the page both my date textboxes are failing validation. This appears to be because the two Date properties are set as required but are null.

My goals are to:
1) Have a model to pass into the controller that contains the criteria to search for.
2) That criteria will get reloaded when the page returns along with the results.
3) When the page 1st loads it will set the dates to a default to DateTime.Now and NOT show any results. When you submit the criteria it will then show results on next page load.

// Model
public class SearchModel
{
    public long? StudentId { get; set; }

    [Required]
    public DateTime? Date1 { get; set; }

    [Required]
    public DateTime? Date2 { get; set; }

    public List<string> Students { get; set; }
}

// View
@model SearchModel
<div>
    @using (Html.BeginForm("StudentSearch", "Student", FormMethod.Post))
    {
        <span>
            Date 1 @Html.TextBoxFor(m => m.Date1)
            Date 2 @Html.TextBoxFor(m => m.Date2)
            <input type="submit" value="Search" />
        </span>
    }
</div>
<div>
    @foreach(var s in model.Students)
    { <span>@s</span> }
</div>

// Controller
[HttpGet]
public ActionResult StudentSearch(SearchModel model)
{
    if (model.Date1 == null || model.Date2 == null)
    {
        model.Date1 = DateTime.Now;
        model.Date2 = DateTime.Now;
    }

    return View();
}
user3953989
  • 1,844
  • 3
  • 25
  • 56
  • Why are they marked as required? – stephen.vakil Sep 06 '17 at 17:56
  • @stephen.vakil Because they are required fields for the form. – user3953989 Sep 06 '17 at 17:57
  • 1
    Then why are you allowing null? – stephen.vakil Sep 06 '17 at 18:06
  • @stephen.vakil I'm using null to determine if the form has been filled out by a user or if its just the default. – user3953989 Sep 06 '17 at 18:13
  • 2
    i don't think you can have [Required] AND allow them to be null as required means that they have to have a value and, allowing null, means they do not! – Rob Anthony Sep 06 '17 at 18:19
  • @RobAnthony I believe that is correct, that's why I asked what the proper way/best practice is to solve this problem. – user3953989 Sep 06 '17 at 18:20
  • 1
    @RobAnthony You can make the model ***Nullable*** type and apply ***Required*** attribute; it doesn't not effect the validation. We sometime intentionally make Nullable type, so that we can catch and display friendly error message using `ModelState.AddModelError` if user leave it blank; otherwise model binder will throws server exception. – Win Sep 06 '17 at 18:32
  • What are you expecting to accomplish with the `Required` attribute? I still don't get it. If they have to fill in a value, then it is properly marking them as invalid when the field is blank. If they don't have to fill in a value and can leave them blank, then `Required` is actually not appropriate. – stephen.vakil Sep 06 '17 at 18:53
  • @stephen.vakil It's a required field and I want to use the validation framework so I marked them as required. I need to make them null to see if the user submitted the form or if they just loaded the page. On loading the page I check and if the date is null, I know not to run the query to get the students. If there is a date then I know the user submitted the form. Right now I have no other way to tell – user3953989 Sep 06 '17 at 18:57
  • Sounds like you should split your Search views to 2 models 1 for the get and one for the post. The get viewmodel is is showing the initial page, you don't really need validation. The post viewmodel would have the required fields. you also want to separate the inital loading of the page from the form submissions on the page by creating 2 controller actions. – Fran Sep 06 '17 at 19:02
  • @Fran Could you provide and example please? – user3953989 Sep 06 '17 at 19:03
  • @Win gave you an example below. That paradigm is the default way to implement form submissions. Also if you want to default in DateTime.Now, just set it in a constructor for the get view model. That way you load the form with the values you want. – Fran Sep 06 '17 at 19:11
  • The are a few issues with the code in your question that do no make sense. `ModelState` is invalid because the parameter in your method is your model which has 2 required properties (the `DefaultModelBinder` adds the `ModelStateError`'s). But nowhere in your code are you checking `ModelState`, and you do not have `@ValidationMessageFor()` elements in your view so no errors are being displayed anyway. And you have not shown your POST method so its no clear if its really `FormMethod.Get`. –  Sep 06 '17 at 22:26
  • And finally you not even passing a model to the view, so what is the point of setting the dates to `DateTime` now in your GET method? (in any case they will be displayed as `null` because setting the value of a property after its been added to `ModelState` is ignored by the `TextBoxFor()` method –  Sep 06 '17 at 22:28

2 Answers2

2

Date time input is very sensitive. User could make a typo and ModelBinder won't be able to bind it to parameter. So, I suggest you to use framework like jQuery UI Datepicker or Kendo Datepicker.

enter image description here enter image description here

public class StudentController : Controller
{
    [HttpGet]
    public ActionResult StudentSearch(SearchModel model)
    {
        if (model.Date1 == null || model.Date2 == null)
        {
            model.Date1 = DateTime.Now;
            model.Date2 = DateTime.Now;
        }
        return View(model); <=====
    }

    [HttpPost]
    public ActionResult StudentSearchPost(SearchModel model)
    {            
        if (ModelState.IsValid)
        {
            // Do something
        }
        return View();
    }
}

View

@model DemoWebMvc.Models.SearchModel

<div>
    @using (Html.BeginForm("StudentSearchPost", "Student", FormMethod.Post))
    {
        <span>
            Date 1 @Html.TextBoxFor(m => m.Date1)
            Date 2 @Html.TextBoxFor(m => m.Date2)
            <input type="submit" value="Search"/>
        </span>
    }
</div>

From Comment: I'm using a datetime picker control. Since the model defaults to NULL since it's a DateTime? the very first page load shows my date time fields failing validation. They're not failing because what the user selects is invalid

The problem is you pass invalid model to View at Page Load, and default model binder tries to bind to model instance and it triggers validation error at Page Load.

If you do not have valid model, you should not send it to view at Page Load to avoid displaying validation error.

public class StudentController : Controller
{
    [HttpGet]
    public ActionResult StudentSearch()
    {
        return View();
    }

    [HttpPost]
    public ActionResult StudentSearch(SearchModel model)
    {
        if (ModelState.IsValid)
        {
        }
        return View(model);
    }
}

View

@model DemoWebMvc.Models.SearchModel

@{
    Layout = null;
}

@using (Html.BeginForm("StudentSearch", "Student", FormMethod.Post))
{
    @Html.ValidationSummary(true)
    <span>
        Date 1 @Html.TextBoxFor(m => m.Date1)
        @Html.ValidationMessageFor(m => m.Date1)<br />
        Date 2 @Html.TextBoxFor(m => m.Date2)
        @Html.ValidationMessageFor(m => m.Date2)<br />
        <input type="submit" value="Search" />
    </span>
}

<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script>
    $(function () {
        $("#Date1").datepicker();
        $("#Date2").datepicker();
    });
</script>
Win
  • 61,100
  • 13
  • 102
  • 181
  • I'm doing that and the page shows validation errors for both date fields – user3953989 Sep 06 '17 at 18:12
  • I'm using a datetime picker control. Since the model defaults to NULL since it's a DateTime? the very first page load shows my date time fields failing validation. They're not failing because what the user selects is invalid – user3953989 Sep 06 '17 at 18:48
  • I updated the answer on how to fix validation errors at page load. – Win Sep 07 '17 at 01:09
0

Just changing the type from DateTime? to DateTime should accomplish what you want. You can set the value to DateTime.Now in the first action or leave it as the default unsigned. Either will work in the view.

Edit: Here's what I mean in a dotnetfiddle: https://dotnetfiddle.net/Ei0LeQ

allen.mn
  • 467
  • 3
  • 11
  • I need to check for null, if it's null then the user didn't submit the form, it was just loaded with the defaults (null) – user3953989 Sep 06 '17 at 18:47
  • You can't have both [Required] and a nullable type, something has to give. You can set the default value to DateTime.MinValue if you want to leave the field as [Required] but check whether the user has selected a date. Otherwise, you'll need to remove the required attribute and manually check for nulls. – allen.mn Sep 06 '17 at 19:05
  • Of course you can have both [Required] and a nullable type. Its best practice to protect against under-posting attacks. Refer [this answer](https://stackoverflow.com/questions/43688968/what-does-it-mean-for-a-property-to-be-required-and-nullable/43689575#43689575) And making it `DateTime ` means its required anyway since it cannot be `null` –  Sep 07 '17 at 05:47