orchardcmsorchardcms-1.6

Orchard ContentItem's AutoroutePart DisplayUrl


I have a custom content type built through the UI (e.g. not via a module) that has a couple of fields on it, one of which is a ContentItemPicker. I managed to get everything with the front-end working for this with the exception of finding the friendly URL off of the ContentItem from the Model's collection of items. I'm seeing some examples where I'm supposed to use Url.ImageDisplayUrl([ContentItem]), but that gives me this error: 'System.Web.Mvc.UrlHelper' has no applicable method named 'ItemDisplayUrl' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.

My using statements at the top are as follows:

@using Orchard.ContentPicker.Fields
@using Orchard.Utility.Extensions;
@using System.Linq
@using Orchard.ContentManagement;
@using Orchard.Mvc.Html;
@using Orchard.Utility.Extensions;

I'd assume I'm missing something with those, but can't seem to figure out what. The way I'm building out my view is below, and the URL I am trying to get is off of tab.ContentItem.HomepageTab.NavigationItem:

/** @Model.Items is a collection of my custom content types that were created through the UI **/
foreach (var tab in @Model.Items)
{
    var t = new SliderTab
    {
        DisplayOrder = tab.ContentItem.HomepageTab.DisplayOrder.Value,
        ButtonText = tab.ContentItem.HomepageTab.ButtonText.Value,
        Description = tab.ContentItem.HomepageTab.Description.Value,
        ImageUrl = tab.ContentItem.HomepageTab.Image.Url,
        Title = tab.ContentItem.TitlePart.Title,
        ContentItem = tab.ContentItem,
        TabText = tab.ContentItem.HomepageTab.TabText.Value
    };

    /** HomepageTab is the custom content type created in the Orchard UI which has a ContentPickerField associated with it. The name on that is NavigationItem, so I just need the friendly URL off of a ContentPickerField's associated ContentItem **/
    if (tab.ContentItem.HomepageTab.NavigationItem != null && tab.ContentItem.HomepageTab.NavigationItem.Ids != null)
    {
        //this is way, super hacky - getting the actual friendly URL would be better
        t.NavigateUrl = "/Contents/Item/Display/" + tab.ContentItem.HomepageTab.NavigationItem.Ids[0];
    }

    tabs.Add(t);
}

** Edit **

I have a class declaration for HomepageTab at the top which does not correlate to the tab.ContentItem.HomepageTag as that is dynamic off the ContentItem property. It is structured like this:

public class HomepageTab
{
    public dynamic DisplayOrder { get; set; }
    public string ImageUrl { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string ButtonText { get; set; }
    public dynamic ContentItem { get; set; }
    public string TabText { get; set; }
    public string NavigateUrl { get; set; }
    public string TabId
    {
        get { return "t" + this.DisplayOrder.ToString(); }
    }
}

Thoughts?


Solution

  • tab.ContentItem.HomepageTab.NavigationItem is your content item picker field, but expressed this way, it's a dynamic object, so the compiler can get all sorts of confused if you try to use it without casting it. So first I'd recommend casting:

    var pickerField = tab.ContentItem.HomepageTab.NavigationItem as ContentPickerField;
    if (pickerField != null) {
    

    Then you can get the first and only item in the field (caution, we're likely causing a select N+1 issue here, see below):

        var firstItem = pickerField.ContentItems.FirstOrDefault();
    

    Finally, we can ask for the display URL for that item:

        if (firstItem != null) {
            var url = Url.ItemDisplayUrl(firstItem);
    

    This should work just fine. Be careful however: as I said above, getting the collection of items for each tab may trigger one new database query per tab, degrading performance. To avoid that problem, you could pre-fetch the related content items with a technique similar to what I describe in this post: https://weblogs.asp.net/bleroy/speeding-up-pages-with-lots-of-media-content-items

    Instead of pre-fetching images, what you'd do here is first get a list of all the related ids (that's free, those ids come stored in the fields, which are stored with the content items you already have). Then you'd build a local cache of ids to items using a GetMany. And finally you'd use that cache instead of the ContentItems collection to look-up items from the ids.

    I hope this makes sense.