MudBlazor DateTime Binding Error Guide

Overview

This document explains a common binding error that occurs when using MudBlazor's MudDatePicker component with non-nullable DateTime properties in Blazor WebAssembly applications.

The Problem

Error Scenario

When binding a MudDatePicker to a non-nullable DateTime property, you may encounter compilation errors or runtime binding issues.

Example of problematic code:

<MudDatePicker @bind-Date="InvoiceModel.InvoiceDate"
Label="Invoice Date"
Required="true" />
 
@code {
private InvoiceDto InvoiceModel { get; set; } = new();
}

Where InvoiceDto is defined as:

public class InvoiceDto
{
public DateTime InvoiceDate { get; set; } // Non-nullable
}

Root Cause

MudBlazor's MudDatePicker component expects its Date parameter to be of type DateTime? (nullable). This design allows the component to represent three states:

  • null: No date selected
  • DateTime.MinValue: Default uninitialized date
  • Actual date: User-selected date

When you bind to a non-nullable DateTime property, there's a type mismatch between:

  • Component expectationDateTime?
  • Property typeDateTime

Solution Options

Use a nullable property as an intermediary between the component and your non-nullable DTO property.

When to use:

  • The field is required in your business logic/database
  • You want to maintain data integrity
  • You need validation at the model level

Implementation:

<MudDatePicker @bind-Date="InvoiceDateInput"
Label="Invoice Date"
Required="true"
RequiredError="Please select an Invoice Date"
Class="mb-4" />
 
@code {
private InvoiceDto InvoiceModel { get; set; } = new()
{
InvoiceDate = DateTime.Today // Sensible default
};
 
private DateTime? InvoiceDateInput
{
get => InvoiceModel.InvoiceDate;
set => InvoiceModel.InvoiceDate = value ?? DateTime.Today;
}
}

Advantages:

  • ✅ Maintains non-nullable requirement in DTO
  • ✅ Works seamlessly with MudBlazor
  • ✅ Provides default value handling
  • ✅ Separates UI concerns from domain model

Disadvantages:

  • ⚠️ Additional boilerplate code
  • ⚠️ Need to handle null coalescing

Option 2: Make DTO Property Nullable

Change the DTO property to be nullable to match the component's expectation.

When to use:

  • The field is optional in your database schema
  • Users should be able to leave it blank initially
  • The date represents optional/future information

Implementation:

public class InvoiceDto
{
public int InvoiceId { get; set; }
public int CustomerId { get; set; }
public DateTime? InvoiceDate { get; set; } // Now nullable
public string? BillingAddress { get; set; }
// ... other properties
}
<MudDatePicker @bind-Date="InvoiceModel.InvoiceDate"
Label="Invoice Date"
Required="true"
Class="mb-4" />
 
@code {
private InvoiceDto InvoiceModel { get; set; } = new();
}

Advantages:

  • ✅ Simple, direct binding
  • ✅ Less code
  • ✅ Clear representation of "not set" state

Disadvantages:

  • ⚠️ May not reflect actual database constraints
  • ⚠️ Requires null handling throughout application
  • ⚠️ Can lead to validation issues if field is actually required

Option 3: Initialize with Default Value and Use Two-Way Binding

Ensure the DTO is never null and the DateTime has a sensible default.

When to use:

  • Quick prototyping
  • The field should always have a value
  • You're okay with default DateTime values

Implementation:

<MudDatePicker @bind-Date="InvoiceDateNullable"
Label="Invoice Date"
Required="true"
Class="mb-4" />
 
@code {
private InvoiceDto InvoiceModel { get; set; } = new()
{
InvoiceDate = DateTime.Today
};
 
private DateTime? InvoiceDateNullable
{
get => InvoiceModel.InvoiceDate;
set
{
if (value.HasValue)
{
InvoiceModel.InvoiceDate = value.Value;
}
}
}
}

Advantages:

  • ✅ Keeps DTO non-nullable
  • ✅ Provides sensible default

Disadvantages:

  • ⚠️ User might not realize default is set
  • ⚠️ Still requires intermediary property

Option 4: Use DateOnly Type (.NET 6+)

If you only need the date (not time), consider using DateOnly.

When to use:

  • You're on .NET 6 or later
  • You only care about dates, not times
  • Your database supports date-only columns

Implementation:

public class InvoiceDto
{
public DateOnly InvoiceDate { get; set; }
}
<MudDatePicker @bind-Date="InvoiceDateTimeProxy"
Label="Invoice Date"
Required="true"
Class="mb-4" />
 
@code {
private InvoiceDto InvoiceModel { get; set; } = new()
{
InvoiceDate = DateOnly.FromDateTime(DateTime.Today)
};
 
private DateTime? InvoiceDateTimeProxy
{
get => InvoiceModel.InvoiceDate.ToDateTime(TimeOnly.MinValue);
set => InvoiceModel.InvoiceDate = value.HasValue
? DateOnly.FromDateTime(value.Value)
: DateOnly.FromDateTime(DateTime.Today);
}
}

Advantages:

  • ✅ Semantically correct for date-only fields
  • ✅ Prevents time-related bugs

Disadvantages:

  • ⚠️ Requires .NET 6+
  • ⚠️ Still needs intermediary property
  • ⚠️ Database migration may be needed

Decision Matrix

Scenario Recommended Solution
Required field in database Option 1: Intermediary property
Optional field in database Option 2: Make DTO nullable
Date-only requirement (.NET 6+) Option 4: Use DateOnly
Quick prototype Option 3: Default value
Audit fields (CreatedDate, etc.) Option 1: Intermediary property with DateTime.UtcNow default

Complete Working Example

Here's a complete implementation using Option 1 (recommended for most cases):

@using Application.DTOs
@using Presentation.Shared
@inject ISnackbar Snackbar
 
<MudDialog>
<DialogContent>
<MudContainer MaxWidth="MaxWidth.Small" Class="pa-4">
<EditForm Model="InvoiceModel" OnValidSubmit="SubmitForm">
<DataAnnotationsValidator />
 
<MudAutocomplete T="CustomerDto"
Label="Customer"
@bind-Value="SelectedCustomer"
SearchFunc="@SearchCustomers"
ToStringFunc="@(x => x?.FirstName)"
Required="true"
Class="mb-4" />
 
<MudDatePicker @bind-Date="InvoiceDateInput"
Label="Invoice Date"
Required="true"
RequiredError="Please select an Invoice Date"
Class="mb-4" />
 
<MudTextField @bind-Value="InvoiceModel.BillingAddress"
Label="Billing Address"
Class="mb-4" />
 
<MudNumericField @bind-Value="InvoiceModel.Total"
Label="Total"
Required="true"
Class="mb-4" />
 
<DialogActions OnCancel="Cancel"
OnSave="HandleSave"
IsEditing="IsEditing" />
</EditForm>
</MudContainer>
</DialogContent>
</MudDialog>
 
@code {
[CascadingParameter]
private IMudDialogInstance? MudDialog { get; set; }
 
[Parameter]
public List<CustomerDto> Customers { get; set; } = [];
 
[Parameter]
public bool IsEditing { get; set; }
 
[Parameter]
public InvoiceDto? Invoice { get; set; }
 
private InvoiceDto InvoiceModel { get; set; } = new()
{
InvoiceDate = DateTime.Today
};
 
private CustomerDto? _selectedCustomer;
 
// Intermediary property for date binding
private DateTime? InvoiceDateInput
{
get => InvoiceModel.InvoiceDate;
set => InvoiceModel.InvoiceDate = value ?? DateTime.Today;
}
 
private CustomerDto? SelectedCustomer
{
get => _selectedCustomer;
set
{
_selectedCustomer = value;
if (value != null)
{
InvoiceModel.CustomerId = value.CustomerId;
}
}
}
 
protected override void OnInitialized()
{
if (!IsEditing || Invoice == null) return;
 
InvoiceModel = new InvoiceDto
{
InvoiceId = Invoice.InvoiceId,
CustomerId = Invoice.CustomerId,
InvoiceDate = Invoice.InvoiceDate,
BillingAddress = Invoice.BillingAddress,
BillingCity = Invoice.BillingCity,
BillingState = Invoice.BillingState,
BillingCountry = Invoice.BillingCountry,
BillingPostalCode = Invoice.BillingPostalCode,
Total = Invoice.Total,
};
 
SelectedCustomer = Customers.FirstOrDefault(c => c.CustomerId == Invoice.CustomerId);
}
 
private async Task<IEnumerable<CustomerDto>> SearchCustomers(string value, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return Customers
.Where(x => string.IsNullOrEmpty(value) ||
x.FirstName.Contains(value, StringComparison.InvariantCultureIgnoreCase))
.OrderBy(x => x.FirstName);
}
 
private bool IsValid() => InvoiceModel.CustomerId > 0;
 
private Task SubmitForm()
{
if (!IsValid())
return Task.CompletedTask;
 
var invoiceToSave = new InvoiceDto
{
InvoiceId = IsEditing ? InvoiceModel.InvoiceId : 0,
CustomerId = SelectedCustomer?.CustomerId ?? 0,
InvoiceDate = InvoiceModel.InvoiceDate,
BillingAddress = InvoiceModel.BillingAddress,
BillingCity = InvoiceModel.BillingCity,
BillingState = InvoiceModel.BillingState,
BillingCountry = InvoiceModel.BillingCountry,
BillingPostalCode = InvoiceModel.BillingPostalCode,
Total = InvoiceModel.Total
};
 
MudDialog?.Close(DialogResult.Ok(invoiceToSave));
return Task.CompletedTask;
}
 
private Task HandleSave()
{
if (!IsValid())
{
Snackbar.Add("Please select a customer", Severity.Error);
return Task.CompletedTask;
}
return SubmitForm();
}
 
private void Cancel() => MudDialog?.Close(DialogResult.Cancel());
}

Additional Considerations

Validation

When using intermediary properties, ensure your validation logic accounts for nullable types:

private bool IsValidDate() => InvoiceDateInput.HasValue;

Database Mapping

If your database column is NOT NULL, keep your DTO property non-nullable to reflect the schema accurately.

API Contracts

Consider how your DTOs are used in API responses. Nullable properties communicate that a value might be absent.

Best Practices

  1. Match your DTO to your database schema - If the database column is NOT NULL, keep the DTO property non-nullable
  2. Use intermediary properties for UI binding - This separates UI concerns from domain models
  3. Provide sensible defaults - Initialize DateTime properties with appropriate default values (e.g., DateTime.TodayDateTime.UtcNow)
  4. Document your choice - Add comments explaining why a field is nullable or not
  5. Consider using DateOnly - For date-only fields in .NET 6+, use DateOnly type for better semantic meaning

Summary

The MudBlazor MudDatePicker binding error occurs due to a type mismatch between the component's nullable DateTime? parameter and non-nullable DateTime properties. The recommended solution is to use an intermediary nullable property that handles the conversion, maintaining data integrity while satisfying the component's requirements.

 

 

 

 


Was this article helpful?