Copied to clipboard

Flag this post as spam?

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


  • Seth Niemuth 275 posts 397 karma points
    Feb 11, 2013 @ 19:12
    Seth Niemuth
    0

    Using RenderMvcController

    Umbraco 4.11.4

    I have followed this reference: https://github.com/Shandem/Umbraco4Docs/blob/4.8.0/Documentation/Reference/Mvc/custom-controllers.md

    I have a controller which extends Umbraco.Web.Mvc.RenderMvcController.

    This controller returns a custom model for me:

    return CurrentTemplate(searchVm);

    Everything works great until I declare a layout for my template. So this doesn't work:

     

    @using System.Web.Mvc.Html    
    @inherits Umbraco.Web.Mvc.UmbracoViewPage< Aspc.Web.Models.SearchVm >
    @{
        Layout = "_Layout.cshtml";
    }
    @using (Html.BeginForm())
    {
        @Html.TextBoxFor(t => t.Terms);
        @Html.TextBoxFor(t => t.Keywords);
        <input type="submit" value="Search" />
    }

    However, this does:

    @using System.Web.Mvc.Html    
    @inherits Umbraco.Web.Mvc.UmbracoViewPage< Web.Models.SearchVm >
    @{
        Layout = null;
    }
    
    @using (Html.BeginForm())
    {
    
        @Html.TextBoxFor(t => t.Terms);
        @Html.TextBoxFor(t => t.Keywords);
    
        <input type="submit" value="Search" />
    }

    It gives me this error:

    The model item passed into the dictionary is of type 'Web.Models.SearchVm', but this dictionary requires a model item of type 'Umbraco.Web.Models.RenderModel'.

    Also seen on this thread post: http://our.umbraco.org/forum/developers/api-questions/36602-Pass-strongly-type-model-in-razor-view

    Declaring that same layout for other templates which don't have custom models (and don't have custom controllers) work just fine. The _Layout.cshtml calls Umbraco macros and makes use of the Umbraco Model.

    I am assuming I am getting this error because the _layout.cshtml is expecting RenderModel but it is only getting my custom model? Am I doing something wrong, is there a way around this?

  • Charles Afford 1163 posts 1709 karma points
    Feb 16, 2013 @ 18:09
    Charles Afford
    0

    Hi, are these partial views?  and if so make sure in your controller you are passing back a partial view.  Hope this helps.  Charlie

  • Jeroen Breuer 4861 posts 12129 karma points MVP 3x admin c-trib
    Feb 16, 2013 @ 18:39
    Jeroen Breuer
    100

    Yes your _layout.cshtml should also have that custom model. I solved this by having a base model and a model which inherits from the base model. Than give your _layout.cshtml the base model (with the properties the master needs) and your own view the model which inherits from the base model.

    Jeroen

  • Seth Niemuth 275 posts 397 karma points
    Feb 19, 2013 @ 22:57
    Seth Niemuth
    0

    Yeah, I had figured out that I need to use a custom model on my _layout.cshtml as well. Unfortunately, this means that I have to hijack every single doc type/template on the website. But what I have done is made all my custom models inherit from a single model (called it BaseVm which actually doesn't have anything on it) and had my _layout.cshtml use this BaseVm.

  • Seth Niemuth 275 posts 397 karma points
    Feb 20, 2013 @ 16:43
    Seth Niemuth
    0

    A team mate of mine found an even better way to do this. In your _layout.cshtml, you don't need to inherit from your own model just have it inherit from 'object' like so: 

    Umbraco.Web.Mvc.UmbracoViewPage< object >

    This means you don't need to hijack every route/controller.

  • Jerremy 9 posts 29 karma points
    Feb 21, 2013 @ 17:01
    Jerremy
    0

    I was about to ask a question about this very same subject.

    In our _layout.cshtml we do this:
    @Html.Partial("Partials/HeaderMenu", Model)

    Which works great for 'normal pages', but if I hijack one of my pages to return a custom model, the above line doesn't work anymore.

    I get 'why' it doesn't work, I just don't like it :)

    The "Umbraco.Web.Mvc.UmbracoViewPage<object>" still isn't a good solution, since the partial still expects a RenderModel.

    A shame really, we're currently disliking this part so much that we're rendering all 'custom models' as SurfaceControllers.

  • Seth Niemuth 275 posts 397 karma points
    Feb 21, 2013 @ 18:52
    Seth Niemuth
    0

    Yeah, I am also finding it frustrating. Can you change the inherits on your partial to 'Umbraco.Web.Mvc.UmbracoViewPage<object>' instead of the RenderModel?

  • Jerremy 9 posts 29 karma points
    Feb 22, 2013 @ 08:10
    Jerremy
    0

    I can't.

    Well I'm saying that, but that's mostly because I haven't found a decent way yet to get the current IPublishedNode. You have Node.GetCurrent() but that returns a Node. I could do "new DynamicNode(Node.GetCurrent())", that works but that still doesn't give me an IPublishedNode.

    Also, in all fairness, it's a really ugly solution. Sure it works, but damn...

  • Seth Niemuth 275 posts 397 karma points
    Feb 22, 2013 @ 12:19
    Seth Niemuth
    0

    It is not any uglier than everything using the RenderModel. You are just changing the model that you are passing into all of your views from RenderModel to Umbraco.Web.Mvc.UmbracoViewPage<object>.

    As for getting to the Dynamic Node, I am not totally sure as I don't ever really use the Dynamic Node because of the loss if intellisense and I have noticed a real performance hit with dynamic node when traversing the content tree.

  • Jeroen Breuer 4861 posts 12129 karma points MVP 3x admin c-trib
    Feb 26, 2013 @ 14:09
    Jeroen Breuer
    0

    What if your own model inherits from the Umbraco RenderModel? Your views which need a custom controller can get their own model and the views which inherit from Umbraco.Web.Mvc.UmbracoTemplatePage without controller probably also still work because they get the correct model.

    Jeroen

  • Edwin van Koppen 156 posts 270 karma points
    Feb 26, 2013 @ 16:39
    Edwin van Koppen
    0

    Got the same problem, RenderModel needs a constructor with no arguments.. so i can't inherit from it.

    This is a really strange that Umbraco doesn't saw this coming, all the examples are with viewmodels.

  • Randy McCluer 59 posts 87 karma points
    Mar 13, 2013 @ 23:04
    Randy McCluer
    0

    You can now easily override the RenderModel behavior, but now you run into issues with the culture not being set on the PublishedContentRequest. Please go up-vote this issue http://issues.umbraco.org/issue/U4-1333

  • Shannon Deminick 1494 posts 5053 karma points hq
    Mar 15, 2013 @ 17:14
    Shannon Deminick
    2

    Hi All, 

    The underlying issue here is that your View has model of type 'X' and your Layout has model of type 'Y'. This problem will occur for any MVC site where you want a strongly typed layout page, it is not Umbraco specific. In normal circumstances your Layout will be defined as:

    @model dynamic

    which means that your layout will work when any type of model is defined in your view. That said, I realize that in many circumstances you'd want a strongly typed layout page as well. There's a few ways to handle this but here's a typical example...

    First, you'll normally be sharing a master layout page for everything that does not actually use the model for anything, for that layout page just use @model dynamic. Then for specific layout pages that require a model, just nest this one to your master layout and then create a custom base model. For example, we'll create a base model:

    public class BasePageModel {
        public string PageTitle {get;set;}
    }

    So your layout pages might now look like:

    CustomMaster.cshtml

    @model dynamic
    @{
        Layout = null;
    }
    
    <html>
        <head>
            <title></title>
            <script src="/scripts/jquery-1.7.1.js"></script>
            <script src="/scripts/jquery.unobtrusive-ajax.js"></script>
            <script src="/scripts/jquery.validate.js"></script>                
            <script src="/scripts/jquery.validate.unobtrusive.js"></script>        
        </head>
    <body>
        @RenderBody()
    </body>
    </html>

    CustomLayout.cshtml

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<BasePageModel>
    @{
        Layout = "CustomMaster.cshtml";
    }
    
    <h1>@Model.PageTitle</h1>
    
    @RenderBody()

    Then you will want a custom model for your normal view. This can just inherit from your base model like:

    public class CustomPageModel : BasePageModel
    {
        public string BodyText { get; set; }
    }

    So then your view could look like this:

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<CustomPageModel>
    @using Umbraco.Core.Dynamics;
    @{
        Layout = "CustomLayout.cshtml";
    }
    
    <p>
        @Model.BodyText
    </p>

     

  • Randy McCluer 59 posts 87 karma points
    Mar 15, 2013 @ 17:27
    Randy McCluer
    0

    Shannon, thanks for chiming in. I think we'd gotten to that point, but getting here just exposed another issue . 

  • Shannon Deminick 1494 posts 5053 karma points hq
    Mar 15, 2013 @ 17:28
    Shannon Deminick
    0

    yeah, looking in to that now, though doesn't seem that related to this issue IMO.

  • Matt Jones 30 posts 141 karma points
    Apr 20, 2013 @ 16:27
    Matt Jones
    0

    Hi

    I'm running in to the same problem, has anyone found a good way around this that would let me use my custom model and access the current umbraco content (Model.Content etc..)

     

    Regards

  • Funka! 398 posts 660 karma points
    Jun 19, 2013 @ 20:34
    Funka!
    0

    Hi Matt,

    Did you ever figure this out? This would really be helpful to be able to do.

    Thanks!

  • Matt Jones 30 posts 141 karma points
    Jun 19, 2013 @ 22:04
    Matt Jones
    0

    Hi

    I think I just ended up using @Html.Action to render a strongly typed view within the master layout or a partial view.

    I can't remember what I was trying to do now, but I can do everything I need to with that combination.

    Cheers

    Matt

  • Shannon Deminick 1494 posts 5053 karma points hq
    Jun 20, 2013 @ 00:54
    Shannon Deminick
    0

    I'm not sure what the problem people are having is ?  If you have a layout page that has a specific model, then your view page that is using that layout page must have the same model or a sub class of that model. My notes above with the BasePageModel example should explain it, no ?

    If you want your own custom model AND have access to the current render model, then your custom model would need to expose the render model. You can do this by hijacking the route for the document type in which you want to render views for. If you take the above example with a BasePageModel, you could just add a property to it of type RenderModel which would expose the wrapped umbraco model as well.

     

  • Funka! 398 posts 660 karma points
    Jun 20, 2013 @ 03:56
    Funka!
    0

    Hi Shannon, thank you for the reply.

    It sounds reasonable enough to add a RenderModel property to my custom model if I need to access that. But to do this, you say that my master Layout view will no longer use UmbracoTemplatePage as its model, but a dynamic instead. So yes, this allows my custom controllers to pass whatever I want to my custom views, but the main Layout page now needs to change in a way that no longer works on "normal" umbraco pages which are still passing RenderModel as the model.

    I think what I need to do is either hope for a CustomModel<T> property on the RenderModel that I can shove my custom model into, or else shove it into the ViewBag. Then I won't need to mess with the master layout view. Or, I could bite the bullet and truly create a separate "CustomerMaster.cshtml" like you showed in your example, which will duplicate the exact same markup as my regular _Layout.cshtml used by the rest of the site, but using slightly different notation for getting at the common things like the navigation and footer.  (This duplication of master layouts is what I have been trying to avoid. Although perhaps I am misunderstanding things, which is quite likely.)

    Thank you for any continued advice and guidance!

  • Shannon Deminick 1494 posts 5053 karma points hq
    Jun 20, 2013 @ 06:37
    Shannon Deminick
    0

    Just remember this is not an Umbraco issue, this is just how ASP.Net MVC works, you will have the same design restrictions with a normal MVC website. I think as best practice, if possible you'd not use a model in your layouts and they'd normally just be layouts which can expose sections so you can use your view's model inside of those layout sections. Or if you have modules in your layout you could use ChildActions to lookup a different model.

    In 6.1 we have the ability to set a default/custom RenderMvcController, the docs are here:

    http://our.umbraco.org/documentation/Reference/Mvc/custom-controllers#Changethedefaultcontroller

    If you did that, it means you could potentially have a base class for your layouts and you'd ensure that all of your views would be at least a sub class of this base class. Then in your custom controller you'd ensure that the model returned is this base class, then if you wanted specific sub classes for differnet views you could hijack those routes.

  • Jerremy 9 posts 29 karma points
    Jun 20, 2013 @ 08:44
    Jerremy
    0

    Hi Shannon,

    I think that the 'issue' is that (by default) Umbraco exposes all content through Model.Content. That then quickly causes one to think that 'their' model needs to be based on RenderModel because 'how can we otherwise get to Model.Content'.

    It took me a bit of decompiling (ok, resharper did that ;) to find out that the Model.Content property is simply based off @Umbraco.AssignedContentItem.

    I'm not sure (haven't looked in a while) if the help files mention @Umbraco.AssignedContentItem, but that does allow someone to just use strongly-typed models without issues.

    So using:
    @inherits Umbraco.Web.Mvc.UmbracoViewPage<object> 

    Solves this issue with the knowledge that you can access content through @Umbraco.AssignedContentItem .

    Regards, J.

  • Shannon Deminick 1494 posts 5053 karma points hq
    Jun 20, 2013 @ 08:47
    Shannon Deminick
    0

    AssignedContentItem is not always the current page though so keep that in mind, it is contextual. It is the currently assigned IPublishedContent to the current UmbracoHelper.

    The 'purist' way to do this is as I mention, implement your own controllers and assign your own models from there. 

    IPublishedContent is really the underlying model for the page so you can easily add that as a property to your base model for all of your views and ensure you return an instance of that base model from your controllers.

  • Jerremy 9 posts 29 karma points
    Jun 20, 2013 @ 09:04
    Jerremy
    0

    Well if I decompile UmbracoHelper and look at the constructor, it says this:
    this._currentPage = this._umbracoContext.PublishedContentRequest.PublishedContent;

    If I also look at 'CurrentPage' property of RenderMvcController, it says this:
    return this.PublishedContentRequest.PublishedContent; 

    Either I'm wrong, or they both look at the same source.

    I'm just trying to understand in what scenario the AssignedContentItem wouldn't be the current page, so I can prepare for possible issues in the future :)

  • Shannon Deminick 1494 posts 5053 karma points hq
    Jun 20, 2013 @ 09:09
    Shannon Deminick
    0

    If you create your own UmbracoHelper and pass it an instance of IPublishedContent, it is contextual to the content you've passed it. I'm just saying if that the AssignedContentItem doesn't mean "Current Page" it means it's the assigned item to that specific UmbracoHelper. There's 2 overloaded ctors.

  • Shannon Deminick 1494 posts 5053 karma points hq
    Jun 20, 2013 @ 09:16
    Shannon Deminick
    0

    A good example of this is when you have a partial view that inherits from UmbracoTemplatePage and you pass the partial view a custom IPublishedContent model like

    @Partial("MyPartial", Model.Content.Children.First())

    The AssignedContentItem inside of that partial view will *not* be the current page being rendered it will be the first child of the current page being rendered.

  • Jeroen Breuer 4861 posts 12129 karma points MVP 3x admin c-trib
    Jun 20, 2013 @ 12:19
    Jeroen Breuer
    1

    On the first page I suggested to let your own model inhertit from the Umbraco RenderModel: http://our.umbraco.org/forum/templating/templates-and-document-types/38311-Using-RenderMvcController?p=0#comment140290. That is still an option and you can also inherit from it like this:

    public class HomeModel : Umbraco.Web.Models.RenderModel
    {
        public BaseModel(IPublishedContent content)
            : base(content)
        {
    
        }
    }

    You can create you own model like this in the Controller:

    public ActionResult Home()
    {
        var model = new HomeModel(CurrentPage);
    
        //Set extra properties.
    
        return CurrentTemplate(model);
    }

    And your view can look like this:

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<HomeModel>
    @{
        Layout = "Master.cshtml";
    }

    And even if you masterpage inherits like this it will still work.

    @inherits Umbraco.Web.Mvc.UmbracoTemplatePage

    This is just an example, but I think that what Shannon is suggestion here is better: http://our.umbraco.org/forum/templating/templates-and-document-types/38311-Using-RenderMvcController?p=1#comment152959. You could also combine it and have custom models on all pages which inherit from the RenderModel.

    Jeroen

  • Funka! 398 posts 660 karma points
    Jun 20, 2013 @ 22:50
    Funka!
    0

    I feel like i'm so close to getting what I want working, but not quite there yet. I hope what I describe makes sense and is possible to do:

    Imagine that I have just a regular controller with full custom routing (does not inherit from RenderMvcController). There is thus no doctype to hijack, and I have my path(s) in umbracoReservedPaths so that Umbraco lets it through. I really want to be able to use the same master layout as the rest of the site, so I realize it's easiest if I return a RenderModel. (Like I said yesterday, I'm fine now with my custom model going into the ViewBag.)

    But the thing I'm wondering about is how to construct my own RenderModel if one isn't handed to me already? I am fine with using the site homepage as the current page to base it on, since this is what the master layout would need for generating the navigation, sidebar, and footer, etc.

    Is this a bad idea to try this? Are there any other ways I can do this? Apologies if I am failing to grasp the concept...

    UmbracoHelper UH = new UmbracoHelper(UmbracoContext.Current);
    IPublishedContent C = UH.TypedContent(1234);
    RenderModel RM = new RenderModel(C); // the ctor throws NullReferenceException, probably because UmbracoContext.Current.PublishedContentRequest is null
    

    Thank you!

  • Randy McCluer 59 posts 87 karma points
    Jun 20, 2013 @ 23:32
    Randy McCluer
    1

    I believe what you want is a custom ContentFinder. Here's one I'm using where I just pass in the root node as a dummy RenderModel for some templates that I want to render as raw html.

    public bool TryFindContent(PublishedContentRequest docRequest) {
    IPublishedContent node = null;
    Regex externalPattern = new Regex("external/(.*)");
    if (externalPattern.IsMatch(HttpContext.Current.Request.Path) ) {
    string templateName = externalPattern.Match(HttpContext.Current.Request.Path).Groups[1].ToString();
    node = docRequest.RoutingContext.UmbracoContext.ContentCache.GetByRoute("/");
    docRequest.PublishedContent = node;
    docRequest.TrySetTemplate(templateName);
    } return node != null;
    }

     

  • Funka! 398 posts 660 karma points
    Jun 22, 2013 @ 00:24
    Funka!
    0

    Hi Randy, thank you for this.

    However I'm still a bit confused. Where would I get a PublishedContentRequest to pass into your method you just showed? The problem I have still seems to be that with my custom routing, and not inheriting my controllers from RenderMvcController, is that I don't have one of these, nor can I construct one. (The ctor is marked internal.) I did check for UmbracoContext.Current.PublishedContentRequest but is null inside my controller's action method. Likely because my custom routing, as I learned in another thread today, means that all of the normal "umbraco-stuff" that normally populates these things gets bypassed.

    Thank you!

  • Jeroen Breuer 4861 posts 12129 karma points MVP 3x admin c-trib
    Jul 11, 2013 @ 17:32
    Jeroen Breuer
    1

    Some more info about this post.

    You can also create your HomeModel like this:

    public class HomeModel : Umbraco.Web.Models.RenderModel
    {
        public HomeModel()
            : base(UmbracoContext.Current.PublishedContentRequest.PublishedContent)
    { } }

    That way your model has a parameterless constructor which is easier to use :-).

    Jeroen

  • Michiel 137 posts 294 karma points
    Aug 01, 2013 @ 23:55
    Michiel
    1

    The error makes sense, but I think it's going to trip up a lot of people when they write their first RenderMvcController. I have created a pull request to update the custom controller documentation to warn people about this potential issue.

  • Duncan 1 post 21 karma points
    Sep 05, 2014 @ 19:02
    Duncan
    0

    One solution I found was to use an empty RenderModel class as the model, and include my actual data model as a different property in the ViewBag.

    Weird, and it does have some reprocussions down the line, but they're not insubmountable, and it works.

    Regards

    Duncan

Please Sign in or register to post replies

Write your reply to:

Draft