As you can see I have setup a url provider so that all of my categories and companies have a custom url, eg no parent segments.
Now when you go to for example /sub-categorie-1/ it will display companies of that categorie with an url to the company of /sub-categorie-1/company-1/.
Using a custom ContentFinder I have managed to get the correct template and show the company details.
I do this by taking the last segment and do a search on the company document type alias and url name.
But the problem is that if I open url /sub-categorie-2/company-1/, even when its not a category of this company, or a non existing url like /test/company-1/ it also opens the template of the company.
Would it be hard to lookup the parent segment and do a check to see if it really is a category of that company and do a redirect if this is not the case?
I'm not sure using an IContentFinder to select the template is possible or the right approach, but if you've got it to work then great. Personally I'd use Published Content Request Preparation or using a controller return View("templateName", model) where templateName is determinded based on the document type alias of the model supplied.
With regard to your question though, this should be quite achievable. I'm not sure what the complication, you IContentFinder just needs to return a result for genuine results. So if you have a cache of all your company, category and sub-category url names then you can check that they genuinely exist.
I'd also have three IContentFinders one for categories, one for sub-categories and one for companies (though it might be easiest to combine categories and sub-categories). Each finder just handles urls of it's type and will validate a geniune url. That way it shouldn't get confused by mixing urls.
Send me some code if you like. Here is some of mine:
public class NewsCategoryContentFinder : IContentFinder
{
public bool TryFindContent(PublishedContentRequest contentRequest)
{
try
{
var newsService = new NewsService(UmbracoContext.Current);
string nextSegment;
if (!newsService.GetSegmentAfterListing(contentRequest.Uri, out nextSegment))
return false;
NewsCategory newsCategory;
if (newsService.IsCategory(nextSegment, out newsCategory))
{
var umbraco = new UmbracoHelper(UmbracoContext.Current);
//This method returns an IPublishedContent created from the Models Builder that I've extended to add RequestedCategory to avoid the controller having to re-workout which category the url relates to
var newsHome = umbraco.GetNewsHome();
newsHome.RequestedCategory = newsCategory;
contentRequest.PublishedContent = newsHome;
return true;
}
}
catch (Exception exception)
{
LogHelper.Error<NewsCategoryContentFinder>(exception.Message, exception);
}
return false;
}
}
//IsCategory looks like
public bool IsCategory(string urlSegment, out NewsCategory newsCategory)
{
foreach (var category in Categories)
{
var urlAlias = category.GetUrlName();
if (!urlSegment.Equals(urlAlias.ToUrlSegment(), StringComparison.InvariantCultureIgnoreCase))
continue;
newsCategory = category;
return true;
}
newsCategory = null;
return false;
}
//GetSegmentAfterListing looks like
public bool GetSegmentAfterListing(Uri uri, out string segment)
{
var newsHomeUri = new Uri(uri, NewsHomeUrl);
//Jump out early if this isn't a news based URL
if (!newsHomeUri.IsBaseOf(uri))
{
segment = null;
return false;
}
//What is right of the news url
var relativePath = newsHomeUri.MakeRelativeUri(uri);
segment = relativePath.OriginalString.Split('/').FirstOrDefault();
if (segment == null)
return false;
if (segment.Contains("?"))
segment = segment.Split('?').First();
segment = HttpUtility.UrlDecode(segment).ToUrlSegment();
return true;
}
What I mean was using my custom urls I created using the IUrlProvider I needed to lookup and display the correct node. So returning the correct IPublishedContent and Template.
Below is my code, I have managed to get it working by checking the parent to see if it exists and if the category belongs to the current Company from the given url:
public bool TryFindContent(PublishedContentRequest contentRequest)
{
try
{
if (contentRequest != null)
{
var url = contentRequest.Uri.AbsoluteUri;
var cachedCompanyNodes =
(Dictionary<string, ContentFinderItem>)HttpContext.Current.Cache["CachedCompanyNodes"];
if (cachedCompanyNodes != null)
{
if (cachedCompanyNodes.ContainsKey(url))
{
var contentFinderItem = cachedCompanyNodes[url];
contentRequest.PublishedContent = contentFinderItem.Content;
contentRequest.TrySetTemplate(contentFinderItem.Template);
return true;
}
}
// Split the url segments.
var path = contentRequest.Uri.GetAbsolutePathDecoded();
var parts = path.Split(new[] { '/' }, System.StringSplitOptions.RemoveEmptyEntries);
// Company can be called directly like : /company-1/
// or it can be called by category like : /category/company-1/
if (parts.Length > 0 && parts.Length < 3)
{
// Get all the root nodes.
var rootNodes = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetAtRoot();
// Find the company item that matches
var companyItem = rootNodes.DescendantsOrSelf("companyItem")
.Where(x => x.UrlName == parts.Last())
.FirstOrDefault();
if (companyItem != null)
{
bool valid = true;
// Company accessed by category
if(parts.Length == 2)
{
var categoryItem = rootNodes.DescendantsOrSelf("subCategory")
.Where(x => x.UrlName == parts.First())
.FirstOrDefault();
// Category must exists and category must be assigned to the company
int[] _categories = Array.ConvertAll(companyItem.GetPropertyValue<string>("categories").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), int.Parse);
valid = categoryItem != null && _categories.Contains(categoryItem.Id);
}
if(valid)
{
// Get the company item template.
var template = Services.FileService.GetTemplate(companyItem.TemplateId);
if (template != null)
{
// Store the fields in the ContentFinderItem-object.
var contentFinderItem = new ContentFinderItem()
{
Template = template.Alias,
Content = companyItem
};
// If the correct node is found display that node.
contentRequest.PublishedContent = contentFinderItem.Content;
contentRequest.TrySetTemplate(contentFinderItem.Template);
if (cachedCompanyNodes != null)
{
// Add the new ContentFinderItem-object to the cache.
cachedCompanyNodes.Add(url, contentFinderItem);
}
else
{
// Create a new dictionary and store it in the cache.
cachedCompanyNodes = new Dictionary<string, ContentFinderItem>()
{
{
url, contentFinderItem
}
};
HttpContext.Current.Cache.Add("CachedCompanyNodes",
cachedCompanyNodes,
null,
DateTime.Now.AddDays(1),
System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.High,
null);
}
}
}
}
}
}
}
catch(Exception e) {
LogHelper.Error<CompaniesContentFinder>("Error while searching for the company", e);
}
return contentRequest.PublishedContent != null;
}
Issue using the IContentFinder
Hi all,
I am using the
IContentFinder
to show the correct template based on the url.My content structure:
As you can see I have setup a url provider so that all of my categories and companies have a custom url, eg no parent segments.
Now when you go to for example
/sub-categorie-1/
it will display companies of that categorie with an url to the company of/sub-categorie-1/company-1/
.Using a custom ContentFinder I have managed to get the correct template and show the company details.
I do this by taking the last segment and do a search on the company document type alias and url name.
But the problem is that if I open url
/sub-categorie-2/company-1/
, even when its not a category of this company, or a non existing url like/test/company-1/
it also opens the template of the company.Would it be hard to lookup the parent segment and do a check to see if it really is a category of that company and do a redirect if this is not the case?
/Michaël
Hi Michael,
I'm not sure using an IContentFinder to select the template is possible or the right approach, but if you've got it to work then great. Personally I'd use Published Content Request Preparation or using a controller return
View("templateName", model)
where templateName is determinded based on the document type alias of the model supplied.With regard to your question though, this should be quite achievable. I'm not sure what the complication, you IContentFinder just needs to return a result for genuine results. So if you have a cache of all your company, category and sub-category url names then you can check that they genuinely exist.
I'd also have three IContentFinders one for categories, one for sub-categories and one for companies (though it might be easiest to combine categories and sub-categories). Each finder just handles urls of it's type and will validate a geniune url. That way it shouldn't get confused by mixing urls.
Send me some code if you like. Here is some of mine:
Hi David,
What I mean was using my custom urls I created using the
IUrlProvider
I needed to lookup and display the correct node. So returning the correctIPublishedContent
andTemplate
.Below is my code, I have managed to get it working by checking the parent to see if it exists and if the category belongs to the current
Company
from the given url:is working on a reply...