What's a good way to set the Item or DataSourc

2019-03-31 05:05发布

问题:

The scenario is that I have a lot of FieldRenderers. These should output data from various places, some from item X and others from item Y. And should be outputting properties from item Z.

Assuming I have a public property ItemX that I want to output a property from, any of the following would be OK. But I get no output by any of them:

<sc:FieldRenderer runat="server" FieldName="Logo" DataSource="<%# ItemX %>" />
<sc:FieldRenderer runat="server" FieldName="Logo" DataSource="<%= ItemX.Paths.FullPath %>" />
<sc:FieldRenderer runat="server" FieldName="Logo" Item="<%# ItemX %>" />
<sc:FieldRenderer runat="server" FieldName="Logo" Item-ID="<%# ItemX.ID %>" />
<sc:FieldRenderer runat="server" FieldName="Logo" Item-ID-Guid="<%# ItemX.ID.Guid %>" />

If I add an ID MyFieldRenderer to it and do the ugly piece below, I get correct output:

MyFieldRenderer.Item = ItemX;

There must be a better way to do this? I'm not sure if this is a Sitecore-specific or WebForms question.

回答1:

It is not possible to set the datasource of a FieldRenderer to a server side object without using code behind. This is a consequence of how WebForms works. The issue is described in a Microsoft Knowledge Base article:

The <%= ... %> displaying expression is an equivalent of the embedded code block that contains only the Response.Write(…) statement. This is the simplest way to display information such as a single string, an int variable, or a constant. [...] Remember that the displaying expression cannot be used in the attributes of server controls. This is because the .NET Framework directly compiles the whole expression instead of the displaying content as the value to the attribute.

In other words, .NET wants to compile the sc:FieldRenderer, and so does not have access to the run-time contents of <%= ItemX.Paths.FullPath %>. You can see this issue in simpler form by trying to display:

<asp:TextBox runat="server" Text="<%= DateTime.Now.ToString() %>" />

which renders <%= DateTime.Now.ToString() %> inside the text box. In short, you cannot get anything other than a static string inside a server control attribute.


There are several possible solutions to this issue:

  1. In your Page_Load method, set the Item field of the FieldRenderer, as you describe. This is the best approach if the number of sublayouts that need to use this logic is limited.

  2. You can create an ItemXFieldRenderer subclass that binds Item to ItemX:

     class ItemXFieldRenderer: FieldRenderer {
       public ItemXFieldRenderer() {
           Item = [code to retrieve ItemX];
       }
     }
    

    Then you can use this control anywhere in your solution where you want to render a field from ItemX. This is the best approach if a large number of sublayouts need to use this logic and the number of items you might need to bind to is very limited.

  3. You could create a subclass of FieldRenderer that parses a string property and uses logic to map the string value to the correct item.

  4. If the path to ItemX is constant, you can set the full path in the Datasource property like this:

    <sc:FieldRenderer runat="server" FieldName="Logo"
    DataSource="/sitecore/context/home/some/item" />
    

    You can also use a relative path. For example, if the context item has a child folder called "Sources", which in turn has a child item "Default", you could reference in your FieldRenderer with this syntax:

    <sc:FieldRenderer runat="server" FieldName="Logo"
    DataSource="sources/default" />
    

    Per my testing, the evaluation of the datasource is case-insensitive, and Sitecore Query expressions like "../.." and "//*[@@name='value']" do not work.

  5. You can use databinding to force ASCX to read the property, as recommended in this Forums thread:

    <sc:FieldRenderer id="myRenderer" FieldName="Logo" 
    DataSource=<%# ItemX.Paths.FullPath %> />
    

    And in codebehind, add

    myRenderer.DataBind(); 
    

    With this last approach you are still using codebehind, but the decision of which FieldRenderer uses which item is now contained in the markup. And as Christian Hagelid points out, you can call this.DataBind() on the sublayout to force DataBind to execute recursively on all controls on the page.

  6. You can use ASP.NET's ExpressionBuilder syntax to centralize the location of your datasource paths. There are three ways of doing this:

    • Places your paths in Web.config. Add this to the <AppSettings> section of Web.config:

      <add key="ItemX" value="/sitecore/content/path/to/itemx" />
      Then set the DataSource attribute to:

      DataSource=<%$ AppConfig: ItemX %>

    • Place the paths in a .resx resource file in App_GlobalSettings. If the file is named Paths.resx, you can access its settings with this syntax:

      DataSource=<%$ Resources: Paths,ItemX %>

    • You can build an ExpressionBuilder class to build logic to translate a string value to a path. Note that the ExpressionBuilder is evaluated at Parse time, so you will not have access to the Sitecore context. This does not look straightforward: the expression builder needs to be referenced in Web.config and the code needs to reside in App_Code.



回答2:

If you have a lot of FieldRenderer controls and you want to contain the logic inside the markup then you can use the <%# ... %> expression and just call this.DataBind(); at the end of Page.Load. That will then bind all the controls inside your usercontrol.



回答3:

I understand you want to be able to do this on the front-end like in MVC-style and not in the code-behind, but that scenario seems to pose that a front-end FieldRenderer control may not be the right tool. I obviously saw the code you wrote in your answer but why not do this then, all on the front-end:

<%= FieldRenderer.Render(item, "field name") %>

E.g.

<%= FieldRenderer.Render(ItemX, "Logo") %>