145

Why am I receiving the error:

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

at this code:

@model IEnumerable<ArtSchoolProject.Models.Trainer>

@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_PageLayout.cshtml";
}

<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<ul class="trainers">


@foreach (var item in Model) {
<li>
  <div>
      <div class="left">
          <a href="@Url.Action("Details", "Details", new { id = item.ID })">
              <img src="~/Images/Trainer/@item.Picture" />
          </a>
      </div>
      <div class="right">
          @Html.ActionLink(item.Name,"Details",new {id=item.ID})
          <br />
          @Html.DisplayFor(modelItem=>@string. item.Description.ToString().Substring(0,100))
      </div>
  </div>
  </li>
  }

  </ul>

at line:

@Html.DisplayFor(modelItem=>item.Description.ToString().Substring(0,100))

Update:

Problem solved. I added to my code :

  @{
string parameterValue = item.Description.ToString().Substring(0, 100); 
          }
          @Html.DisplayFor(modelItem=>parameterValue)

My new code:

@foreach (var item in Model) {
<li>
  <div>
      <div class="left">
          <a href="@Url.Action("Details", "Details", new { id = item.ID })">
              <img src="~/Images/Trainer/@item.Picture" />
          </a>
      </div>
      <div class="right">
          @Html.ActionLink(item.Name,"Details",new {id=item.ID})
          <br />
          @{
string parameterValue = item.Description.ToString().Substring(0, 100); 
          }
          @Html.DisplayFor(modelItem=>parameterValue)
      </div>
  </div>
 </li>
}

This is only one possibility. Just for curiosity is there another solution for solving the error?

Ajay2707
  • 5,690
  • 6
  • 40
  • 58
POIR
  • 3,110
  • 9
  • 32
  • 48
  • 3
    Excellent! Your solution worked for me, and it is so simple. It would be nice if you can put your solution as an answer instead as update, and accept it. since we usually look at accepted answers. – Dush Aug 14 '15 at 00:27

8 Answers8

124

I had the same problem with something like

@foreach (var item in Model)
{
    @Html.DisplayFor(m => !item.IsIdle, "BoolIcon")
}

I solved this just by doing

@foreach (var item in Model)
{
    var active = !item.IsIdle;
    @Html.DisplayFor(m => active , "BoolIcon")
}

When you know the trick, it's simple.

The difference is that, in the first case, I passed a method as a parameter whereas in the second case, it's an expression.

Daniel
  • 9,312
  • 3
  • 48
  • 48
  • 1
    Perfect!!! Thanx for your answer @Daniel, I was fighting with this s*** for hours – JSEvgeny Jan 15 '18 at 11:53
  • 4
    No, no, no. This is a bad pattern. Use `@Html.Display`. You only need `DisplayFor` if you want the rendered HTML to be named after the property-- if you want it to post back to that same property. _This_ and people using `RenderAction` inside curly braces instead of just `Action`. It makes me feel like no one actually knows how MVC works. – Jordan Jan 08 '21 at 15:10
82

The template it is referring to is the Html helper DisplayFor.

DisplayFor expects to be given an expression that conforms to the rules as specified in the error message.

You are trying to pass in a method chain to be executed and it doesn't like it.

This is a perfect example of where the MVVM (Model-View-ViewModel) pattern comes in handy.

You could wrap up your Trainer model class in another class called TrainerViewModel that could work something like this:

class TrainerViewModel
{
    private Trainer _trainer;

    public string ShortDescription
    {
        get
        {
            return _trainer.Description.ToString().Substring(0, 100);
        }
    }

    public TrainerViewModel(Trainer trainer)
    {
        _trainer = trainer;
    }
}

You would modify your view model class to contain all the properties needed to display that data in the view, hence the name ViewModel.

Then you would modify your controller to return a TrainerViewModel object rather than a Trainer object and change your model type declaration in your view file to TrainerViewModel too.

Sean Airey
  • 6,352
  • 1
  • 20
  • 38
  • 6
    +1 for giving a proper interpretation of the error message and for a better solution than the trick described by the others. – R. Schreurs Apr 06 '17 at 10:52
12

I ran into a similar problem with the same error message using following code:

@Html.DisplayFor(model => model.EndDate.Value.ToShortDateString())

I found a good answer here

Turns out you can decorate the property in your model with a displayformat then apply a dataformatstring.

Be sure to import the following lib into your model:

using System.ComponentModel.DataAnnotations;
Community
  • 1
  • 1
Rob
  • 869
  • 9
  • 11
3

use @Html.Raw(item.Description.ToString().Substring(0,100))

Nguyễn Văn Phong
  • 13,506
  • 17
  • 39
  • 56
1

The ...For extension methods on the HtmlHelper (e.g., DisplayFor, TextBoxFor, ElementFor, etc...) take a property and nothing else. If you don't have a property, use the non-For method (e.g., Display, TextBox, Element, etc...).

The ...For extension methods provides a way of simplifying postback by naming the control after the property. This is why it takes an expression and not simply a value. If you are not interested in this postback facilitation then do not use the ...For methods at all.

Note: You should not be doing things like calling ToString inside the view. This should be done inside the view model. I realize that a lot of demo projects put domain objects straight into the view. In my experience, this rarely works because it assumes that you do not want any formatting on the data in the domain entity. Best practice is to create a view model that wraps the entity into something that can be directly consumed by the view. Most of the properties in this view model should be strings that are already formatted or data for which you have element or display templates created.

Jordan
  • 9,642
  • 10
  • 71
  • 141
  • Thank you for the tip about using TextBox rather than TextBoxFor, and most importantly why it matters and where to use each. Appreciated. Re the last paragraph - a quick search on SO on "where one should do formatting in mvc" revealed opinions for doing in the view, the model, the view model, and the service layer. Only the controller escapes. I'm still new to mvc. – David Pierson Jun 01 '21 at 00:33
  • 1
    @DavidPierson, I've tried formating everywhere and found that formatting dates and currency values into strings on the view model works best. There are always purists; I recommend taking their advice with a small grain of salt. Ultimately, do what you feel makes things cleanest and easiest for you and your team. In my experience, it is best not to do formatting in the view because the view is best left declarative. There are ways of doing formatting declaratively, but I've found them limiting. – Jordan Jun 03 '21 at 12:56
0

Fill in the service layer with the model and then send it to the view. For example: ViewItem=ModelItem.ToString().Substring(0,100);

RainyTears
  • 171
  • 4
0

The correct syntax for converting a DateTime to shortdate follows:

    @foreach (var item in Model)
    {
        @{var endDate= !item.EndDate.ToShortDateString();}
        @Html.DisplayFor(m => endDate,)
    }
JEuvin
  • 866
  • 1
  • 12
  • 31
0

If you want to get a substring on a view without creating a template, this will work. Concat() was used for personal use just to see if it would work. Substring(0, 100) would work only if the data itself is represented as a single string. Instead, the database takes a long string NVARCHAR(MAX)/VARCHAR(MAX) as an array of strings or list of strings, so Substring() and ToCharArray() will fail because both methods are looking for the length of the first string in the list of strings.

This way you create a variable based on the model and return that to a list in which you use the Take() method to grab the characters within that list. Please refer yourself to the above methods in dotnet to get a better understanding.

It should not matter what C# or .NET version you use. I am on .NET Core 6.

<tbody>
@foreach (var item in Model) 
{
    var it = item.Contents.ToList().Take(100).Concat("...");
        <tr>
            <td>
                @Html.DisplayFor(modelItem => it)
            </td>
            <
        </tr>
}
</tbody>
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
DroidRiot
  • 1
  • 1