Guidelines for creating urls/anchor-tags and accessing url-parameters

Topics: General, XSLT
Apr 5, 2011 at 1:56 PM

Hi,

Im creating a simple store/shop with Composite-C1, but I need help or a push in the right direction with the following:

I want to have urls like this: shop.aspx/[My_Category]/[Product-item-name]

What I have done so far:

Created two datatypes:

  • Product(name,price,category(data-reference to a Categoryitem))
  • Category(name)

Created a page(shop.aspx) with an xslt function (ShopItems).

What I think I need:

The function ShopItems to do the following:

  • If no category is specified in the url. Return a list of all categories
  • if category specified, but no product. Return a list of all products in the specifed category
  • if category and product specified. Return a single product item.

I also need a function to create url to the categories

and a function to create url to the products.

Are you suppose to create the friendly-urls manually? or is all urls transformed before its sent to the client?

My solution is heavyli inspired by the News package and how it works. I just cant figure out how its grapping information from the url.

I think i need something like the GetNewsFilterFromUrl function, but like in this discussion: http://compositec1.codeplex.com/discussions/237709, I was not able to see how it was implemented.

I hope my questions arent too confusing, and please tell me if I need to explain myself further.

Thinus

Apr 5, 2011 at 2:19 PM

i would highly recommend going for this schema instead, its easier to read and more seo friendly. You could even omit the .aspx extension entirely.

  • shop.aspx
  • shop/[My_Category].aspx
  • shop/[My_Category]/[Product-item-name].aspx

This is easy to implement with url-rewriting if you know that shop will be the starting node all the time. Internally you can rewrite the url to append category and product name as PathInfo, or you can create a Context Item that contains information about the category and product name that you'll retrieve in your ShopItems xslt function.

You would of course need to create these url's manually since its not "real" pages and therefor won't show up in the sitemap and the Composite navigation controls. You have to render them "by hand" by creating a tags and setting href to be the category and product names that you are listing.

Apr 6, 2011 at 8:34 AM
Edited Apr 6, 2011 at 8:34 AM

Thanks for the answer, you are right; your url syntax is more clear. Later in my project I will try the Contrib project so i can get even more nicer urls :)

Could you give an example how one or more of the following is done?

  1. Internally you can rewrite the url to append category and product name as PathInfo
  2. or Create a Context Item that contains information about the category and product name
  3. Retrieve category and product names from url and  in your ShopItems xslt function.

I guess there are these steps in point 3, correct me if I am wrong.

  1. Retrieve url
  2. UrlDecode category and product
  3. Find corresponding category and product with the decoded string from the url.
Apr 7, 2011 at 12:04 AM

i'll come up with a simple HttpModule tomorrow that you can use to

  1. recognize the url-pattern
  2. extract category and product information
  3. rewrite the url
  4. use the extracted information in a XSLT function

right now its 1 am and i should try to get some sleep :)

Apr 7, 2011 at 8:07 AM

I'll look forward for your HttpModule :) - I'm sure others also can use it as a template for other projects.

Apr 7, 2011 at 6:17 PM

So, here you go... compile this and add the modules to your modules collection in web.config. The shopGuid should of course be the Guid of your shop-page in C1

using System;
using System.Text.RegularExpressions;
using System.Web;

using Composite.Data;

namespace Composite.App_Code
{
    public class ShopHttpModule : IHttpModule
    {
        private static Guid shopGuid = new Guid("b9ecde1c-0ac4-4e9d-b102-770b9fce9029");

        private void app_BeginRequest(object sender, EventArgs e)
        {
            var ctx = ((HttpApplication)sender).Context;

            using (var conn = new DataConnection())
            {
                var sitemap = conn.SitemapNavigator;
                var node = sitemap.GetPageNodeById(shopGuid);
                if (node != null)
                {
                    string url = node.Url.Replace(".aspx", String.Empty);
                    string pattern = url + "/(.+).aspx";

                    var match = Regex.Match(ctx.Request.Url.LocalPath, pattern);
                    if (match.Success && match.Groups.Count == 2)
                    {
                        var parts = match.Groups[1].Value.Split(new [] { '/' }, StringSplitOptions.RemoveEmptyEntries);
                        if (parts.Length >= 1)
                        {
                            ctx.Items.Add("shop_category", parts[0]);

                            if (parts.Length == 2)
                            {
                                ctx.Items.Add("shop_item", parts[1]);
                            }

                            ctx.RewritePath(node.Url);
                        }
                    }
                }                
            }
        }

        void IHttpModule.Init(HttpApplication app)
        {
            app.BeginRequest += new EventHandler(app_BeginRequest);
        }

        void IHttpModule.Dispose()
        {

        }
    }
}

I didn't find any built-in function to access Context Items in C1 xslt, so i just quickly tested it by inserting a UserControl as this

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Shop.ascx.cs" Inherits="Composite.Renderers.UserControls.Shop" %>

<h1>Shop module</h1>

Category: <%= Context.Items["shop_category"] ?? "Empty" %> <br />
Item: <%= Context.Items["shop_item"] ?? "Empty" %>

Apr 7, 2011 at 6:24 PM
Edited Apr 7, 2011 at 6:27 PM

this is common requested functionality and is solved in ie. Sitecore by having a * node. Its basically a normal page-node where the title is *. This means that if you have a hireachy like this

  • site
    • shop
      • *

the cms will automatically figure out, that when you enter /site/shop/something.aspx or /site/shop/something_else.aspx that it should render this *-page. And you could of course take it further so

  • site
    • shop
      • *
        • *

would allow the user to access /site/shop/something/specal_shoe.aspx and the cms figures out that its the * on second level it should render.

Using a double asterisk (**) would tell the cms, that no matter how deep the url goes, it should just render this page. So if you didn't need a special page for the Shop Item, but just the same page for categories and items you could this

  • site
    • shop
      • **

and the user could still enter /site/shop/something/specal_shoe.aspx as url and see a page.

I use it myself on several sites and its something i could put into the C1Contrib package pretty easily if it has interest.

Apr 8, 2011 at 8:56 PM

Thanks for HttpModule.

The Category and Product strings are getting added to the Context.Items, but im getting a 404 when its redirecting the url.

The resource cannot be found.

Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable.  Please review the following URL and make sure that it is spelled correctly. 

Requested URL: /Unika/Shop.aspx

strangely, if I browse to /Unika/Shop.aspx manually.The Shop page is showing up fine.

This is what I did:

  1. I added ShopHttpModule.cs to my App_Code folder.
  2. Added <add name="ShopHttpModule" type="Composite.App_Code.ShopHttpModule"/> to IIS6 & IIS7 Clasic mode
  3. Added <add name="ShopHttpModule" type="Composite.App_Code.ShopHttpModule"/> to IIS7 Intergrated mode configuration
  4. Tryed this url /Unika/Katalog/MyCategory/MyProduct.aspx

And again, thanks for your work burningice

Apr 10, 2011 at 6:02 PM

where in the list of httpmodules did you add your own one? if /Unika/Shop.aspx is a real C1 url the problem is that your module is added after CompositeRequestInterceptor which is the module that will take a C1 url and make sure it renders correctly. Make sure your shop-module is added before this one.

Apr 10, 2011 at 8:13 PM

Thanks again burningice.  It was added after CompositeRequestInterceptor. I moved the shop-module up before CompopsiteRequestIntercepter and its working now. :) 

Im just wondering if im using Composite C1 the correct way? It seems to be a relative complex task for such a simple webshop I am creating. 

Should I have used pages instead of custom datatypes for my products and categories? or am I missing something?

 

The sitecore functionality looks awesome, but atm its a "nice to have" for my site, and not "must have" :) . I'll request i later if I need it..

 

Apr 11, 2011 at 9:44 AM

Do you mean if its too complex with this url-pattern or that the creation of datatypes in C1 is too much for a small webshop?

I would always recommend using dedicated datatypes for such a task, since you can't restrict and make sure which fields are present on your products of just using pages. There is nothing more annoying than a webshop where the product-pages doesn't look consistent.

Apr 19, 2011 at 1:10 PM

I was just wondering if I was using composite the right way, or if I should have structured my site in a different direction. But it looks like a custom datatype was the right choice.

Do you know what URL methods I should use to create and extract information from my url?

Lets say my original productname was: "old toy car"

Is there a method in composite that can create a string i.e: "old-toy-car" ?

And is there a string that can transform: "old-toy-car" to "old toy car" ?

Apr 19, 2011 at 1:52 PM

Looking through the source of the Add Page workflow we can see that the url-title is constructed on basis of the page-title but unfortunately the method doing it is not public so you would have to copy-paste and use this snippet

(from the file AddNewPageWorkflow.cs - http://compositec1.codeplex.com/SourceControl/changeset/view/6706#46135)

private string GenerateUrlTitleFromTitle(string title)
{
    title = title.Trim().Replace(" ", "-");

    RegexClientValidationRule regexClientValidationRule = ClientValidationRuleFacade.GetClientValidationRules(somePageInstance, "UrlTitle").OfType<RegexClientValidationRule>().Single();

    StringBuilder generated = new StringBuilder();

    Regex regex = new Regex(regexClientValidationRule.Expression);

    foreach (char c in title)
    {
        string matchString = new string(c, 1);
        if (regex.IsMatch(matchString) == true)
        {
            generated.Append(c);
         }
     }

     return generated.ToString();
 }

this will return old-toy-car from old toy car. BUT... if you know that your product name won't contain any illegal characters a much simpler solution is just to use Replace("-", " ") and Replace(" ", "-")

May 4, 2011 at 3:30 AM
burningice wrote:

this is common requested functionality and is solved in ie. Sitecore by having a * node. Its basically a normal page-node where the title is *. This means that if you have a hireachy like this

  • site
    • shop
      • *

the cms will automatically figure out, that when you enter /site/shop/something.aspx or /site/shop/something_else.aspx that it should render this *-page. And you could of course take it further so

  • site
    • shop
      • *
        • *

would allow the user to access /site/shop/something/specal_shoe.aspx and the cms figures out that its the * on second level it should render.

Using a double asterisk (**) would tell the cms, that no matter how deep the url goes, it should just render this page. So if you didn't need a special page for the Shop Item, but just the same page for categories and items you could this

  • site
    • shop
      • **

and the user could still enter /site/shop/something/specal_shoe.aspx as url and see a page.

I use it myself on several sites and its something i could put into the C1Contrib package pretty easily if it has interest.

 

Are you saying this is built in functionality of C1? Or does this apply only if you use the code module provided. Also, when you're talking about "title" I'm assuming you mean the "Page Title" correct?

You mention "sitecore" but i'm not sure what that is in reference to.

May 4, 2011 at 9:06 AM

Nono, the above scenario is just an theoretical example, C1 has not such functionality built in.

And sitecore is the CMS system Sitecore http://www.sitecore.net/... here are some articles/blogs about the wildcard functionality

Actually in the latest commit of the Contrib project i implemented a similar feature. But instead of having a wildcard page, im allowing you to append as much as you like to the url, and the part that doesn't match any page is stripped away but can be accessed via PathInfo. You can read more about it here http://compositec1contrib.codeplex.com/discussions/255920. With this you can have an url like /shop/category/shoes even if you only have a page in C1 named Shop. Now the Contrib system will take away /category/shoes and render the Shop-page, but you can retrieve /category/shoes via Request.PathInfo