0

I have a summary page which is computing several average and total times from stored items in the database. All of the calculations work, but due to how the averages are computed the values are stored as a double representing the number of minutes. The goal here is to convert this to a HH:MM:SS format when displaying the averages and totals on the Razor Page.

I started here.

How to display a TimeSpan in MVC Razor

It seemed like bad practice to create a helper class for every single average and total I have to display, so I made the solution a bit more general with a method.

public string TSConvert(double minutes)
        {
            return TimeSpan.FromMinutes(minutes).ToString(@"hh\:mm\:ss");
        }

So this works

<td>
     @item.TSConvert(item.SalesAverageTime)
</td>

This does not

<td>
     @Html.DisplayFor(modelItem => item.TSConvert(item.SalesTotalTime))
</td>

Specifically, I see the following error:

InvalidOperationException: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.

Is there a way to make this method play nicely with the DisplayFor? If not, is there a big impact in going around DisplayFor as I've done above?

jtucker13
  • 58
  • 7

1 Answers1

1

Couple different things going on here:

The error is because you are not using DisplayFor to display an object propety. Instead use @Html.Raw:

<td>
     @Html.Raw(item.TSConvert(item.SalesTotalTime))
</td>

Additionally instead of placing the convert method on your object I would recommend using a separate class (or even an extension method). For example:

public class TimeUtils {
    public static string TSConvert(double minutes)
    {
        return TimeSpan.FromMinutes(minutes).ToString(@"hh\:mm\:ss");
    }
}

Then call it like:

<td>
     @Html.Raw(TimeUtils.TSConvert(item.SalesTotalTime))
</td>

That way you can reuse it with another class if you need to.

Brad Patton
  • 4,007
  • 4
  • 34
  • 44
  • So the Lamda expression appears to be correct with modelItem => item., when I try to call the method using modelItem. I get the following build error: Error CS1061 'OnboardingModel' does not contain a definition for 'SalesTotalTime' and no accessible extension method 'SalesTotalTime' accepting a first argument of type 'OnboardingModel' could be found (are you missing a using directive or an assembly reference?) – jtucker13 Sep 10 '20 at 15:52
  • Branching the function out into TSUtils is a good call, made a few adjustments to account for total hours greater than 24 -`public class TimeUtils { public static string TSConvert(double minutes) { TimeSpan ts = TimeSpan.FromMinutes(minutes); return string.Format("{0}:{1:mm}:{1:ss}", (int)ts.TotalHours, ts); } }` – jtucker13 Sep 10 '20 at 15:53
  • Where is item coming from ? A foreach loop ? You could use the something like following @Html.Raw(TimeUtils.TSConvert(item.SalesTotalTime)) – Brad Patton Sep 10 '20 at 15:56
  • Unfortunately, calling TimeUtils method inside of the DisplayFor like so..... ` @Html.DisplayFor(modelItem => TimeUtils.TSConvert(item.SalesTotalTime)) ` .....still generates the same error as originally described when visiting the page _InvalidOperationException: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions_ – jtucker13 Sep 10 '20 at 15:57
  • There's a variety of items using TS convert coming from methods inside the model class for the page, but its all called on the html with a foreach at the top `@foreach (var item in Model.OnboardGroups.onboardWeekGroups) {` – jtucker13 Sep 10 '20 at 15:59
  • ` @TimeUtils.TSConvert(item.SalesTotalTime) ` works fine, was just concerned it might render incorrectly somewhere without DisplayFor – jtucker13 Sep 10 '20 at 16:01
  • 1
    Use @Html.Raw(). DisplayFor is only for applying an output template to a property or field. see https://stackoverflow.com/questions/9465376/when-should-i-use-html-displayfor-in-mvc – Brad Patton Sep 10 '20 at 16:03