0

I have the following "date" fields in my model that are representing as a string:

enter image description here

enter image description here

I have the following in my view:

enter image description here

enter image description here

My question is how do I get cast the string properly to fit the date picker?

I'm not sure if it should be done in the Model or View.

5
  • 1
    WHY are you storing dates as strings? Use the most appropriate datatype - always - no exceptions. This is just asking or headaches and lots of messy code down the line ....
    – marc_s
    CommentedNov 9, 2024 at 15:08
  • I don't have control over the table.
    – H22
    CommentedNov 9, 2024 at 15:10
  • 1
    @H22 Even so, you can still use DateOnly or DateTime in your application-code.
    – Dai
    CommentedNov 9, 2024 at 16:16
  • @H22 Also, using = null! with a non-nullableString property's initializer is just wrong. If a property can be null then it should be typed as String? and reference-types don't need initializing to null because it's implicit: learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/… . - what your code is doing right-now is sabotaging itself and hiding the evidence of sabotage under the rug.
    – Dai
    CommentedNov 9, 2024 at 16:17

1 Answer 1

1

My pontifical points are in no particular permutation:

  • Do not use String when a better value-type is available.
    • In this case, you should use DateOnly? (a.k.a. Nullable<DateOnly>).
      • Nullable<T> should be used because it allows for empty date inputs, which <input type="date" value="" /> allows, whereas a non-nullable DateOnly view-model property is harder to work with when using ASP.NET's model-binding because any form field can be empty or invalid - or just plain undefined. You might think that the [Required] attribute solves this problem but it doesn't: the [Required] attribute doesn't stop a property being empty, it just stops ModelStateDictionary.IsValid returning true when a property is empty, which means you will always need to correctly (if not gracefully) handle empty/undefined form fields and differentiate that from whatever the default(T) of a value-type is (e.g. default(DateOnly) is .
    • Even though your underlying database is unfortunately storing dates incorrectly, that doesn't stop you from using appropriate types in your application code.
      • If you're using EF entity classes generated from your existing DB then that's no excuse because you should not be using EF entity classes with ASP.NET Form Binding, instead you should define a separate class MyPageViewModel, which can expose the data as DateOnly?-typed properties.
  • HTML <input type="date" />requires that the value="" parameter be formatted as yyyy-MM-dd.
  • ASP.NET's HTML-Helpers are effectively obsolete thesedays, consider using Tag-Helpers instead:
    • Use <input asp-for="YMDEFF" type="date" /> instead of Html.TextBoxFor.
      • The asp-for="" is a specialModelExpression attribute just like TextBoxFor's Expression<> parameter and it's used to generate the id="" and name="" attributes at runtime.
  • For the love of program-correctness, please never do public String SomeProperty { get; set; } = null! - it's just wrong on so many levels.
    • If a String property can ever be null then its type must be String? - not String - and the initialization to null is just redundant.
    • If a String property will never be null then initialize it to = String.Empty or = "", not null!, yikes.
  • Do not use all-caps names like YMDEFF and YMDEND - in C#/.NET we use PascalCase and camelCase such that all words and acronyms 3 letters or longer are always TitleCase, while all 2-letter initialisms are always uppercase (like IO) while 2-letter abbreviations (like Db for "Database") are not.
    • Now YMDEFF looks like "YMD Eff...", I assume YMD refers to the date-format and that "Eff" is short for "Effective", so just named it EffectiveDate.
    • And YMDEND looks like "YMD End", by the same logic this should be named EndDate.

Like so:

public class MyPageViewModel { // [Required] // <-- Uncomment or delete this line, based on your actual requirements. [DisplayFormat( DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true )] // <-- This attribute is necessary to ensure the `<input value=""/>` attribute has the correct format that HTML requires. public DateOnly? EffectiveDate { get; set; } // [Required] [DisplayFormat( DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true )] public DateOnly? EndDate { get; set; } } 

Your Razor markup could look like this:

@model MyPageViewModel // etc <form asp-controller="Foobar" asp-action="PostForm"> <div class="field"> <label asp-for="EffectiveDate"></label> <input asp-for="EffectiveDate" type="date" /> <span asp-validation-for="EffectiveDate"></span> </div> <div class="field"> <label asp-for="EndDate"></label> <input asp-for="EndDate" type="date" /> <span asp-validation-for="EndDate"></span> </div> <button type="submit">Submit</button> </form> 

Your Controller Action would hold the logic for marshaling between your database's (abominable) String-based dates and DateOnly dates:

public sealed class FoobarController : Controller { [Route( "/my-form" )] // <-- Use [Route] for GET actions instead of [HttpGet] because it means ASP.NET can handle HEAD, and other request verbs, for you automagically. public async Task<IActionResult> GetForm() { SomeEntityClass? e = await this.db.GetThingAsync(); if( e is null ) return this.NotFound(); MyPageViewModel vm = new MyPageViewModel() { EffectiveDate = DateOnly.TryParseExact( s: e.YMDEFF, format: "yyyyMMdd", provider: CultureInfo.InvariantCulture, style: 0, out DateOnly parsed1 ) ? parsed1 : (DateOnly?)null, EndDate = DateOnly.TryParseExact( s: e.YMDEND, format; "yyyyMMdd", provider: CultureInfo.InvariantCulture, style: 0, out DateOnly parsed2 ) ? parsed2 : (DateOnly?)null }; return this.View( model: vm ); } [HttpPost( "/my-form" )] public async Task<IActionResult> PostForm( [FromForm] MyPageViewModel model ) { SomeEntityClass? e = await this.db.GetThingAsync(); if( e is null ) return this.NotFound(); if( !this.ModelState.IsValid ) return this.View( model: model ); Debug.Assert( model.EffectiveDate.HasValue ); // <-- This is to workaround how the C# compiler isn't aware that `ModelState.IsValid` indicates (for example) non-nullability of those View-Model properties. Debug.Assert( model.EndDate.HasValue ); // e.YMDEFF = model.EffectiveDate.Value.Tostring( format: "yyyyMMdd" ); e.YMDEND = model.EndDate .Value.Tostring( format: "yyyyMMdd" ); await this.db.SaveChangesAsync(); return this.RedirectToAction( nameof(GetForm) ); } } 
6
  • 1) Following your "For the love of program-correctness" gives me "possible null reference assignment" in my service class. 2) EffectiveDate = DateOnly.TryParseExact( format; "yyyyMMdd", value: e.YMDEFF, out DateOnly parsed1 ) ? parsed1 : (DateOnly?)null, is not valid for .NET 6 or 8??? 3) SomeEntityClass? e = await this.db.GetThingAsync(); ...So I'm getting the data from the database (string from DB to DateOnly in the model) and then I'm converting the fields to a DateOnly again?
    – H22
    CommentedNov 11, 2024 at 14:08
  • @H22 re point 1: that means you've got a null-correctness issue in your service-class.
    – Dai
    CommentedNov 11, 2024 at 22:20
  • @H22 re point 2: the code I posted is not meant to be copied-and-pasted literally (which is why I wrote "...look like this"), but I'll correct that now.
    – Dai
    CommentedNov 11, 2024 at 22:22
  • @H22 re point 3: the SomeEntityClass is just an example - your post didn't say where/how you got those values from your database so I had to make assumptions. I can adjust my answer to suit your situation but only if you add those details to your question post, which you haven't yet done.
    – Dai
    CommentedNov 11, 2024 at 22:23
  • I was able to get the datatype changed to date in the database. Thanks for your help. Should I delete the question?
    – H22
    CommentedNov 15, 2024 at 13:13

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.