I have a section on the website for listing categories and subcategories of products.
The subcategories have a merchello product list view and at this point the products are listed with the url "mywebsite.com/products/category/subcategory".
When I click on a product the url points to "mywebsite.com/product".
In the merchello.config you can define the route by culture:
<contentFinderCulture>
<!-- You can set slug prefixes for products for each culture if you need to.
e.g. with the following settings, en-US product URLs will be /en/[slug]
<route cultureName="en-US" productSlugPrefix="en" />
-->
</contentFinderCulture>
Or in the back office per product you can change the slug...
If you need something more sophisticated, you could write your own content finder based on the slug and not use the one that ships with Merchello.
How can I disable the Merchello content finder, because I have created a new one for products and is throwing the same error
Parent must be set in order to recurse
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NotSupportedException: Parent must be set in order to recurse
Source Error:
I am registering my content finder on OnApplicationStarting
Could anyone who has made their own ProductFinder with SEO friendly urls, please post their code over here? Would help me, and propably many others in getting the urls for products right.
I thought I'd share my content finders with you as you're asking, there are 3, one for brands, one for products (with categories) and one for members, this code never went into production, but was working before we went with a different approach. Some of the member content finder was code found through this forum.
I would not say that this is the correct way to do this, but I managed to get it to work.
I hope this helps someone
using System;
using System.Linq;
using System.Web;
using umbraco.cms.businesslogic.member;
using umbraco.cms.businesslogic.web;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Web;
using Umbraco.Web.Routing;
using WG2K.Classes;
namespace MySite.BusinessLogic
{
public class MyContentFinder : IContentFinder
{
public bool TryFindContent(PublishedContentRequest request)
{
var path = request.Uri.GetAbsolutePathDecoded();
var urlParts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (urlParts.Length > 3 && urlParts[1].ToLower() == "brands")
{
var language = urlParts[0];
var categoryName = urlParts[2];
var brandName = urlParts[3];
var contentCache = UmbracoContext.Current.ContentCache;
//var brandParent = contentCache.GetById(4385);
string currentDomain = HttpContext.Current.Request.ServerVariables["SERVER_NAME"];
int rootNodeID = Domain.GetRootFromDomain(currentDomain);
if (rootNodeID == -1) return false;
var siteName = Utils.FriendlyName(contentCache.GetById(rootNodeID).Name);
var siteProductsNode = UmbracoContext.Current.ContentCache.GetByRoute("/" + siteName + "/" + language + "/brands/brand-builder", true);
var brandParent = siteProductsNode.AncestorOrSelf(1).Children.Where(x => x.DocumentTypeAlias == "WG2K_Starter_Data").FirstOrDefault().Children.Where(x => x.DocumentTypeAlias == "ProductBrandList").FirstOrDefault();
var brand = brandParent.Children.SingleOrDefault(x => Utils.FriendlyName(x.Name) == brandName && x.HasProperty("brandCategory") && Utils.FriendlyName(contentCache.GetById(Int32.Parse(x.GetProperty("brandCategory").Value.ToString())).Name) == categoryName);
if (brandParent == null) return false; // not found
HttpContext.Current.Items["currentBrand"] = brand;
HttpContext.Current.Items["brandName"] = brand.Name;
request.PublishedContent = siteProductsNode;
return true;
}
if (urlParts.Length > 3 && urlParts[1].ToLower() == "products")
{
var language = urlParts[0];
var categoryName = urlParts[2];
var productName = urlParts[3];
var contentCache = UmbracoContext.Current.ContentCache;
//var productParent = contentCache.GetById(4385);
string currentDomain = HttpContext.Current.Request.ServerVariables["SERVER_NAME"];
int rootNodeID = Domain.GetRootFromDomain(currentDomain);
if (rootNodeID == -1) return false;
var siteName = Utils.FriendlyName(contentCache.GetById(rootNodeID).Name);
var siteProductsNode = UmbracoContext.Current.ContentCache.GetByRoute("/" + siteName + "/" + language + "/products/product-builder", true);
var productParent = siteProductsNode.AncestorOrSelf(1).Children.Where(x => x.DocumentTypeAlias == "WG2K_Starter_Data").FirstOrDefault().Children.Where(x => x.DocumentTypeAlias == "sharedProductList").FirstOrDefault();
var product = productParent.Children.SingleOrDefault(x => Utils.FriendlyName(x.Name) == productName && x.HasProperty("productCategory") && Utils.FriendlyName(contentCache.GetById(Int32.Parse(x.GetProperty("productCategory").Value.ToString())).Name) == categoryName);
if (productParent == null) return false; // not found
HttpContext.Current.Items["currentProduct"] = product;
HttpContext.Current.Items["productName"] = product.Name;
request.PublishedContent = siteProductsNode;
return true;
}
return false;
}
}
public class MemberProfileContentFinder : IContentFinder
{
public bool TryFindContent(PublishedContentRequest contentRequest)
{
var urlParts = contentRequest.Uri.GetAbsolutePathDecoded().Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
//Check if the Url Parts
// Starts with /member/*
if (urlParts.Length > 1 && urlParts[1].ToLower() == "member")
{
//Lets try & find the member
var categoryName = urlParts[2];
var productName = urlParts[3];
var memberName = urlParts[1];
LogHelper.Info(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType, "this is the host " + urlParts[1]);
//Try and find a member where the property matches the memberName
var tryFindMember = Member.GetAll.SingleOrDefault(x => Utils.FriendlyName(x.getProperty("_firstName").Value.ToString()) + "-" + Utils.FriendlyName(x.getProperty("_surname").Value.ToString()) == memberName);
//var tryFindContent = Content.GetAll
//Need to set the member ID or pass member object to published content
//Will pass a null to view if not found & can display not found member message
HttpContext.Current.Items["memberProfile"] = tryFindMember;
//Add in string to items - for profile name user was looking for
HttpContext.Current.Items["memberName"] = memberName;
//Set the Published Content Node to be the /Profile node - can get properties off it & my member profile in the view
contentRequest.PublishedContent = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetByRoute("/contributors/newcontibutorpage/");
//Return true to say found something & stop pipeline & other contentFinder's from running
return true;
}
//Not found any content node to display/match - so run next ContentFinder in Pipeline
return false;
}
}
}
Also used a utility to format the url, that is used in here
public static string FriendlyName(string phrase)
{
string str = phrase.ToLower();
// invalid chars, make into spaces
str = Regex.Replace(str, @"[^a-z0-9\s-]", "");
// convert multiple spaces/hyphens into one space
str = Regex.Replace(str, @"[\s-]+", " ").Trim();
// hyphens
str = Regex.Replace(str, @"\s", "-");
return str;
}
We can Achieve this simply ,please go through this
Create a Class file with any name ExampleFinder.cs
Paste the code
public class ExampleFinder: IContentFinder
{
public bool TryFindContent(PublishedContentRequest request)
{
var merchelloHelper = new MerchelloHelper();
// var slug = request.Uri.AbsolutePath.TrimStart('/');// EnsureNotStartsOrEndsWith('/');
var currentCulture=Thread.CurrentThread.CurrentCulture.Name;
var path = request.Uri.GetAbsolutePathDecoded();
var urlParts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var productSlug= urlParts[urlParts.Length - 1];
var productContent = merchelloHelper.TypedProductContentBySlug(productSlug);
if (productContent == null) return false;
var urlList= urlParts.Where(x => x != productSlug).Select(x => x).ToList();
var url= "/"+ String.Join("/", urlList);
var content = umbraco.uQuery.GetNodeByUrl(url);
// var content = UmbracoContext.Current.ContentCache.GetByRoute(url);
if (content != null)
{
request.PublishedContent = productContent;
return true;
}
return false;
}
}
3.) Add the missing references to your project.
4.) Create a Event handler by Inheriting the Umbraco Event Handler
5.) Paste the code.
public class CustomApplicationEventHandler : ApplicationEventHandler
{
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
this.RegisterContentFinders();
}
private void RegisterContentFinders()
{
var types = ContentFinderResolver.Current.GetTypes();
var UmbcontentFinderByIdPathIndex = ContentFinderResolver.Current.GetTypes().IndexOf(typeof(ContentFinderByIdPath));
ContentFinderResolver.Current.RemoveType <ContentFinderProductBySlug>();
UmbcontentFinderByIdPathIndex = ContentFinderResolver.Current.GetTypes().IndexOf(typeof(ContentFinderByIdPath));
//Register our content finder class here
ContentFinderResolver.Current.InsertType<ExampleFinder>(UmbcontentFinderByIdPathIndex + 1);
} }
Path to product URL
Hi,
I have a section on the website for listing categories and subcategories of products.
The subcategories have a merchello product list view and at this point the products are listed with the url "mywebsite.com/products/category/subcategory".
When I click on a product the url points to "mywebsite.com/product".
Is there any way to change this?
There are a few ways -
In the
merchello.config
you can define the route by culture:Or in the back office per product you can change the slug...
If you need something more sophisticated, you could write your own content finder based on the slug and not use the one that ships with Merchello.
Merchello's content finder of reference:
https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Web/Routing/ContentFinderProductBySlug.cs
Umbraco docs for IContentFinder:
https://our.umbraco.org/documentation/Reference/Routing/Request-Pipeline/IContentFinder
Great.
I was able to create a custom content finder and now the URL looks like "mywebsite.com/products/category/subcategory/product".
Thanks!
After adding a new contentFinderCulture i get a 404 when going to the page.
The url to the product is changed from /product
/products/product
but that page returns a 404... :/
Never mind!
Turns out i had a different language selected in "culture and hostnames" which made the slug wrong.
Hi Rusty,
How can I disable the Merchello content finder, because I have created a new one for products and is throwing the same error
I am registering my content finder on
OnApplicationStarting
Appreciate any help.
Hi Pedro ,
Could you please help me, I am also trying to do this but it fails, could you please guide me.
Hi Pedro,
I am struggling with the same issue as you.
Would it be possible to get a glance at your custom content finder?
Many Thanks,
Miguel L.
Could anyone who has made their own ProductFinder with SEO friendly urls, please post their code over here? Would help me, and propably many others in getting the urls for products right.
Thanks!
Bumping this post - could anyone please share their custom content finders for category URLs?
Bumpity Bump.
Plz share--- I need it too... :-)
Bump
Currently, Ive been building my first merchello site, and i`ve also encountered this problem.
You need to append logic to an existing class which extends ApplicationEventHandler, or create one if you dont have one yet.
HI Guys
I thought I'd share my content finders with you as you're asking, there are 3, one for brands, one for products (with categories) and one for members, this code never went into production, but was working before we went with a different approach. Some of the member content finder was code found through this forum.
I would not say that this is the correct way to do this, but I managed to get it to work.
I hope this helps someone
Also used a utility to format the url, that is used in here
Hey Guys,
We can Achieve this simply ,please go through this
Create a Class file with any name ExampleFinder.cs
Paste the code
3.) Add the missing references to your project.
4.) Create a Event handler by Inheriting the Umbraco Event Handler
5.) Paste the code.
6.) Add missing references.
7.) Buid and Run.
This is Work's fine for me ,thank you !
Hello Guys,
I want my product open using my custom URL. The current product URL name is for example "Test", and the URL is like that https://website/test/ but I want the product to open my custom URL, like https://website/products/test/ and https://website/products/categoryname/test/
as I created a text string in document type products URL alias, and I add "/products/test/" and "/products/categoryname/test/"
Please check the screenshot and Please guide me step by step
is working on a reply...