Exception in Glass mapper for Sitecore in PageEditor mode

2.6k views Asked by At

For the code:

strMenuText.Append(RenderLink(mainlinkitem, 
        x => x.NavigationItem.Url.StringToLink(), 
        isEditable: true,            
        contents: mainlinkitem.NavigationTitle));

Here mainlinkitem is Navigation object for interface created for data template. I am using interfaces in this case and castle windsor creates dynamic proxy objects for this.

Things work ok until I try to use Page editor mode and below error shows up from glass mapper api.

Expression doesn't evaluate to a member x.NavigationItem.Url.StringToLink() at Glass.Mapper.Sc.GlassHtml.MakeEditable[T](Expression1 field, Expression1 standardOutput, T model, String parameters, Context context, Database database, TextWriter writer)

Note: StringToLink is extension method for converting external url in string form to Glass mapper Glass.Mapper.Sc.Fields.Link type.

public static Link StringToLink(this string urlvalue)
{
    Link itemLink = new Link();
    itemLink.Url = urlvalue;
    return itemLink;
}

UPDATE

Code for menu user control:

public partial class MenuControl : GlassUserControl<INavigationFolder>
{
    protected override void GetModel()
    {
        base.GetModel();

        SiteLevelSettings siteSettings = SitecoreContext.GetItem<SiteLevelSettings>(Guid.Parse("Some GUID"));
        Model = siteSettings.HeaderMenuFolder;
    }

    protected void Page_Load()
    {
        if (!Page.IsPostBack)
        {
            LoadMenu();
        }
    }

    private void LoadMenu()
    {
        StringBuilder strMenuText = new StringBuilder();

        foreach (INavigationLink mainlinkitem in Model.ChildLinks)
        {
            if (CanRead(mainlinkitem))
            {
                strMenuText.Append("<td class='menu-item'>");
                if (mainlinkitem.ChildLinks != null && mainlinkitem.ChildLinks.Count() > 0)
                {
                    strMenuText.Append("<ul>");
                    foreach (INavigationLink linkitem in mainlinkitem.ChildLinks)
                    {
                        if (CanRead(linkitem))
                        {
                            strMenuText.Append("<li>");
                            if (linkitem.NavigationItem != null)
                            {
                                strMenuText.Append(RenderLink(linkitem, x => x.NavigationItem.Url.StringToLink(), isEditable: false, contents: linkitem.NavigationTitle));
                            }
                            else if (linkitem.NavigationGeneralLink != null)
                            {
                                strMenuText.Append(RenderLink(linkitem, x => x.NavigationGeneralLink, isEditable: false, contents: linkitem.NavigationTitle));
                            }
                            strMenuText.Append("</li>");
                        }
                    }

                    strMenuText.Append("</ul>");
                }
                strMenuText.Append("<div class='nav-divider'>");
                if (mainlinkitem.NavigationItem != null)
                {
                    strMenuText.Append(RenderLink(mainlinkitem, x => x.NavigationItem.Url.StringToLink(), isEditable: false, contents: mainlinkitem.NavigationTitle));
                }
                else if (mainlinkitem.NavigationGeneralLink != null)
                {
                    strMenuText.Append(RenderLink(mainlinkitem, x => x.NavigationGeneralLink, isEditable: true, contents: mainlinkitem.NavigationTitle));
                }
                strMenuText.Append("</div></td>");
            }
        }

        ltrMenu.Text = strMenuText.ToString();
    }

    private bool CanRead(IItem mainlinkitem)
    {
        var ItemId = mainlinkitem.TemplateId;
        var ItemIDObj = new Sitecore.Data.ID(ItemId);
        var contentdatabase = Sitecore.Context.Database;
        var item = contentdatabase.GetItem(ItemIDObj);
        return item.Access.CanRead();
    }
}

Navigation Folder interface for glass mapper:

[SitecoreType(TemplateId = "{Some GUID}")]
public interface INavigationFolder : IItem
{
    [SitecoreChildren(IsLazy = false)]
    IEnumerable<INavigationLink> ChildLinks { get; set; }
}

Navigation Link interface for glass mapper:

[SitecoreType(TemplateId = "{Some GUID}")]
public interface INavigationLink : IItem
{
    [SitecoreField(FieldId = "{Some GUID}")]
    string NavigationTitle { get; set; }

    [SitecoreField(FieldId = "{Some GUID}")]
    IItem NavigationItem { get; set; }

    [SitecoreField(FieldId = "{Some GUID}")]
    Link NavigationGeneralLink { get; set; }

    [SitecoreField(FieldId = "{Some GUID}")]
    string ShortDescription { get; set; }

    [SitecoreChildren(IsLazy = false)]
    IEnumerable<INavigationLink> ChildLinks { get; set; }

}

Note: This will code will generate menu similar to sitecore site

UPDATE

Url property in interface IItem is defined as follows:

[SitecoreType(TemplateId = "{Some GUID}")]
public interface IItem
{
    [SitecoreId()]
    Guid ID { get; }

    [SitecoreInfo(Type = SitecoreInfoType.Language)]
    Language Language { get; }

    [SitecoreInfo(Type = SitecoreInfoType.Version)]
    int Version { get; }

    [SitecoreInfo(Type = SitecoreInfoType.Url)]
    string Url { get; }

    [SitecoreInfo(Type = SitecoreInfoType.TemplateId)]
    Guid TemplateId { get; }

    [SitecoreInfo(Type = SitecoreInfoType.Key)]
    string Key { get; }
}
2

There are 2 answers

0
Michael Edwards On BEST ANSWER

The second expression in the RenderLink methods should resolve to the property that represents the field you want to be editable in the Page Editor, e.g.:

RenderLink(linkitem, x => x.NavigationItem.Url, isEditable: false, contents: linkitem.NavigationTitle));

When you add the additional method call to the end of the expression Glass.Mapper cannot evaluate which field to make editable correctly.

Instead if you want to do something like this you should probably use an if statement to switch between the two renderings:

        if (IsInEditingMode)
        {
            strMenuText.Append(RenderLink(
                linkitem,
                x => x.NavigationItem.Url
                isEditable: false,
                contents: linkitem.NavigationTitle));
        }
        else
        {
            strMenuText.Append(RenderLink(
                linkitem,
                x => x.NavigationItem.Url.StringToLink(),
                isEditable: false,
                contents: linkitem.NavigationTitle));
        }

However I haven't tested this, instead you should update your property to the Link field type and it will automatically map it:

  [SitecoreField]
  public virtual Glass.Mapper.Sc.Fields.Link Url{get;set;}

You can then update the menu code to:

            strMenuText.Append(RenderLink(
                linkitem,
                x => x.NavigationItem.Url
                isEditable: false,
                contents: linkitem.NavigationTitle));
2
Mark Cassidy On

Extension methods are not "true" extensions of a class, they are resolved at compile time. Without seeing the rest of your code, pointing out how your code can be rewritten to work around this is not easy. I suggest something like:

public static Link MakeLink(string urlvalue)
{
    Link itemLink = new Link();
    itemLink.Url = urlvalue;
    return itemLink;
}

And then in your calling code

strMenuText.Append(RenderLink(mainlinkitem, 
        x => MakeLink( x.NavigationItem.Url ), 
        isEditable: true,            
        contents: mainlinkitem.NavigationTitle));