-1

I'm trying to create a custom validation rule which will check to see if Times overlap in any case, to do this I have the following:

public class TimesheetLogicAttribute : ValidationAttribute
{

    public TimesheetLogicAttribute()
    {
        //this.jobRecords = jobRecords;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var records = value as List<TimesheetJobRec>;

        records = records.OrderBy(r => r.StartTime).ToList();
        DateTime? current = null;

        foreach(var record in records)
        {
            if (record.StartTime > record.FinishTime)
            {
                return new ValidationResult("Finish time is before start time on " + record?.JobNumber);
            }

            if (current == null)
            {
                current = record.FinishTime;
                continue;
            }

            if(record.StartTime < current)
            {
                return new ValidationResult("Time overlapping");
            }
            current = record.FinishTime;
        }

        return ValidationResult.Success;

        //return base.IsValid(value, validationContext);
    }

}

My corresponding model is:

public class TimesheetJobRec : ModelsBase
{
    // Job number stuff needs to go in here.
    [Required]
    public string JobNumber { get; set; }
    public Timesheet Timesheet { get; set; }

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

    [Required]
    [DataType(DataType.Time)]
    public DateTime StartTime { get; set; }

    [Required]
    [DataType(DataType.Time)]
    public DateTime FinishTime { get; set; }

    [Required]
    public double LunchTime { get; set; }

    [ReadOnly(true)]
    [Range(0.25, 24)]
    public double Total { get; set; }

}

Then I decorate my view model like this:

  [Display(Name = "Job Records")]
  [TimesheetLogic]
  public IList<TimesheetJobRec> JobRecords { get; set; }

This all works fine and in my validation I receive my item list correctly, the problem occurs when returning a ValidationResult I get the following error on the ASP side of things:

Object reference not set to an instance of an object.

Line 43:                         <th>Total</th>
Line 44:                     </tr>
Line 45:                     @foreach (var item in Model.JobRecords)
Line 46:                     {
Line 47:                         Html.RenderPartial("_JobEditorRow", item);

On Line 45 the error is thrown, in that foreach in my View I am just rendering existing items. I'm not sure what the cause for this is and whether I'm doing validation correctly or not.

sham
  • 3,269
  • 3
  • 12
  • 21
  • 2
    Check the value of Model.JobRecords. Most probably the object might be null. – Karthik AMR Jan 11 '17 at 10:25
  • @KarthikAMR Yes I was thinking this, but in my Validation attribute how can I return an instance of the viewmodel? AFAIK we can only return a `ValidationResult` in which `null` will be a success; however I need to return my view model which will include the instance of `Model` and `Model.JobRecords`. – sham Jan 11 '17 at 10:27
  • 1
    You don't return a model in a validation attribute. You return your model in the POST method (using `return View(model);`) if `ModelState` is invalid –  Jan 11 '17 at 10:29
  • You can initialize the object instead of having null value. – Karthik AMR Jan 11 '17 at 10:30
  • @StephenMuecke I am doing that already in my controller method on the `GET` for that same page so I don't see why this would have an issue. – sham Jan 11 '17 at 10:36
  • But you obviously not doing it in the POST method. Your validation attribute has nothing at all to do with your issue. –  Jan 11 '17 at 10:37
  • @KarthikAMR I am instantiating the object when the `GET` request is made initially to the page, I just want to return an error message onto the same page in this case which doesn't seem to be happening. Maybe I'm not understanding something. – sham Jan 11 '17 at 10:37

1 Answers1

0

You must set the JobRecords property of each instance of the view model that you pass to the view to an IList<TimesheetJobRec>. You can use an auto-property initializer to initialize the property in C#6:

[Display(Name = "Job Records")]
[TimesheetLogic]
public IList<TimesheetJobRec> JobRecords { get; set; } = new List<TimesheetJobRec>();

Or you could do it in the constructor of the view model class

//constructor
public YourViewModel()
{
    JobRecords = new List<TimesheetJobRec>();
}

This will make sure that the property is always initialized provided that you don't explicitly set it to a null reference somewhere.

mm8
  • 135,298
  • 10
  • 37
  • 59