Copied to clipboard

Flag this post as spam?

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


  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    May 29, 2013 @ 14:47
    Warren Buckley
    0

    Umbraco 6 MVC - Custom MVC Route

    Hello all,
    I am trying to register a custom route to an action in my SurfaceCntroller as follows:

    routes.MapRoute(
      "MemberProfile"// Route name
      "user/{profileURLtoCheck}", // URL with parameters
      new { controller = "ProfileSurface", action = "RenderMemberProfile" }// Parameter defaults
    );

     

    The plan is for me to allow visitors to goto /user/warrenbuckley and it loads in the my public profile.

    public ActionResult RenderMemberProfile(string profileURLtoCheck)
    {
        //Try and find member with the QueryString value ?profileURLtoCheck=warrenbuckley
        Member findMember = Member.GetAllAsList().FirstOrDefault(x => x.getProperty("profileURL").Value.ToString() == profileURLtoCheck);
    
        //Create a view model
        ViewProfileViewModel profile  = new ViewProfileViewModel();
    
        //Check if we found member
        if (findMember != null)
        {
            //Increment profile view counter by one
            int noOfProfileViews = 0;
            int.TryParse(findMember.getProperty("numberOfProfileViews").Value.ToString(), out noOfProfileViews);
    
            //Increment counter by one
            findMember.getProperty("numberOfProfileViews").Value = noOfProfileViews + 1;
    
            //Save it down to the member
            findMember.Save();
    
    
            //Got the member lets bind the data to the view model
            profile.Name                    = findMember.Text;
            profile.MemberID                = findMember.Id;
            profile.EmailAddress            = findMember.Email;
            profile.Description             = findMember.getProperty("description").Value.ToString();
            profile.NumberOfLogins          = Convert.ToInt32(findMember.getProperty("numberOfLogins").Value.ToString());
            profile.LastLoginDate           = DateTime.ParseExact(findMember.getProperty("lastLoggedIn").Value.ToString(), "dd/MM/yyyy @ HH:mm:ss"null);
            profile.NumberOfProfileViews    = Convert.ToInt32(findMember.getProperty("numberOfProfileViews").Value.ToString()); 
        }
        else
        {
            //Couldn't find the member return a 404
            return new HttpNotFoundResult("The member profile does not exist");
        }
    
        // HERE - I Want to return an entire Umbraco page with site chrome/navi rarther than just the partial
        return PartialView("ViewProfile", profile);
    
    }

     

    I know this is more of a general MVC question, but I am sure other Umbraco developers will be coming across this problem as well i the future perhaps.

    Cheers,
    Warren :)

  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    May 29, 2013 @ 16:46
    Warren Buckley
    100

    Ok solved this on my own.
    Maybe not the perfect solution as I am sure I could do this properly with MVC routes, however I have soplved the problem with the IIS URL Rewriting module and update my web.config with the following:

        <rewrite>
          <rules>
            <rule name="ProfileURLRewrite" stopProcessing="true">
              <match url="^user/(.+)" ignoreCase="false" />
              <action type="Rewrite" url="/profile?profileURLtoCheck={R:1}" appendQueryString="true" />
            </rule>
          </rules
        </rewrite> 

     

    Let me know if there is a better way to solve this.

    Cheers,
    Warren :) 

  • Jonas Eriksson 930 posts 1825 karma points
    May 29, 2013 @ 17:11
    Jonas Eriksson
    1

    Hi Warren!

    Looks good,

    I'm playing with a "catchall" fallback handler. Works good for me in 4.7, but should be usable in MVC aswell. Stephen mentioned a feature coming in 6.1 here http://our.umbraco.org/forum/developers/api-questions/41184-Anyone-has-a-Catch-all-routes-under-node-url-

    (I updated the code since then, I will try make it good enough to fit in uComponents)

    Cheers

  • kim Thomsen 59 posts 277 karma points
    May 29, 2013 @ 17:19
    kim Thomsen
    0

    I use a API Controller for some of my Services
    And I register it like this 

    public static class WebApiConfig
    {
    public static void Register(HttpConfiguration config)
    {
    config.Routes.MapHttpRoute(
    name: "WebAPI",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
    );

    config.EnableSystemDiagnosticsTracing();
    }
    }

    and in the Application start

    WebApiConfig.Register(GlobalConfiguration.Configuration);


    So i guess you can change it to something like:


     config.Routes.MapHttpRoute(
    name: "profileAPI",
    routeTemplate: "profile/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
    );
  • Stephen 767 posts 2273 karma points c-trib
    May 29, 2013 @ 17:25
    Stephen
    0

    @Warren: what version of Umbraco are you using? With 6.1 I'd use an IContentFinder...

  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    May 29, 2013 @ 17:25
    Warren Buckley
    0

    Yeh I have it registering, so that is not the problem it was more to do with it rendering the rest of the page as opposed ot just the partial HTML.
    I resolved it by using URL rewriting in IIS

    Thanks,
    Warren :) 

  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    May 29, 2013 @ 17:30
    Warren Buckley
    0

    @Stepehen what is an IContentFinder and can you give me an example of using it please.

    Thanks,
    Warren :) 

  • Stephen 767 posts 2273 karma points c-trib
    May 29, 2013 @ 17:35
    Stephen
    0

    Knew that question will come ;-) Sebastiaan is also putting pressure so I document all this. There'll be a session at CG13 to talk about it and I'm preparing some documentation, so the best I can say right now is... stay tuned.

  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    May 29, 2013 @ 17:36
    Warren Buckley
    0

    Aww man that's a bit of a anti-climax then for a solution :-P
    Well i look forward to the session & docs then....

  • Jeroen Breuer 4908 posts 12265 karma points MVP 5x admin c-trib
    May 29, 2013 @ 17:43
    Jeroen Breuer
    0

    There is some info in this issue: http://issues.umbraco.org/issue/U4-1327#comment=67-6919

    Jeroen

  • Dan White 206 posts 510 karma points c-trib
    May 29, 2013 @ 18:12
    Dan White
    0

    @Stephen,

    I was just looking over IContentFinder.

    I implement https://umbraco.codeplex.com/SourceControl/latest#src/Umbraco.Web/Routing/IContentFinder.cs, correct? 

    But I can't find out how to add my content finder into the queue. Not in 404handlers.config, is it?

  • Stephen 767 posts 2273 karma points c-trib
    May 29, 2013 @ 18:42
    Stephen
    2

    @Dan

    Correct. Then, you want to have a class looking like the following class, anywhere in your code:

    public class Application : Umbraco.Core.ApplicationEventHandler
    {
        protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            base.ApplicationStarting(umbracoApplication, applicationContext);
            Umbraco.Web.Routing.ContentFinderResolver.Current.AddType<YourFinder>();
        }
    }

    Which will append YourFinder to the list of finders. Alternatively, you can do .InsertType<YourFinder>() to insert it at the top of the list (will run first) or play with RemoveType<T>(), InsertTypeBefore<TExisting, T>() to re-order the list as you wish so the finders run in the order that you want.

    By default the list is initialized with:

        typeof (ContentFinderByPageIdQuery), // finds /anything.aspx?umbPageID=1234
        typeof (ContentFinderByNiceUrl), // finds /path/to/node.aspx
        typeof (ContentFinderByIdPath), // finds /1234.aspx
        typeof (ContentFinderByNotFoundHandlers) // runs legacy NotFoundHandlers

    The last one ensures that the legacy NotFoundHandler still run (those configured in 404handlers.config).

    Stephan

  • Dan White 206 posts 510 karma points c-trib
    May 29, 2013 @ 19:19
    Dan White
    1

    Thx! Will .AddType<>() run before ContentFinderByNotFoundHandlers?

  • Stephen 767 posts 2273 karma points c-trib
    May 29, 2013 @ 20:10
    Stephen
    1

    Nope, AddType<>() adds to the end of the list, so after ContentFinderByNotFoundHandler. If you want to run just before that one you'll want to do

    Umbraco.Web.Routing.ContentFinderResolver.Current.InsertTypeBefore<ContentFinderByNotFoundHandler, YourFinder>();

    Stephan

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    May 30, 2013 @ 07:32
    Shannon Deminick
    0

    @Warren, what you had initially can work, you just need to return the correct result and obviously should be a 'View' not a partial view. The normal RenderMvcController always returns an object of type RenderModel, so your result could just return the view you want and an instance of RenderModel. You should also inherit from the controller: UmbracoController which exposes all the handy umbraco properties like UmbracoHelper, UmbracoContext, etc...

    //create a RenderModel based on some content ID
    var model = new RenderModel(Umbraco.TypedContent(123));
    //return a view called "MemberProfile" 
    return View("MemberProfile", model);

     

  • Randy McCluer 59 posts 87 karma points
    Jul 03, 2013 @ 18:36
    Randy McCluer
    0

    Shannon, I'm trying to do almost the exact same thing as Warren's original attempt. Running into an issue with the PCR being null though, since custom routing doesn't fire the Content Finders. This is on a build pretty close to 6.1.2.

    Line 44: {
    Line 45: //change the model to a RenderModel and auto set the culture
    Line 46: viewData.Model = new RenderModel((IPublishedContent)viewData.Model, UmbracoContext.PublishedContentRequest.Culture);
    Line 47: }

    If I do it the other way, via a Content Finder, I don't seem to be able to pass any ViewData to my view to add the user's profile info. 

    I will try Warren's url rewriting trick, but I'd really like to be able to move the profile fetching logic out of the view.

  • Stephen 767 posts 2273 karma points c-trib
    Jul 03, 2013 @ 18:50
    Stephen
    0

    I might be a bit late in the discussion but... Shannon, if I understand correctly a custom MVC route seems to entirely bypass the UmbracoModule request handling? Is that even possible? That would explain why the PCR is null. But I'm not sure I understand what's going on really?

    And on the same kind of topic, various ppl have expressed the need to store some "context data" when running the finders. Currently telling them to use the httpContext.Items collection... but could we imagine that the PCR provides such a possibility directly eg PublishedContentRequest.Items? Would it make sense and be easier to use, or would it be redundant?

  • Randy McCluer 59 posts 87 karma points
    Jul 03, 2013 @ 21:25
    Randy McCluer
    0

    Thanks, Stephen. I rigged it up using Request.Items for now, but that feels really dirty. Would be great if a wrapper were available for this.

  • Funka! 398 posts 661 karma points
    Jul 03, 2013 @ 22:28
    Funka!
    0

    Hi Stephane,

    The fact that custom MVC routing bypasses all Umbraco-related functionality has been a thorn in my side for the last four weeks and in the end was never able to do what I wanted. I would love to be able to manually construct a PCR and set the current IPublishedContent model to whatever node I want by its ID. But, the PCR ctor is marked internal, and only works if Umbraco handles the request. But custom routing means umbraco doesn't handle the request so back to square one.

    What I'd really love to be able to do is this. Thank you!

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 04, 2013 @ 01:22
    Shannon Deminick
    0

    Change this to specify a Culture explicitly:

    //create a RenderModel based on some content ID
    var model =newRenderModel(Umbraco.TypedContent(123), CultureInfo.CurrentUICulture);
    //return a view called "MemberProfile"
    returnView("MemberProfile", model);

    Then you won't get the exception. There should really be no reason why you need a reference to the PublishContentRequest for what you are trying to acheive, the culture setting above is the reason why the PCR is giving you problems because it is trying to get the culture for the request from the PCR but as you have noted, there is no PCR because it is a custom route which of course bypasses Umbraco... its just a regular old MVC route, no difference.

    What you might find with the above is you'll get a YSOD saying that it cannot find the MemberProfile view. This is because the Umbraco ViewEngine will only look in the root of ~/Views if the route has an 'umbraco' DataToken applied to it with a 'RenderModel' object as it's value (to indicate that it is an umbraco route). So you can do two things: put your view in a controller specific folder, or add a custom datatoken to the current route in your controllers action with a key of "umbraco" and the vaue set to your custom RenderModel.

    I'm going to write up a blog post on this now.

  • Funka! 398 posts 661 karma points
    Jul 04, 2013 @ 01:40
    Funka!
    0

    Hi Shannon,

    Thank you for the reply.

    I actually had tried exactly that. (Passing CurrentUICulture just as you indicated.) However, UmbracoHelper's Field method crashes with NullReferenceException, probably because it is expecting there to be a PCR. Not sure if there are other helper methods besides Field that also expect this since I wasn't able to make it past this point, but see also for example that even RenderMacro will complain too, "Cannot render a macro when there is no current PublishedContentRequest".

     

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 04, 2013 @ 01:52
    Shannon Deminick
    1

    Your first first issue is already fixed:

    http://issues.umbraco.org/issue/U4-2324

    The macro issue is due to how macros work in the core which are not very elegant and still rely on many old legacy objects in order to execute which are part of the Umbraco pipeline. What kind of macro are you trying to execute? Does it need to be a macro for a particular reason instead of just a Partial View or Child Action ?

    Just trying to understand why in these cases that custom routing is necessary and why you want to bypass Umbraco altogether to then render out Umbraco again. Is it just because you want a page to execute under a different URL?

     

     

  • Funka! 398 posts 661 karma points
    Jul 04, 2013 @ 01:57
    Funka!
    0

    Oh, looks like I missed this. Unfortunately I spent so long with 6.1.1 totally frustrated (both myself and the client) that I ended up giving up. Never had a chance to try 6.1.2 so I guess the timing there didn't work in my favor this time. Thank you though for finally bringing closure to this ordeal I've been struggling with for over 4 weeks now. Will try this out sometime when I get a chance. Thanks again!

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 04, 2013 @ 02:00
    Shannon Deminick
    0

    Any chance you can give me an indication though as to why you require these custom routes ? Also, you don't particularly need to use @Umbraco.Field to render content, you have acceess to the content through @Model.Content

    Anyways, would just like to get an idea as to whether you are just using a custom route only in order to specify a custom URL for your content or if it is something more complex than that.

    Cheers,

    Shannon

  • Funka! 398 posts 661 karma points
    Jul 04, 2013 @ 02:00
    Funka!
    0

    P.S., I didn't need a macro per se, ... was just looking over the codebase to see what other potential problems one might encounter when you don't have a PCR handed to you. It seemed like so much of what i saw kept assuming there would be one present, which I wasn't able to get in my scenario. Thanks!

  • Funka! 398 posts 661 karma points
    Jul 04, 2013 @ 02:15
    Funka!
    0

    We have a fairly elaborate need for custom routing for an events calendar app. We allow a variety of URLs to fetch various types of pages. For example:

    • /events/ - the main index page showing any custom notes, events starting today, tomorrow, and throughout the rest of the week.
    • /events/2013/07/ - shows all events running or starting within a given month
    • /events/2013/07/03/ - shows all events running or starting on a given day
    • /events/20130701-20130708/ - shows events running anytime within the selected span of dates
    • /events/456789/ - shows the particular event with ID#456789
    • /events/456789/00000000111122223333444444444444/ - private URL for admin to preview an unpublished, user-submitted event (clicking a link from a notification email with one-click approval or denial)
    • /events/music/ - shows events tagged as "music"
    • /events/music+food/ - combination of tags

    I think there may be more possibilities but those are the main ones I can remember. The situation we have is that this is an existing (MVC2) application we did not want to have to rewrite entirely. The ultimate goal would have been to use our own controllers, our own routing, and our own models, ... but be able to share the umbraco views and "common parts" such as navigation and sidebars. I have heard that maybe this could be done with a "ContentFinder", but the simplicty and elegance of MVC routing would be hard thing to say goodbye to plus the rewriting of our controllers to start handing back RenderModels etc etc... it seemed like would need a ton of rewriting just for umbraco integration and everything i kept trying to avoid this always fell short at about 97% it seemed.

    Thanks again!

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 04, 2013 @ 02:18
    Shannon Deminick
    1

    Awesome! Thats the info I needed :)

    I agree, having the elegance of MVC routing is not something we want to get rid of. There's probably many uses for IContentFinder but with your above example I can see that this would be handled much nicer with normal MVC routes.

    I have some ideas I'm working on now !! 

  • Funka! 398 posts 661 karma points
    Jul 04, 2013 @ 02:37
    Funka!
    0

    High five Shannon; thank you for taking my request seriously! I look forward to whatever improvements you think are possible. I keep an eye on the umbraco-dev google group so if any requests for comments are made over there I would be happy to chime in further. Thank you!

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 04, 2013 @ 04:01
    Shannon Deminick
    1

    Just blogged about this here:

    http://shazwazza.com/post/Custom-MVC-routing-in-Umbraco

    Hope that helps a bit for the time being :)

  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    Jul 04, 2013 @ 11:01
    Warren Buckley
    0

    Hello all,
    Just seen this post had a ton of replies.

    Will read through them. @Shannon thanks for the advice will re-work what I have to use a controller route rather than IIS URL Rewriting and give that a go.

    Cheers,
    Warren :) 

  • Randy McCluer 59 posts 87 karma points
    Jul 04, 2013 @ 16:55
    Randy McCluer
    0

    Shannon, I'm doing the exact same thing as Warren (a member profile page), but will also have situations like Funka's in the near future. I think those cover most of the scenarios I can think of. It's a holiday here in the States, but I'll try the technique in your blog post on Friday.

    Warren & Funka, thanks for posting these so that I'm not the only guy asking for crazy stuff ;)

     

  • Mark 49 posts 130 karma points
    Jul 10, 2013 @ 13:44
    Mark
    0

    Hey Shannon, just to reply to this

    Just trying to understand why in these cases that custom routing is necessary and why you want to bypass Umbraco altogether to then render out Umbraco again. Is it just because you want a page to execute under a different URL? 
     
    So at the moment I have some nodes that have have a macro selector property, that I can reorder and select certain optional functionality, however I run into that same "Cannot render a macro when UmbracoContext.PageId is null." issue - due to using the RenderModel method.
     
    Typically not too fussed using macros to be honest as generally they are fairly usless based on the way I develop, however in this case because of the macro selector datatype they had their place - I guess I'm saying it could potentially be handy to insert not just macros but also insert partials.. actually thinking about it would be pretty easy to write a datatype to do that... hm.
     

  • Funka! 398 posts 661 karma points
    Jul 12, 2013 @ 02:16
    Funka!
    0

    Well I had some more time to work on this, and started all over again with 6.1.2 this time. U4-2324 seems to have fixed the first problem, as Shannon noted last week. Thank you! But now one more challenge is how to render legacy macros. (This still seems to stem from the problem of not being able to get a PCR when you do custom routing, a.k.a. the Cannot render a macro when there is no current PublishedContentRequest error, not to mention the problem trying to set the UmbracoContext.Current.PageId manually...)

    Shannon, you also asked me last week, "What kind of macro are you trying to execute? Does it need to be a macro for a particular reason instead of just a Partial View or Child Action?" The answer here is simply the time and cost it would take to convert dozens of legacy macroScripts that are all using umbraco.MacroEngines.DynamicNodeContext as the Model and which call upon its many lovely methods into the newer IPublishedContent model... But this is certainly worth a try if I have more time to spare on this.

    Thanks again!

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 12, 2013 @ 02:27
    Shannon Deminick
    0

    Yup, as i mentioned Macros will be the thing that doesn't work currently, however I've been playing around with some ideas. One of the ideas I had which I think might be the easiest/nicest one is to create a filter attribute so you could do something like this:

    [EnsurePublishedContentRequest]
    public ActionResult Product(string id)
    {
        // ... do stuff here
        return View("Product", CreateRenderModel(result.First()));
    }

    Basically that filter will look into the current DataTokens in the route values (by default would look for the "umbraco" key) and check if there's a RenderModel there. If there is then internally we'd construct and set the correct PublishedContentRequest for the current request. Then macros would work. I got distracted last week from implementing this so it might not be as straight forward as this but hopefully it should be. This filter would take care of all of the legcy complexity which needs to set all sorts of things in the current http context.

    We can then also extend this filter so you could specify other options like:

    [EnsurePublishedContentRequest("mycustomkey")]

    so you could specify your own DataTokens key that will contain the RenderModel. We can then also ensure that during this operation that the PublishedContentRequest.Prepared event executes so you could modify it if you wanted. Of course the modification of it might really only affect how macros are rendered since the route has already been executed.

    Does all that make sense?

  • Funka! 398 posts 661 karma points
    Jul 12, 2013 @ 22:39
    Funka!
    0

    I'm not sure I fully grasp yet all of the complexities that need to happen to make this work so not sure if it makes sense to me or not.

    Question: when you say that the [EnsurePublishedContentRequest] attribute might check if there's a RenderModel already present, in my scenario, wouldn't the attribute run before my action method has had a chance to create a RenderModel for this in the first place?

    Recall that I don't really care about which IPublishedContent node I have, ... I just need one of them (the homepage, for example) so that my layout can generate the common things like navigation, sidebar, footer, etc.

    So in my case---or in any custom routing scenario?---I have neither a RenderModel nor a PublishedContentRequest by the time my action method starts running. I understand that the PCR isn't going to magically know how to set itself up, so I don't mind telling it: "You should pretend you are a request for the homepage using whatever current culture." I would be happy to tell it this by either my homepage's NodeID# or the "/" root path. I'm also happy to manually setup a RenderModel to return to my view, which is a bit convoluted at the moment, but at least it's working as shown below:

    //public class EventsController : Controller
    //public class EventsController : RenderMvcController
    public class EventsController : UmbracoController // ???
    {
        public ActionResult Index()
        {
            const int NODE_ID = 1061;

            UmbracoContext UC = UmbracoContext.Current;
            //UC.PageId = NODE_ID;  // Not Allowed!    // UC.PageId is required so we can render a legacy macro??
            UC.HttpContext.Items["pageID"] = NODE_ID;  // this is a workaround for the above attempt!

            PublishedContentRequest PCR = UmbracoContext.Current.PublishedContentRequest;    // is null :-(
            UmbracoHelper UH = new UmbracoHelper(UmbracoContext.Current);

            // Get a "RenderModel" or custom derivation to hand to view? (instead of whatever other model we might have preferred)
            IPublishedContent C = UH.TypedContent(NODE_ID);
            //RenderModel RM = new RenderModel(C); // the ctor throws NullReferenceException, probably because UmbracoContext.Current.PublishedContentRequest is null
            CultureInfo CI = CultureInfo.CurrentUICulture;
            RenderModel RM = new RenderModel(C, CI);

    // Do lots of other stuff here, shove my custom model into ViewBag, etc.
    // ...

            return View("~/Views/EventsIndex.cshtml", RM);

        }
    }

    So as you can see, lots of steps I'm doing here already besides the final problem with the missing PCR.

    I wonder if a different type of attribute might make this scenario much easier,

    [SimulatePublishedContentRequest(1061)]  // NodeID 1061 is my homepage, or by path, such as "/"?
    public ActionResult Product(string id)
    {
        // ... do stuff here
        return View("Product", CreateRenderModel(result.First()));
    }

    It could be called Simulate or Mock or Create or whatever. The idea would be that within my action method now, UmbracoContext.Current.PublishedContentRequest wouldn't be null and I could get a pre-built RenderModel somehow (or more easily create one, as you had shown with the CreateRenderModel method).

    P.S., where does your "result.First()" come from and what would this be?  an IPublishedContent node I would imagine, such as from using UH.TypedContent() to fetch?

    Thank you! I hope this makes sense what I'm asking and is not totally crazy or dumb.

  • Randy McCluer 59 posts 87 karma points
    Jul 12, 2013 @ 23:04
    Randy McCluer
    1

    Shannon, I'd like to second Funka's request here. I too have special stuff that I'd like to render in a regular template, but don't care to (or can't) put in the content tree. However, my nav menu, footer, and sidebars all require a PCR on the context. So currently, I'm creating a ContentFinder, then setting the PCR content to the homepage and the template to what I want to render. It works, but it doesn't seem all that straight-forward.

  • Funka! 398 posts 661 karma points
    Jul 12, 2013 @ 23:25
    Funka!
    0

    Hey I've answered one of my own questions already in my "P.S." above from your excellent blog post, where I now see your code sample came from. I'm working through the rest of your article more carefully which may hopefully answer more of my questions so sorry if anything else above was already answered. (The code I posted above I had written last week before your article was written, but is good to see that I'm not too far off track with what I've been doing...)  Thanks!

  • Funka! 398 posts 661 karma points
    Jul 12, 2013 @ 23:40
    Funka!
    0

    Randy! Yay, a great idea for a way to get a PCR! And this works? Sadly I'm heading out for the weekend but am excited to try this when I get back next week. Thanks!

  • Stephen 767 posts 2273 karma points c-trib
    Jul 13, 2013 @ 09:21
    Stephen
    0

    Shan, Funka... so I'll join the discussion: I think the attribute looks quite nice but reading from Funka's posts I'm not sure how it is going to work. Funka says he doesn't care which node the PCR points to... but we need a node, and it can't be just any node, because in case of a multi-website setup it needs to belong to the correct tree branch. Also the PCR knows about various things (template, culture, domain) that I'm not sure how we would re-create?

    Also as Funka mentions, the RenderModel is created within the Action method so it wouldn't be available at the time the attribute is used?

    @Funka: still think a content finder would be more appropriate but I understand that you want to share views with an MVC app... plus I don't want to discuss it now, I'd rather find a way to get your stuff working. More interesting ;-)

    @Shannon: is there a simple explanation for, why doesn't the UmbracoModule set a PCR in the first place for custom MVC routes? Technically speaking, I fail to see where / what causes the module to skip the PCR creation? Or is it a matter of running different umbraco contexts?

    @Randy: you're doing what I would have done ie create a content finder that maps the requests to "some node" so that the PCR is sorted out and exists... and using the root node makes perfect sense. That way, it should work in a multi-websites setup, it would handle the culture properly, it could handle the 404... all sorts of things the PCR is supposed to do.

    Interesting challenge anyway...

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 15, 2013 @ 04:45
    Shannon Deminick
    0

    Ok guys, here's a further explanation:

    MVC filters run before and after the action, so as long as you indicate what IPublishedContent you'd like to use to create a PCR in some way, then it will work just fine. We can acheive this in numerous ways:

    • Explicit node id: [EnsurePublishedContentRequest(1234)]
    • Putting either an IPublishedContent or RenderModel into the DataTokens in the action method, we can have a default key or you could override it like: [EnsurePublishedContentRequest("mykey")]
    @Stephen, we don't create a PCR in the module initially because an empty PCR or a null PCR are effectively the same thing, it doesn't get us any closer to solving the issue.
    To re-iterate, the only issue here is how to get macro's rendering from a custom route, and remember a custom route is totally outside of the umbraco pipeline. So why do we need this PCR thing to render macros? Its because a macro needs to be running in the context of an Umbraco request. A macro needs to have a few legacy things wired up properly and depending on what the macro is doing and how it is being rendered (i.e. via RTE or directly) it might need to know what the current Umbraco page is that is rendering, what the current culture is for the node that is rendering, etc... Basically in some cases a macro might need to know all of the information contained in a PCR.
    So to solve this issue, we need to have a PCR, this attribute will 'fix' the issue but you would still need to populate the PCR with a specific node (in one way or another). We can populate it with a default culture an empty template, etc... but depending on what your macros are doing you might need to handle the PublishedContentRequest.Prepared event to set whatever values in the PCR that your macro might need. Of course we can have overloads to this attribute that will allow you to specify all of these parameters statically, or allow you to set all of these parameters dynamically by allowing you to store what the values should be in DataTokens with specific keys or by handling the Prepared event.
  • Funka! 398 posts 661 karma points
    Jul 16, 2013 @ 02:45
    Funka!
    0

    @Stephane: to clarify when I say I don't care which node I get... I actually do care in a sense that I need to use the node to traverse up and down it generating nav links and generating the sidebar. In my events calendar app I've been talking about, i would probably even create an "events" node in the tree which could be used for storing settings such as notification email addresses or basic content blurbs, but this "events" node wouldn't actually need a template nor ever be rendered directly.

    And also when you say it "can't just be any node" because of conflicts with multi-site scenarios and how the PCR needs to know template, culture and domain:  these are pieces of information I am happy to supply to the PCR so that it doesn't have to figure it out for itself, or else I don't undertand why at least some of these couldn't be inferred? If I already know the NodeID i want to simulate, there wouldn't be any tree branch confusion. And also, that NodeID would already have a template picked on it, correct? (Even though I don't need this in my scenario.) The culture I can infer from the current request's domain or even manually supply to the PCR. So to add my two cents onto your final thought on how we wouldn't/couldn't be sure how the PCR might get this information: I understand and agree that with custom routing there are certain missing pieces that it needs to know, but then shouldn't it be possible for me to supply this to it manually? ("Dear Umbraco Engine: Please render this view I am specifying, using a Model based on NodeID #1234, using en-US as my culture and example.com as my domain.") I guess this is exactly what you are saying the ContentFinder is supposed to be used for... But that's only if I let the Umbraco pipeline handle the request in the first place?

    @Shannon, apologies for my memory flaking on the fact that attributes can run before and/or after the action itself. And your followup explanation on what such an attribute might accomplish does make more sense to me now, and certainly seems like it would get things working, especially with some sensical defaults and the ability to overload/override whatever we need to in certain settings. This is probably the final piece in the puzzle that anyone using custom routing would need to render legacy macros and I look forward to seeing if this might become a reality!

    Thank you!

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 16, 2013 @ 03:27
    Shannon Deminick
    1

    Cool, I'll try to get it done this friday :)

  • Funka! 398 posts 661 karma points
    Jul 17, 2013 @ 19:11
    Funka!
    0

    Shannon, thank you again for this! Just wanted to mention and remind, that for macros to render they also will be expecting the current UmbracoContext.PageId property to be set, which is a read-only value that seems to always come directly from HttpContext.Items["pageID"]. So might then the EnsurePublishedContentRequest attribute you're planning take care of setting this for us too? (Is easy enough to work around by setting the HttpContext Item yourself, but for the sake of neatness this might be nice to handle for us---especially if U4-61 should ever come around and change how this works on us later...)

    Thank you!

  • Stephen 767 posts 2273 karma points c-trib
    Jul 18, 2013 @ 09:37
    Stephen
    0

    @Shan: I might be dense or maybe it's because I'm on pseudo-vacations again with kids everywhere but... the PCR is created in UmbracoModule.ProcessRequest... and I fail to see what in there would prevent the PCR from being created... unless I'm just ignoring something about the MVC pipeline and ProcessRequest does not fire? That's the only question I have, prob. because there's something I don't know?

    Other than that I understand now (obviously still have a lot to learn) that the attribute can trigger after the action completes so yes that seems to be a solution. @Funka: yes the attributes should then setup everything that's needed for macros, etc. You should not have to set anything in HttpContext.Items as we can't guarantee that this is where pageID is going to be stored.

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 18, 2013 @ 09:41
    Shannon Deminick
    0

    @Stephen, as mentioned, we can create a PCR but it will remain "empty" until the Umbraco routing processes, with a custom route the Umbraco routing never processes and therefore the PCR would still remain empty. An empty PCR is essentially the same as having a null reference there, it gets us no further to solving this issue.

    @Funka, yeah that's all that legacy stuff I've been referring to. What actually happens in the routing process is that we wire up a new legacy 'page' object based on the PCR which then automatically wires up all the legacy bits and peices including all the httpcontext.items stuff.

  • Stephen 767 posts 2273 karma points c-trib
    Jul 18, 2013 @ 09:43
    Stephen
    0

    @Shan: it's just that I don't see why the "Umbraco routing never processes"? Must be obvious but I'm missing it.

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 18, 2013 @ 09:45
    Shannon Deminick
    0

    Lol, yeah you're definitely missing something. We're talking about explicit custom MVC routes so of course the routing pipeline doesn't process otherwise Umbraco would process every request even if you wanted to have your own MVC pages outside of Umbraco. 

  • Stephen 767 posts 2273 karma points c-trib
    Jul 18, 2013 @ 10:06
    Stephen
    0

    OK and... where do we "not process" the request? Does the UmbracoModule.ProcessRequest trigger at all? I can see the things we've put in place in order not to process back-office urls, static files (css, js...) but I fail to see where we exclude custom MVC routes. Now... will look further into it tonight, right now it's becoming rather impossible at home.

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jul 18, 2013 @ 10:08
    Shannon Deminick
    1

    Look at GlobalSettings.IsReservedPathOrUrl

    If a custom route is specified that matches the request, we don't process it. Basically it auto-handles custom routes instead of having to specify explicit stuff in the app setting "umbracoReservedUrls"

     

  • Funka! 398 posts 661 karma points
    Aug 07, 2013 @ 06:18
    Funka!
    0

    Update on my own saga:

    I ended up converting dozens of legacy macro scripts into partial views, and updating all of our templates. Site is up and running now with my custom routing working and a controller that derives from my own base controller instead of UmbracoController et al. (I did have to generate a RenderModel for returning to my views, which I'll show an easy way to do below, and am shoving my legacy/custom models into ViewBag to keep the umbraco view pages happy.)

    While converting my legacy macro scripts into partial views, I found it easiest to add the line

    dynamic MODEL = (dynamic)Model.AsDynamic();
    

    to the top of each partial view, and then just use MODEL instead of where I used to use Model. Search and replace was my friend for changing things like Library.NodeById(xxx) into Umbraco.Content(xxx) etc.

    Oh, my partial views all inherit from Umbraco.Web.Mvc.UmbracoViewPage<IPublishedContent> and I call them with Html.Partial by passing in Model.Content (where Model is of type RenderModel of course). This is pretty standard I think from what I've seen others doing?

    P.S., here's the convenient magic parts that I put into MyOwnBaseController, which is now working in a custom routing scenario. (In case anyone cares.) I'll leave my comments in here, which show some of my manic thought process in arriving at this final solution.

    #region UMBRACO INTEGRATION HELPERS
    
    /// <summary>
    /// We can return the ViewResult from this custom, umbraco-specific method instead of the
    /// normal/built-in "View()" method, which will take care of:
    /// (1) producing a RenderModel for the (official/proper) model; and
    /// (2) shoving our own (custom) model into the ViewBag if supplied.
    /// </summary>
    protected virtual ViewResult UmbracoView(int nodeId, string viewName, object model = null)
    {
        var RM = loadAndCreateRenderModel(nodeId);
        if (model != null)
        {
            ViewBag.CustomModel = model;
        }
        return View(viewName, RM);
    }
    
    /// <summary>
    /// Adding onto Shazwazza's idea... 
    /// </summary>
    protected RenderModel loadAndCreateRenderModel(int nodeId)
    {
    
        // NOTE, if we have our controller inherit from UmbracoController, we wouldn't need to create local instances like this...
        // And even if not... consider moving these to base controller or somewhere else more common, such as OnActionExecuting etc?
        UmbracoContext UC = UmbracoContext.Current;
        UmbracoHelper UH = new UmbracoHelper(UmbracoContext.Current);
    
        // Are we going to need to render any legacy macros? How about being able to use something like Umbraco.Field()?
        //UC.PageId = nodeId;  // NOT ALLOWED!    // UC.PageId is required so we can render a macro??
        //UC.HttpContext.Items["pageID"] = nodeId;  // this is a workaround for the above attempt! NOTE! JULY 17, 2013: DO NOT NEED IF WE DON'T CALL LEGACY MACROS? REMOVE FOR NOW UNTIL PROVEN OTHERWISE???
    
        IPublishedContent C = UH.TypedContent(nodeId);
        return createRenderModel(C);
    }
    
    /// <summary>
    /// Inspired per http://shazwazza.com/post/Custom-MVC-routing-in-Umbraco
    /// Note, while this serves as a fine place to start, use "loadAndCreateRenderModel" instead,
    /// or better yet, just call UmbracoView() and not worry about these details every time!?
    /// </summary>
    private RenderModel createRenderModel(IPublishedContent content)
    {
        var model = new RenderModel(content, CultureInfo.CurrentUICulture);
        //add an umbraco data token so the umbraco view engine executes
        RouteData.DataTokens["umbraco"] = model;
        return model;
    }
    
    #endregion
    

    Then, from your individual controllers, you can say return UmbracoView(SOME_NODE_ID, "MyViewPage", MyCustomModel); instead of where you normally return View(...). I keep this crazy umbraco stuff out of sight, out of mind this way, and individual controllers can be kept much cleaner, often only needing to change the return line as noted.

    OK I'm done! I hope this helps someone else at some point down the line. Thanks for putting up with my rambling!

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Nov 29, 2013 @ 07:51
    Shannon Deminick
    2

    Yay!

    I've just pushed this new attribute, the revision is: e981776e6da574899e20ca4b77d1a0308393fbb2

    So here's how it works and what you can do with it. You attribute any of your custom actions with:

    [EnsurePublishedContentRequest(1234)]
    

    This will ensure a PCR is set against the UmbracoContext with the IPublishedContent with Id 1234 (if it is not found you'll get an exception. This will also set the Culture of the PCR to the default one - the first one found in the Language.GetAllAsList() call, or if one is not found will use CultureInfo.CurrentUICulture. However there's an option al param you can use to set the culture yourself:

    [EnsurePublishedContentRequest(1234, "en-AU")]
    

    There's another overload as well that allows you to specify a DataToken key name for which you'll need to add a valid instance of IPublishedContent to. So lets say in your action your lookup an IPublishedContent, you can add this to the DataTokens of the current route with a key of (for example) "MyContent", then use:

    [EnsurePublishedContentRequest("MyContent, "en-AU")]
    

    and again the culture is optional. If an IPublishedContent instance does not exist in the DataTokens by that key, an exception will be thrown when the filter executes. The other cool news is that you can create your own attribute by inheriting from this one and then configure the PCR however you'd like. So if you inherit from EnsurePublishedContentRequestAttribute, you can override ConfigurePublishedContentRequest and assign an IPublishedContent item, culture or whatever you like however you like.

    Sorry it's taken so long to get this done, but at least I did it :) It would be marvellous if someone could test this out!

    Cheers, Shan

  • Randy McCluer 59 posts 87 karma points
    Nov 29, 2013 @ 16:31
    Randy McCluer
    0

    This is really cool, Shannon. I'm in mid-launch right now, but will try to test in the next couple of weeks. Once we're up & running, I'll write something up to share my widget framework & large site tuning w/ everyone. 

     I'll be walking away from the umbraco world in February, but I've really enjoyed the community for these couple of years trying to get my behemoth launched. I especially appreciate all the help you've been through all of the u5 insanity and working w/ u6. 
  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    Dec 02, 2013 @ 14:51
    Warren Buckley
    0

    Hello all,
    I wanted ro re-visit this thread as there seems to have been discussions on alternatives & perhaps better practises on how to do this now.

    So if I was rebuilding my custom route/controller for handling public member profile URLs - site.co.uk/members/john etc
    Is the best way to do this now with a iLastChanceFinder or another way?

    Cheers,
    Warren :) 

  • Damiaan 442 posts 1302 karma points MVP 6x c-trib
    Dec 02, 2013 @ 14:56
    Damiaan
    0

    Hi Warren,

    I would rather say: a MemberProfileContentFinder, which you insert BEFORE the default content finders.

    That way you can take care that the member profile URL is not hijacked by "normal" umbraco nodes.

    Kind regards
    Damiaan

  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    Dec 02, 2013 @ 15:35
    Warren Buckley
    0

    Hey Damian,
    Do you have an example or suggestion on how best to approach this please?

    Thanks,
    Warren :) 

  • Anthony Dang 1404 posts 2558 karma points MVP 3x c-trib
    Dec 02, 2013 @ 19:19
    Anthony Dang
    1

     

    Heya

    This is all you need:

     

        public class MyApplicaitonHandler : ApplicationEventHandler
        {
            protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
            {
                ContentFinderResolver.Current.InsertTypeBefore<ContentFinderByNiceUrl, MyContentFinder>();
    
                ContentFinderResolver.Current.RemoveType<ContentFinderByNiceUrl>();
            }
        }
    
    
        public class MyContentFinder : ContentFinderByNiceUrl
        {
            public override bool TryFindContent(PublishedContentRequest request)
            {
                if (base.TryFindContent(request))
                {
                    return true;
                }
    
                var id = 1244; // hardcoded - do something to get a node id
                if (id != -1) 
                {
                    // have a node id so lets route to it
                    request.PublishedContent = UmbracoContext.Current.ContentCache.GetById(id);
                    return true;
                }
    
                return false;
            }
        }

     

     

     

     

     

     

     

  • Warren Buckley 2106 posts 4836 karma points MVP 7x admin c-trib
    Dec 03, 2013 @ 21:33
    Warren Buckley
    3

    For anyone interested in I have created a blog post on how I implemented this using the iContentFinder method as suggested.

    http://creativewebspecialist.co.uk/2013/12/03/using-umbraco-pipeline-for-member-profile-urls/

    Cheers,
    Warren :) 

  • andrew shearer 512 posts 661 karma points
    May 25, 2014 @ 22:53
    andrew shearer
    0

    hi - is someone able to update me on this thread? If I have a custom route for showing search results, for example, and that route has a corresponding CMS content page for managing certain aspects (i.e. title, intro etc), then is the best way to set this umbraco context from my controller:

    protected void RenderUmbracoContent(int pageId)
    {
    RenderUmbracoContent(Umbraco.TypedContent(pageId));
    }
     
    protected void RenderUmbracoContent(IPublishedContent content)
    {
    var model = new RenderModel(content, CultureInfo.CurrentUICulture);
     
    //add an umbraco data token so the umbraco view engine executes
    RouteData.DataTokens["umbraco"] = model;
    HttpContext.Items["pageID"] = Convert.ToString(content.Id);
    }

     

    where PageId is my search page from the Cms.

     thx

  • andrew shearer 512 posts 661 karma points
    May 25, 2014 @ 22:58
    andrew shearer
    0

    Basically, what im asking is there a helper method that will set those tokens and httpcontext items for me?

     

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    May 26, 2014 @ 03:00
    Shannon Deminick
    0

    Yes - if you are not using IContentFinder and doing custom MVC routes then you just need

    [EnsurePublishedContentRequest]

    attributed on your MVC action, there more info on the previous page to this thread at the bottom: http://our.umbraco.org/forum/developers/extending-umbraco/41367-Umbraco-6-MVC-Custom-MVC-Route?p=4

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    May 26, 2014 @ 03:01
  • andrew shearer 512 posts 661 karma points
    May 26, 2014 @ 03:04
    andrew shearer
    0

    thanks Shannon, just read your latest blog post. So this is 7.x functionality?

     

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    May 26, 2014 @ 03:11
    Shannon Deminick
    0

    That last blog post's functionality doesn't exist in the core yet - but yes it will be v7 (will probably port back to v6 too, should be quite straight forward). The EnsurePublishedContentRequest attribute exists in 6.2. You should be able to achieve what you want with that attribute.

  • Jeric 122 posts 192 karma points
    Oct 08, 2014 @ 19:12
    Jeric
    0

    Hi Shannon,

    I'm using a REST call with the UmbracoApiController and i'm having the same issue where one of the field is a RTE and it comes with macro in it.

    Any ways i can use this in an UmbracoApiController?

    It keeps showing "cannot render a macro when there is no current PublishedContentRequest" 

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Oct 08, 2014 @ 23:35
    Shannon Deminick
    0

    Hi, I'm not really sure i understand your question.

    A REST call of any sort, whether it's an UmbracoApiController or any other REST call is 'stateless', it has nothing to do with the Umbraco pipeline and therefore there is no PublishedContentRequest.

  • Jeric 122 posts 192 karma points
    Oct 09, 2014 @ 10:52
    Jeric
    0

    I'm using the UmbracoApiController to generate an xml feed.

    The error occur when I need to access the RTE property that comes with macro in it.

    Any work around for this or any way i can strip out the macro?

     

  • Martin Griffiths 826 posts 1269 karma points c-trib
    Oct 09, 2014 @ 11:46
    Martin Griffiths
    0

    Hi all

    I'm working on a new project in Umbraco 7 that's multi-subdomain / multisite build.

    I have structured the site so that the root "/" sits at the top of the tree and all subsites sit below with each one having a sub-domain assigned to it.

    Now, I'm also registering some custom routes for the entire application ie the roots are available to every sub-site. These customs roots provide some application functionality that I dont want to be part of Umbraco, but I do want to continue to access each subsites root (home) to be able to use some important properties i've added to it and of course render all of the usual site menus and structure that are contained within the subsite.

    I was using this code on the actions on my controller for my custom pages to get the subsite root nodeId.

    siteRootNodeId = Domain.GetDomain(Request.Url.Authority).RootNodeId

    But it would appear that this only works on site roots "/" and seems to be returning null on any other subsite node under a custom route.

    I've looked at this new suggested attribute decoration to pass in my site root as a starting point but I have no idea where to go from there! Of course the one thing I do have is the hostname for the subsite, which is what I thought I could use as my hook, but it would seem not.

    Does anyone have any ideas how I would go about getting the subsite node id in custom routes?

    Thanks

    Martin

     

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Oct 09, 2014 @ 14:08
    Shannon Deminick
    0

    @martin, this thread has gone on for a very long time. You seem to be asking a new question which will probably get a lot more direct feedback if you ask it separately on its own thread on the forum.

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Oct 09, 2014 @ 14:09
    Shannon Deminick
    0

    @martin, but yes, if you look at the latest codebase in articulate on github it has new examples of custom routing with multi-tenancy (but you should still post another thread and I can reply in detail there)

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Oct 09, 2014 @ 14:10
    Shannon Deminick
    0

    @Jeric, I would also urge you to start a new thread with your specific problem since it's basically a new question.

  • Jeric 122 posts 192 karma points
    Oct 09, 2014 @ 14:12
  • Martin Griffiths 826 posts 1269 karma points c-trib
    Oct 09, 2014 @ 14:14
    Martin Griffiths
    0

    Hi Shannon

    Many thanks for the prompt reply, created new post...

    http://our.umbraco.org/forum/developers/extending-umbraco/57239-Getting-a-multisites-rootnodeId-from-a-custom-route-(controlleraction)

    A simple code example would be most helpful....i've resorted for now to using a petapoco sql helper to query the domains table directly as it holds the rootNodeId for each domain, but if the schema changes....oops!

    M.

  • Daniel 9 posts 80 karma points
    Aug 03, 2016 @ 08:29
    Daniel
    0

    Hi,

    I revisit this thread, since I want to make some custom routing for Umbraco but for some certain routes, I don;t want the Umbraco context at all (I do not care about the IPublishedContent).

    I have described why here: https://our.umbraco.org/forum/extending-umbraco-and-using-the-api/79014-custom-routing-revisited

    For example, I have a view that generates some content based on a POCO (the generation it is a bit complex, but it is not a 1-1 relation) I want to be able to provide this POCO as a model to a view in the GET (this is the hard part) and be able to retrieve in POST (this can be done relatively easily)

    So, can I skip the umbraco RenderModel for that page ?

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Aug 03, 2016 @ 08:46
    Shannon Deminick
    0

    If you don't care about IPublishedContent, an UmbracoContext, or anything, then just go ahead and make a normal MVC route, there's nothing stopping you from doing that.

    I'll reply to your thread.

Please Sign in or register to post replies

Write your reply to:

Draft