Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Saied 349 posts 674 karma points
    Sep 02, 2015 @ 19:37
    Saied
    0

    Is it possible to map the Url of child nodes to custom data?

    I am connecting to an external database to retrieve products. I have pages for each product, but I am not sure how to get the map the Url to the products returned in the Custom Model. This is what my view currently looks like:

    @model ProductSearchResultsModel
    
    @{
        Layout = "../Master.cshtml";
    }
    <div style="padding-top:85px;">
        <h1 style="font-size:24px;">Search Criteria</h1>
        <br/>
        <p><span style="font-weight: bold">Make: </span>@Model.ProductFilterModel.Make</p>
        <p><span style="font-weight: bold">Model: </span>@Model.ProductFilterModel.Model</p>
        <p><span style="font-weight: bold">Engine: </span>@Model.ProductFilterModel.Engine</p>
        <p><span style="font-weight: bold">Year: </span>@Model.ProductFilterModel.Year</p>
    
        <br/>
        <h1 style="font-size:24px;">Search Results</h1>
        <br/>
        @foreach (var product in Model.Products)
        {
            <div style="border: 1px solid black; padding: 10px; margin: 10px;">
                <h1><span style="font-weight: bold">Name: </span>@product.Name</h1>
                <p><span style="font-weight: bold">Part Number: </span>@product.PartNumber</p>
                <p><span style="font-weight: bold">Type: </span>@product.Type</p>
                <p><span style="font-weight: bold">Short Description: </span>@product.ShortDescription</p>
            </div>
        }
    

    It does inherit from RenderModel, so I do have acccess to the Umbraco extras. I was thinking of creating a table in my external db that would hold the page id's and link them with the products, but this is a last resort since it would require some extra maintenance. Is there another option?

  • James Jackson-South 489 posts 1747 karma points c-trib
    Sep 03, 2015 @ 00:02
    James Jackson-South
    1

    Hi Saied,

    What I think you are looking for is a way to map your products to virtual nodes within the CMS yeah?

    I.E To be able to list pages for each product without having to create a page for each product in the back office?

    If so, you should use an UmbracoVirtualNodeRouteHandler to create a custom route for your product pages. There's an excellent article on Shannon's blog about this functionality here but I'll also summarize it with an example of how I'd do it should the URL change.

    If I'm way off bat here, sorry...

    First you would set up a generic document type with matching template in the back office. Lets call it Product. A single page of this document type will be used to display each individual product virtually.

    You then need to create an implementation of UmbracoVirtualNodeRouteHandler to tell Umbraco how to map the route to the page.

    /// <summary>
    /// The product virtual node route handler.
    /// </summary>
    public class ProductVirtualNodeRouteHandler : UmbracoVirtualNodeRouteHandler
    {
        /// <summary>
        /// returns the <see cref="IPublishedContent"/> associated with the route.
        /// </summary>
        /// <param name="requestContext">
        /// The request context.
        /// </param>
        /// <param name="umbracoContext">
        /// The umbraco context.
        /// </param>
        /// <returns>
        /// The <see cref="IPublishedContent"/>.
        /// </returns>
        protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext)
        {
            UmbracoHelper helper = new UmbracoHelper(umbracoContext);
            string alias = "Product";
            return helper.TypedContentAtRoot()
                .First()
                .Descendants()
                .First(d => d.DocumentTypeAlias.InvariantEquals(alias));
        }
    }
    

    Also create a custom route you can do this via inheriting the ApplicationEventHandler class.

    /// <summary>
    /// The custom route handler for assigning custom routes.
    /// </summary>
    public class CustomRouteRegistration : ApplicationEventHandler
    {
        /// <summary>
        /// Boot-up is completed, this allows you to perform any other boot-up logic required for the application.
        /// Resolution is frozen so now they can be used to resolve instances.
        /// </summary>
        /// <param name="umbracoApplication">The current <see cref="UmbracoApplicationBase"/></param>
        /// <param name="applicationContext">The Umbraco <see cref="ApplicationContext"/> for the current application.</param>
        protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            RouteTable.Routes.MapUmbracoRoute(
            "ProductPage",
            "Products/Product/{id}/",
            new
            {
                controller = "Product",
                action = "GetProduct"
            },
            new ProductVirtualNodeRouteHandler());
        }
    }
    

    Now it's time to create a controller. In there you will grab the product from your external database and map it to your view model.

    /// <summary>
    /// The product page controller.
    /// </summary>
    public class ProductController : RenderMvcController
    {
        /// <summary>
        /// Renders an individual product page.
        /// </summary>
        /// <param name="model">
        /// The <see cref="RenderModel"/> representing the current Umbraco node.
        /// </param>
        /// <param name="id">
        /// The id of the product.
        /// </param>
        /// <returns>
        /// The <see cref="ActionResult"/> encapsulating the result of an action method that is used to 
        /// perform a framework-level operation on behalf of the action method.
        /// </returns>
        public ActionResult GetProduct(RenderModel model, int id)
        {
            // Do your logic here to pull the product from the external database and map it to your 
            // custom viewmodel
    
            return this.View("Product", viewmodel);
        }
    }
    

    Umbraco should now be able to find all the products by url. You will also now also be able to use Url.Action() to generate the correct url to your products since you have created a custom route.

    Hope that helps

    James

    P.S I wrote this without the IDE so I dunno if it compiles. It should :)

  • Saied 349 posts 674 karma points
    Sep 03, 2015 @ 16:26
    Saied
    0

    James,

    I have been playing around with the code you gave me and you read my mind. This is exactly what I was looking for. I did implement it and everything worked fine. I am getting an error now, but I also have a few questions:

    1. Does a Product node have to exist in the back office or is a DocumentType and Template enough?
    2. I wanted something better more friendly that /products/1, so I just ended up adding {name} to my route as well. Do you know if there is a way to avoid having the {id} parameter in there, but still have access to it? Also, {name} could have illegal characters in it, so I created a helper to strip those out unless Umbraco has something to handle this?
    3. Where do you normally put the custom route handler and route mapper classes? I currently have them in the root of my website.
      1. OK, on to the error message. Originally I tested the code with no Masterpage and with a model that does not inherit from RenderModel and everything works fine. Now when I include aMasterpage and inherit from RenderModel, when it gets to the following line:

    var helper = new UmbracoHelper(umbracoContext); in FindContent, it throws the following error:

    Value cannot be null. Parameter name: umbracoContext

    Oddly though, have it throws it a few times, it still renders the Product Page, even with an exception.

    Thanks again for the great help.

  • Saied 349 posts 674 karma points
    Sep 03, 2015 @ 16:50
    Saied
    0

    James,

    I found this snippet of code and now I don't get the error, but I don't understand what it does and why I don't get the error anymore. I added umbracoContext = UmbracoContext.EnsureContext(new HttpContextWrapper(HttpContext.Current), ApplicationContext.Current); to my FindContent method.

  • James Jackson-South 489 posts 1747 karma points c-trib
    Sep 06, 2015 @ 00:21
    James Jackson-South
    0

    Interesting...

    Normally that umbracoContext parameter shouldn't be null. That method is ensuring that you have an UmbracoHelper available to query the published content cache for your node and I would much rather we figured out why the code I supplied doesn't work as it should than add the additional code. Did you set up the handlers exactly as I described? Can I see how your controller is coded?

    To answer your questions though.

    1. Yes. A node of that document type does have to exist in the back office. We need something to pin our virtual product nodes to.

    2. I wouldn't remove the id from your path. There should be a primary key for your product data so that any lookups are fast. Adding the name as a slug though is a good idea. I would do it as an optional parameter though so your structure is something like mysite.com/products/{id}/{name} where the url would actually still route correctly without the name. There is an extension method to the string type in the Umbraco.Core namespace called ToUrlSegment() that you can use to clean up the name.

    Your new route would be bound like:

        RouteTable.Routes.MapUmbracoRoute(
        "ProductPage",
        "Products/Product/{id}/{slug}",
        new
        {
            controller = "Product",
            action = "GetProduct",
            slug = UrlParameter.Optional
        },
        new ProductVirtualNodeRouteHandler()),
        new { id = @"(\d)+" }); // Add some validation for the Id.
    

    3.The location of the classes shouldn't really matter as long as they are compiled within the solution and are referenced by namespace correctly. I'm assuming you are using a web application, not a site since you are able to place them in the route.

    I would try to structure everything sensibly though and will place my classes in a separate class library project. You could organise yours under a folder called Infrastructure if you want to keep all the code together.

  • Saied 349 posts 674 karma points
    Sep 06, 2015 @ 02:34
    Saied
    0

    Hi James,

    Thanks for the suggestions and feedback. This is what my controller looks like:

    public class ProductController : RenderMvcController
    {
        public ActionResult GetProduct(RenderModel model, int id)
        {
            var db = new Database("productsDb");
    
            ProductViewModel product =
                db.Fetch<ProductViewModel>(@"SELECT p.ProductID, Type, Name, PartNumber, ShortDescription
                                                        FROM Products p
                                                        WHERE Brand = 'SCT' AND p.ProductID = @id", new {id})
                    .FirstOrDefault();
    
    
            return View("Product", product);
        } 
    }
    

    The error is happening at this line:

     var helper = new UmbracoHelper(umbracoContext);
    

    The error goes away if I add the following:

    umbracoContext = UmbracoContext.EnsureContext(new HttpContextWrapper(HttpContext.Current), ApplicationContext.Current);
    

    As a side note, I noticed that in the following line:

    return View("Product", product);
    

    Product is red like Visual Studio can't find it, I have seen this in other controllers as well, so not sure if it is a glitch. Just wanted to point it out.

    Thanks Again, Saied

  • James Jackson-South 489 posts 1747 karma points c-trib
    Sep 09, 2015 @ 12:59
    James Jackson-South
    0

    Sorry for the slow reply.

    There's nothing I could see in your code that would indicate a problem.

    Those lines intrigue me though. Have you tried cleaning and rebuilding the solution. When you build are any warnings shown?

  • Saied 349 posts 674 karma points
    Sep 09, 2015 @ 13:21
    Saied
    0

    Hi James,

    The only warnings are css warnings (3 of them). Not sure if this would help, but initially when I was testing with some static data, I was not inheriting from RenderMvcController and I wasn't getting the null context. Once I inherited from RenderMvcController, that is when I started getting it.

  • James Jackson-South 489 posts 1747 karma points c-trib
    Sep 10, 2015 @ 03:51
    James Jackson-South
    0

    The only thing I can think of that would cause an error like that would be calling the method on a separate thread from the request. It looks like you are not doing that though.

    What version of Umbraco are you running?

Please Sign in or register to post replies

Write your reply to:

Draft