IContentFinder returns content with the wrong site features in multi-language site
I have a customer with a pretty large multi-language site that roughly takes the following structure:
Site Container
/UK Home
/Content
/Award Winners
/Organisation 1
/Organisation 2
/Organisation 1
/...
Site Container
/FR Home
/Content
/Award Winners
Site Container
/IT Home
/Content
/Award Winners
...etc etc. Each site has its own navigation in it's own language. The UK site hosts all of the Organisation (award winner) nodes that have been imported from SalesForce which is a process that runs nightly. Currently there is ~500 nodes. In the other country sites they will have the top level awards page but show the actual awards from the UK site (hopefully you're still following).
I've implemented an IContentFinder which works to the point that it returns the correct content as intended except it is rendered in the context of the UK site and so shows English navigation etc when the content appears on the French site.
Here is the implementation which is pretty straight forward:
public class OrganisationContentFinder : IContentFinder
{
public bool TryFindContent(PublishedContentRequest contentRequest)
{
if (contentRequest != null)
{
var foodBusinessMaster = uQuery.GetNodesByType("SiteContainerFB").OrderBy(n => n.Id).FirstOrDefault();
if (foodBusinessMaster != null)
{
var foodBusinessMasterHome = foodBusinessMaster.ChildrenAsList.FirstOrDefault(c => c.NodeTypeAlias == "HomePageNational");
if (foodBusinessMasterHome != null)
{
var path = contentRequest.Uri.GetAbsolutePathDecoded();
var urlPath = string.Concat(foodBusinessMasterHome.Id, '/', path, '/');
var node = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetByRoute(urlPath, true);
if (node != null && node.DocumentTypeAlias == "Organisation")
{
contentRequest.PublishedContent = node;
}
}
}
return contentRequest.PublishedContent != null;
}
return false;
}
}
Am I doing something obviously wrong or missing something or is this just not a suitable solution to this problem?
The content finder basically just sets the current page, so if the current page is in the UK site, then then page will be rendered in that context.
You could probably use your content finder to set the current page to f.ex. "/FR/Award-Winners", add the nodeid of the Organization to the context, and then use a different view/template on the Award Winners page when there is an organization id in the context, and fetch the related info from that node.
I have managed to get somewhere with your proposed solution which now allows me to render the content in the context of the current site so we get the correct language for the outer template. I have one outstanding issue to try and resolve and that relates to the breadcrumbs on the site which now obviously show the path only to the award winners page and not the "virtual path" to the award winners node.
Pretty much what Morten said. I built something recently where we had a site of "shared"content, and several individual sites, which displayed their own content, as well as the shared stuff (in a big list) . I set up a ContentFinder to look for the shared content and fetch it on the other sites, but as you've found, the shared pages are considered to be in their original location, not where you're displaying them.
In order to get round this, I just had to refactor some of my Partials for things like navigation, breadcrumbs etc. I wrote a helper method to get the root node for the current domain, and use that as the starting point for navigation etc, rather than going back to the root for the current page. Here's the code for my helper method:
public static IPublishedContent GetDomainRootNode()
{
IPublishedContent root = null;
if (UmbracoContext.Current.PublishedContentRequest.DomainUri != null)
{
string currentDomain = "http://" + UmbracoContext.Current.HttpContext.Request.Url.DnsSafeHost;
//try and find the current domain assigned to a root homepage node
foreach (var rootPage in UmbracoContext.Current.ContentCache.GetByXPath("/root/HomePage [@isDoc]"))
{
if (rootPage.UrlWithDomain().StartsWith(currentDomain))
{
root = rootPage;
break;
}
}
}
//if no domain found, assume we're on local dev and just return the first home page we can find
if (root == null)
{
root = UmbracoContext.Current.ContentCache.GetSingleByXPath("/root/HomePage [@isDoc][1]");
}
return root;
}
There might be a better way of doing this, but it worked pretty well for my fairly simple needs. For the breadcrumbs, I just check the DocType in my Partial and if it's the one from the shared site (which has a unique DocType), I render out the bredcrumbs using slightly different logic.
If we could manipulate more of the returned IPublishedContent item we would be onto a winner we could (for render purposes) assign a new parent dynamically...but we can't!
This site is currently in the process of being upgraded to v7 but right now it is stuck in v6 with MasterPages and macroscripts and not views so I have that to deal with these issues as well. I have almost nailed the breadcrumbs with some very ugly path manipulation.
First I check HttpContext.Current.Items for the key I'm looking for, if it doesn't exist everything behaves as normal. If it does exist then I get a collection of "Virtual Ancestors" from the shared content node which is as simple as sharedContent.Ancestors().Where("Visible && Level > 3"). On the node that I am rending the content on I build the path up with something like this:
foreach (var item in virtualAncestors)
{
var urlParts = new Uri(item.Url).AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var path = string.Join("/", urlParts.Skip(1)); // Skips over the first level as that is technically the current node
<li><a href="@string.Concat(Model.Url, path)" class="current after-current">@Library.Coalesce(item.MenuText, item.Name)</a></li>
}
If we could manipulate more of the returned IPublishedContent item we would be onto a winner we could (for render purposes) assign a new parent dynamically...but we can't!
That would probably not be a good solution anyways, since the the IPublishedContent object is not necessarily going to be the same instance as the one you would get in other places in your code, making it confusing/misleading.
And since it might be cached by Umbraco, you would get race conditions as to which page request modifies the object last, making for very weird behavior as soon as you get into concurrent request scenarios :)
Not sure if it's in v6 (it's definetely in later versions of v7) but there's a WrappedPublishedContent class (which is technically IPublishedContent) you can inherit from an manipulate the properties on. I've not used it, so I don't know if it would work, but might be worth a look.
IContentFinder returns content with the wrong site features in multi-language site
I have a customer with a pretty large multi-language site that roughly takes the following structure:
...etc etc. Each site has its own navigation in it's own language. The UK site hosts all of the
Organisation
(award winner) nodes that have been imported from SalesForce which is a process that runs nightly. Currently there is ~500 nodes. In the other country sites they will have the top level awards page but show the actual awards from the UK site (hopefully you're still following).I've implemented an
IContentFinder
which works to the point that it returns the correct content as intended except it is rendered in the context of the UK site and so shows English navigation etc when the content appears on the French site.Here is the implementation which is pretty straight forward:
Am I doing something obviously wrong or missing something or is this just not a suitable solution to this problem?
Any help appreciated.
Thanks, Simon
The content finder basically just sets the current page, so if the current page is in the UK site, then then page will be rendered in that context.
You could probably use your content finder to set the current page to f.ex. "/FR/Award-Winners", add the nodeid of the Organization to the context, and then use a different view/template on the Award Winners page when there is an organization id in the context, and fetch the related info from that node.
Interesting idea - I will have a look into that further and see what can be achieved.
Thanks, Simon
Hi Morten,
I have managed to get somewhere with your proposed solution which now allows me to render the content in the context of the current site so we get the correct language for the outer template. I have one outstanding issue to try and resolve and that relates to the breadcrumbs on the site which now obviously show the path only to the award winners page and not the "virtual path" to the award winners node.
It's certainly a step forward though so thanks!
Simon
Yeah, breadcrumbs can be tricky when there is not an actual node. :)
Glad it helped.
Pretty much what Morten said. I built something recently where we had a site of "shared"content, and several individual sites, which displayed their own content, as well as the shared stuff (in a big list) . I set up a ContentFinder to look for the shared content and fetch it on the other sites, but as you've found, the shared pages are considered to be in their original location, not where you're displaying them.
In order to get round this, I just had to refactor some of my Partials for things like navigation, breadcrumbs etc. I wrote a helper method to get the root node for the current domain, and use that as the starting point for navigation etc, rather than going back to the root for the current page. Here's the code for my helper method:
There might be a better way of doing this, but it worked pretty well for my fairly simple needs. For the breadcrumbs, I just check the DocType in my Partial and if it's the one from the shared site (which has a unique DocType), I render out the bredcrumbs using slightly different logic.
Thanks Tim.
If we could manipulate more of the returned
IPublishedContent
item we would be onto a winner we could (for render purposes) assign a new parent dynamically...but we can't!This site is currently in the process of being upgraded to v7 but right now it is stuck in v6 with MasterPages and macroscripts and not views so I have that to deal with these issues as well. I have almost nailed the breadcrumbs with some very ugly path manipulation.
First I check
HttpContext.Current.Items
for the key I'm looking for, if it doesn't exist everything behaves as normal. If it does exist then I get a collection of "Virtual Ancestors" from the shared content node which is as simple assharedContent.Ancestors().Where("Visible && Level > 3")
. On the node that I am rending the content on I build the path up with something like this:Thanks for the input guys!
That would probably not be a good solution anyways, since the the IPublishedContent object is not necessarily going to be the same instance as the one you would get in other places in your code, making it confusing/misleading.
And since it might be cached by Umbraco, you would get race conditions as to which page request modifies the object last, making for very weird behavior as soon as you get into concurrent request scenarios :)
Not sure if it's in v6 (it's definetely in later versions of v7) but there's a WrappedPublishedContent class (which is technically IPublishedContent) you can inherit from an manipulate the properties on. I've not used it, so I don't know if it would work, but might be worth a look.
is working on a reply...