Nullable DateTime error in Razor Template

Viewed 534

Update

So it looks like this is replicateable in .NET 4.7.2 as well:

https://dotnetfiddle.net/NI8H1n


Original

I have been having an issue with a DateTime? in a razor template in a netcoreapp3.1. The template is being run through:

Engine.Razor.RunCompile("templateName", "templateKey" model: model);

The model being passed consists of the property:

public DateTime? IssueDate { get; set; }

The razor line that is throwing a null ref error inside the template is:

<td>Issue date: @(result.IssueDate.HasValue ? result.IssueDate.Value.ToString("dd/MM/yyyy") : "")</td>

The error:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot perform runtime binding on a null reference

So after some debugging, I found that if I change the problematic line in the template to:

<td>Issue date: @(result.IssueDate != null? result.IssueDate.ToString("dd/MM/yyyy") : "")</td>

Then we are good to go.

So the question I have is why doesn't the initial line work?

1 Answers

Since your view - copied here below from your Fiddle - does not contain a @model declaration, the type dynamic is being used for the viewmodel.

@(Model.SomeDate.HasValue ? Model.SomeDate.Value.ToString("dd/MM/yyyy") : "No Value")  

Once compiled, the above statement looks like below - notice the use of dynamic.

public override async Task ExecuteAsync()
{
    this.Write((((dynamic)base.Model).SomeDate.HasValue) 
        ? ((dynamic)base.Model).SomeDate.Value.ToString("dd/MM/yyyy") 
        : "No Value"
        );
}

Consider the information from the documentation about dynamic

  1. Variables of type dynamic are compiled into variables of type object. Therefore, type dynamic exists only at compile time, not at run time.
  2. any non-null expression can be converted to the dynamic type

This makes that the type dynamic must evaluate its operations and data types at runtime.


public class SampleViewModel
{
    public DateTime? SomeDate {get;set;}
}

Given your model here above, where SomeDate is of type Nullable<DateTime>.

When that SomeDate property would have a value null, you get the Cannot perform runtime binding on a null reference exception, since the code tries to access .HasValue on a null, instead on something that could have been converted to a dynamic (bullet point 2).

When that SomeDate would have a DateTime.Now value as shown in your Fiddle, the dynamic runtime binding concludes that SomeDate must be of type System.DateTime because DateTime.Now is not nullable and only a non-null expression can be converted to the dynamic type (bullet point 2).
Therefore you get the 'System.DateTime' does not contain a definition for 'HasValue' exception, since a DateTime does not contain a HasValue property.


One way to make the HasValue check work, is by including the model declaration in the view, as it avoids having to use the type dynamic - see updated Fiddle.

@model HelloWorldMvcApp.SampleViewModel
@(Model.SomeDate.HasValue ? Model.SomeDate.Value.ToString("dd/MM/yyyy") : "No Value")
Related