Copied to clipboard

Flag this post as spam?

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


  • Lefteris Trichakis 12 posts 58 karma points
    Mar 21, 2016 @ 09:08
    Lefteris Trichakis
    0

    Update to 7.4.2. all custom ? hijacked controllers stopped working

    Hello, I just upgraded a 7.4.1 to 7.4.2 via nugget.

    All hijacked controllers stopped working. the error is

    Cannot bind source type Umbraco.Web.Models.RenderModel to model type .....
    

    I even created a new 7.4.2 with a highjacked controller but the same issue occurs.

    ModelBuilder is turned off for this project.

    Any thoughts? Any workaround?

  • Alex Skrypnyk 4695 posts 16793 karma points MVP 2x admin
    Mar 21, 2016 @ 09:21
    Alex Skrypnyk
    0

    Hi Lefteris,

    Did you check configs ? Maybe some nuget made some changes in your config.

    Thanks

  • Lefteris Trichakis 12 posts 58 karma points
    Mar 21, 2016 @ 09:29
    Lefteris Trichakis
    0

    All configs checked and are ok.

  • Alex Skrypnyk 4695 posts 16793 karma points MVP 2x admin
    Mar 21, 2016 @ 10:25
    Alex Skrypnyk
    0

    Maybe you need to rebuild your models ?

  • Lefteris Trichakis 12 posts 58 karma points
    Mar 21, 2016 @ 10:32
    Lefteris Trichakis
    0

    Tried all, build rebuild etc. I reverted back to 7.4.1 and hijacked controllers work ok.

    Also for the new test project I made starting at 7.4.2 that didn't worked as well, I downgraded it to 7.4.1 and works.

    The render models I create are like :

    public class TextPageModel : RenderModel
    {
        public TextPageModel()
           : base(UmbracoContext.Current.PublishedContentRequest.PublishedContent,UmbracoContext.Current.PublishedContentRequest.Culture)
        {
    
        }
        public string CustomProperty { get; set; }
    }
    

    And the RenderMvcController like:

    public class TextPageController : RenderMvcController
    {
        // GET: Home
        public ActionResult textPage(TextPageModel model)
        {
    
            model.CustomProperty="Yeah!";
            return CurrentTemplate(model);
        }
    }
    

    of course I user DocType / ViewName auto route. Works nicely in 7.4.1.

    Is this a bug of 7.4.2?

  • Roman 4 posts 74 karma points
    Mar 21, 2016 @ 12:17
    Roman
    0

    Hi, try this:

    public class TextPageController : RenderMvcController{
    
    public ActionResult textPage()
    {
        var model = new TextPageModel();
        model.CustomProperty="Yeah!";
        return CurrentTemplate(model);
    }
    
  • Roman 4 posts 74 karma points
    Mar 21, 2016 @ 10:32
    Roman
    0

    Hi, I've got the same problem after upgrading to 7.4.2.

  • Roman 4 posts 74 karma points
    Mar 21, 2016 @ 10:35
    Roman
    0

    I return base.Index(model); in my RenderMvcController and use @inherits UmbracoViewPage

  • Lefteris Trichakis 12 posts 58 karma points
    Mar 21, 2016 @ 12:35
    Lefteris Trichakis
    0

    I assume it is a full bug in 7.4.2

    Anyone else tested hijacked controllers?

  • Shannon Deminick 1444 posts 4861 karma points hq
    Mar 21, 2016 @ 15:04
    Shannon Deminick
    0

    As it turns out i think this is the one scenario we didn't test for: A sub-classed RenderModel type being bound to a controller's action parameter. I'd actually recommend not doing this as it's not quite best practices and relies on Singletons (i.e. not unit testable)

    There's a few work around's you can do:

    • Leave your controller's action parameter as type: RenderModel and create your custom model inside of your MVC Action
      • this is actually nicer anyways because you don't really want to rely on Singleton's in your models constructor - this makes it near impossible to unit test. You'd be better off using the given RenderModel 's values in the Action parameter to constructor your custom model.
    • Create a custom ModelBinder that inherits from DefaultModelBinder and implements IModelBinderProvider then in the IModelBinder GetBinder(Type modelType) method you would be targeting your model type explicitly like: return modelType == typeof (TextPageModel);
      • You can then assign this model binder directly using the [ModelBinder] attribute on your Action parameter, or you can assign it globally during startup.

    I'll create an issue on the tracker for 7.4.3 to fix this but these are suitable work-arounds for the time being.

  • Asbjørn 72 posts 168 karma points c-trib
    Mar 23, 2016 @ 09:48
    Asbjørn
    0

    @Shannon I use this construct to pass additional values back and forth. So creating the model inside the action wouldn't really work.

    Basically, I am doing what is shown in this example: https://umbraco.com/follow-us/blog-archive/2014/1/27/a-practical-example-of-route-hijacking-in-umbraco/

    Are you saying that this is not the proper way to do it? Then I would appreciate some guidance on how to this properly. I am aware of the unit testing limitation, of course - and if there is a way of doing this and making it unit testable, well - that would be great :)

  • Shannon Deminick 1444 posts 4861 karma points hq
    Mar 23, 2016 @ 10:13
    Shannon Deminick
    0

    I would not recommend doing it that way because again, you are tied to Singletons - the use of Singletons is an anti-pattern, they are there mostly for legacy reasons.

    I don't understand why you cannot create this model inside of the Action, can you explain this?

    That blog post example should be changed, the code should be:

    public class BlogOverview : RenderModel
    {
        public BlogOverview(IPublishedContent content) : base(content)
        {
        }
    
        public int Page { get; set; }
    }
    
    public class BlogOverviewController : RenderMvcController
    {
        public ActionResult BlogOverview(RenderModel model)
        {
            var blogOverviewModel = new BlogOverview(model.Content);
    
            if (blogOverviewModel.Page == 0) // default value for int
                blogOverviewModel.Page = 1;
    
            // do paging 
    
            return CurrentTemplate(blogOverviewModel);
        }
    }
    

    In 7.4.3 I've updated the code so that your original way will still work - but I really don't recommend that for the reasons above.

  • Shannon Deminick 1444 posts 4861 karma points hq
    Mar 21, 2016 @ 15:13
  • Lefteris Trichakis 12 posts 58 karma points
    Mar 22, 2016 @ 22:55
    Lefteris Trichakis
    0

    Hey Shannon, thank you for your reply. Is there any possibility to create a quick fix for 7.4.2?

    I think many devs have implemented RenderModels such way and would face pain when the try to upgrade to 7.4.2 (most reason of the true/false issue of 7.4.1).

  • Daniel Chenery 118 posts 458 karma points
    Mar 23, 2016 @ 09:14
    Daniel Chenery
    1

    Same problem here, I can confirm that switching from

    public ActionResult(CustomRenderModel model) 
    { 
        return View(model)
    } 
    

    To

    public ActionResult(RenderModel model)
    { 
        var myModel = new CustomRenderModel();
        return View(myModel)
    } 
    

    Has fixed it for me. Thanks!

  • Chris Ashton 50 posts 80 karma points
    Mar 24, 2016 @ 23:46
    Chris Ashton
    0

    Following an upgrade from 7.4.1 to 7.4.2, I was also getting errors of type Cannot bind source type Umbraco.Web.Models.RenderModel to model type Odin.Models.ViewModels.MyCustomPageModel..

    Shannon's fix you confirmed as working here, also worked for me.

    Thanks, Chris

  • Asbjørn 72 posts 168 karma points c-trib
    Mar 23, 2016 @ 09:51
    Asbjørn
    0

    An easy fix for this is to grab the fixed RenderModelBinder from https://raw.githubusercontent.com/umbraco/Umbraco-CMS/cd401f5e37b8517c2a6d39e2746d8295d56ebf92/src/Umbraco.Web/Mvc/RenderModelBinder.cs.

    Then rename the class to something unique and give it a different namespace. Then in an ApplicationEventHandler class you remove the faulty RenderModelBinder and add your own instead:

    public class Startup : ApplicationEventHandler
    {
        protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            //Workaround for http://issues.umbraco.org/issue/U4-8216
            var rendermodelbinder = ModelBinderProviders.BinderProviders.FirstOrDefault(x => x.GetType() == typeof(RenderModelBinder));
            ModelBinderProviders.BinderProviders.Remove(rendermodelbinder);
            ModelBinderProviders.BinderProviders.Add(new FixedRenderModelBinder());
        }
    }
    
  • Shannon Deminick 1444 posts 4861 karma points hq
    Mar 23, 2016 @ 10:18
    Shannon Deminick
    0

    You can find the nightly build here with the fix: https://ci.appveyor.com/project/Umbraco/umbraco-cms-hs8dx/build/artifacts

    We will get 7.4.3 out after the Easter break.

  • Daniel Chenery 118 posts 458 karma points
    Mar 23, 2016 @ 11:26
    Daniel Chenery
    0

    Hi Shannon,

    I'm actually in a similar scenario to Asbjørn here, I'm sending form post data to my model, as a result the model is now coming through as null! Changing this to a RenderModel() would obviously lose my custom values passed through...

    Any workarounds in this scenario? I'm working more with traditional MVC methods in this scenario.

  • Shannon Deminick 1444 posts 4861 karma points hq
    Mar 23, 2016 @ 11:45
    Shannon Deminick
    0

    I would suggest that you don't use a sub class of RenderModel for posting data. That model is for "rendering" information, not for posting. I would have to assume that you don't actually require all of the information in RenderModel to accomplish what you want?

    In any case, if you want to continue using a sub class of RenderModel for posting data

    • you can use the latest nightly I posted previously
    • or you can use Asbjørn's workaround to register a 'Fixed' model binder
    • or you can probably just attribute your action's parameter with [ModelBinder(typeof(DefaultModelBinder))]' since it was only working previously based on theDefaultModelBinder` executing
  • Daniel Chenery 118 posts 458 karma points
    Mar 23, 2016 @ 11:54
    Daniel Chenery
    0

    There are some Umbraco queries inside the controller, which threw a PublishedContentRequest error if I didn't inherit from RenderModel.

    The attributes didn't seem to fix anything either.

    I'm actually receiving an empty model, rather than the error mentioned above, but it's stopped working after the update. I'm wondering if it's a slightly different version of the bug.

  • Shannon Deminick 1444 posts 4861 karma points hq
    Mar 23, 2016 @ 12:13
    Shannon Deminick
    0

    Maybe you can show us some code as I don't really understand what you are doing and what you have tried.

  • Daniel Chenery 118 posts 458 karma points
    Mar 23, 2016 @ 12:23
    Daniel Chenery
    0

    I've got quite a lot of complex code going on, but here's a similar version of the code

    public CustomController : RenderMvcController
    {
        public ActionResult Post(MyViewModel model)
        {
            model.Property = "Something"; // Throws an error because model is null
    
            model.UmbContent = UmbracoHelper.TypedContentAtRoot().FirstOrDefault();
        }
    }
    
    public MyViewModel : RenderModel
    {
        public DetailsViewModel()
            : this(new UmbracoHelper(UmbracoContext.Current).TypedContentAtRoot().FirstOrDefault(), CultureInfo.CurrentUICulture)
        {
    
        }
    
        public DetailsViewModel(IPublishedContent content, CultureInfo culture)
            : base(content, culture)
        {
        }
    
        public string Property { get; set; }
    
        public IPublishedContent UmbContent { get; set; }
    }
    
    
    <form action="/Custom/Post" method="POST">
        <input type="text" name="Property" />
    </form>
    

    Before I updated model, would either by an empty MyViewModel, or contain the posted data. Now it's always null.

  • Shannon Deminick 1444 posts 4861 karma points hq
    Mar 23, 2016 @ 12:37
    Shannon Deminick
    0

    RenderMvcControllers are not intended to be POSTed to, that is what SurfaceControllers are for, but i guess everyone does all sorts of zany things with Umbraco.

    In any case, why do you need to POST the whole RenderModel? What is this Action actually doing? What was your code before (i.e. when it was working)?

    You said you tried this.... is this actually what you tried while keeping your model the same as it was before?

    public ActionResult Post([ModelBinder(typeof(DefaultModelBinder)]MyViewModel model)
    
  • Daniel Chenery 118 posts 458 karma points
    Mar 23, 2016 @ 12:54
    Daniel Chenery
    0

    Can you remove the "Surface" from the SurfaceController URL? So I can keep a nicely formatted URL for users.

    But ideally, yes, I do need to post the whole model, or a model of somekind, there are about 15+ properties, of many different types.

    I'd stupidly put [ModelBinder(typeof(DefaultModelBinder))] as an attribute on the class, not the paramater. My mistake. Applying it to the attribute does work. Thank you!

  • Shannon Deminick 1444 posts 4861 karma points hq
    Mar 23, 2016 @ 14:28
    Shannon Deminick
    1

    Please see these docs on creating forms:

    https://our.umbraco.org/Documentation/Reference/Templating/Mvc/Forms

    Part of the point of a SurfaceController is that is routed nicely (the same URL), it does NOT route to the /umbraco/surface URL. Please try it and you'll see. It all works by using BeginUmbracForm

  • Asbjørn 72 posts 168 karma points c-trib
    Mar 23, 2016 @ 14:32
    Asbjørn
    0

    Thanks for the pointers, Shannon. I shall definitely be changing my code in the future, then - good to know.

    Perhaps some docs/tutorials on the interaction of Route Hijacking and SurfaceControllers would be useful? (I knows you guys are busy, of course...)

    Have a nice Easter

  • Matthew Kirschner 317 posts 571 karma points
    Mar 31, 2016 @ 21:06
    Matthew Kirschner
    0

    This is quite an oversight.

    Thanks to that blog post by Sebastian, I have no doubt that many people have run into this issue. In order to prevent other developers from the headache I just went through trying to find this thread, this NEEDS to be listed as a breaking change in the release notes for 7.4.2. Especially since what I thought was going to be an easy update turned out to be a pain.

    Someone over at Umbraco HQ needs to work on quality control 'cuz this shit is bananas.

  • Andy Westley 16 posts 86 karma points
    Apr 04, 2016 @ 13:47
    Andy Westley
    0

    Hi there, I wonder if you could help me. I'm new to Umbraco and .net MVC, but was following a tutorial to set up my own route hijacking controller. I ended here up because I was getting the same initial error.

    Cannot bind source type Umbraco.Web.Models.RenderModel to model type .....
    

    I reworked my code as Shannon suggested, ie:

    public class RSSFeedController : RenderMvcController
    {
        // GET: RSSPage
        public ActionResult RssFeed(RenderModel model)
        {
            var rssFeedModel = new RssFeedModel(model.Content);
            rssFeedModel.RssFeed = GetRssFeedItems(model);
            Response.AddHeader("content-type", "text/xml");
            return CurrentTemplate(rssFeedModel);
        }
    
        private IEnumerable<IPublishedContent> GetRssFeedItems(RenderModel model)
        {
            const int pageSize = 5;
            var posts = model.Content.Children.ToList();
            return posts.OrderByDescending(x => x.CreateDate).Take(pageSize);
    
        }
    }
    

    Except now I get a compilation error in my view:

       CS1061: 'Umbraco.Web.Models.RenderModel' does not contain a definition for 'RssFeed' and no extension method 'RssFeed' accepting a first argument of type 'Umbraco.Web.Models.RenderModel' could be found (are you missing a using directive or an assembly reference?)
    

    at this line:

    @foreach (var article in Model.RssFeed)
    

    How do I tell my View to expect an RssFeedModel, and not a standard RenderModel? My RssFeedModel is based on RenderModel:

    public class RssFeedModel : RenderModel
    
        {
            public RssFeedModel(IPublishedContent content) : base(content) { }
    
            public IEnumerable<IPublishedContent> RssFeed { get; set; }
        }
    

    Thanks in advance for any advice.

    Andy

  • Matthew Kirschner 317 posts 571 karma points
    Apr 04, 2016 @ 13:47
    Matthew Kirschner
    0

    Upgrading to the nightly (v7.4.3build3482) has fixed the issue for me, though it does introduce a ReflectionTypeLoadException in the Developer section of the backoffice. I get this exception on the call for GetNonCoreTypesAssignableFrom.

  • Shannon Deminick 1444 posts 4861 karma points hq
    Apr 04, 2016 @ 14:19
    Shannon Deminick
    0

    Hi @Matthew & @Andy,

    To fix this you can use the workaround posted above, it will also continue to work after you upgrade because the fix is to ensure that the DefaultModelBinder continues to execute for any sub classes of RenderModel. See above: https://our.umbraco.org/forum/using-umbraco-and-getting-started/75998-update-to-742-all-custom-hijacked-controllers-stopped-working#comment-243220

    You can attribute your parameter with the specific model binder:

     public ActionResult Post([ModelBinder(typeof(DefaultModelBinder)]MyViewModel model)
    

    Maybe someone can mark that as fixed so it's obvious what the work around is.

    @Matthew if you have steps to reproduce the other error you are getting with the nightly can you please open a ticket with details and steps to reproduce. I haven't seen this error before.

  • Matthew Kirschner 317 posts 571 karma points
    Apr 04, 2016 @ 14:36
  • Andy Westley 16 posts 86 karma points
    Apr 04, 2016 @ 14:34
    Andy Westley
    0

    Hi @Shannon, thanks the speedy reply.

    My controller now looks like this:

    public ActionResult RssFeed([ModelBinder(typeof(DefaultModelBinder))]RssFeedModel model)
    {
        /*
        var rssFeedModel = new RssFeedModel(model.Content);
        rssFeedModel.RssFeed = GetRssFeedItems(model);
         * */
        model.RssFeed = GetRssFeedItems(model);
        Response.AddHeader("content-type", "text/xml");
        return CurrentTemplate(model);
    }
    
    
    
    private IEnumerable<IPublishedContent> GetRssFeedItems(RenderModel model)
    {
        const int pageSize = 5;
        var posts = model.Content.Children.ToList();
        return posts.OrderByDescending(x => x.CreateDate).Take(pageSize);
    
    }
    

    But I still get the compile error

    Compiler Error Message: CS1061: 'Umbraco.Web.Models.RenderModel' does not contain a definition for 'RssFeed' and no extension method 'RssFeed' accepting a first argument of type 'Umbraco.Web.Models.RenderModel' could be found (are you missing a using directive or an assembly reference?)
    
    Source Error:
    
    
    Line 16: 
    Line 17:      
    Line 18:    @foreach (var article in Model.RssFeed)
    Line 19:    {
    Line 20:       var tease = article.GetPropertyValue<string>("teaserText");
    

    Any thoughts?

    Cheers Andy

  • Shannon Deminick 1444 posts 4861 karma points hq
    Apr 04, 2016 @ 14:49
    Shannon Deminick
    1

    Hi Andy, Can you please advise that this worked before or are you just having a new issue?

    First thing I want to mention is that you should really not be using RenderModel for this purpose. IPublishedContent is the real model you should be using to display data that comes from it. In v8 we won't even have RenderModel everything will be IPublishedContent, RenderModel was only ever invented just in case we had additional model information that we needed to pass to a view outside of the data contained in IPublishedContent and we've determined that will never happen since there's never been a need in over 2 years. This is actually the first time I've seen people sub classing RenderModel, i didn't know people were actually doing this ... had I known that was going to happen we would have made RenderModel sealed since it's not exactly what we intended by design.

    In your case you could have a custom implementation of IPublishedContent called RssFeedContent that inherits from PublishedContentWrapped and you pass in the current IPublishedContent instance to its ctor. Then you can add your RssFeed property to this class... which you could also make with a backing field so that it's lazily resolved and only resolved once. Or you could just as easily make a GetRssFeedItems extension method on IPublishedContent

    Of course if this used to work then we need to fix it - which should be fixed in 7.4.3 which will be out soon

  • Steven Harland 78 posts 491 karma points c-trib
    Apr 22, 2016 @ 17:17
    Steven Harland
    0

    Hi Shannon,

    I'm surprised this is the first time you've seen this. I thought it was commonplace for people to subclass RenderModel in order to pass data from their custom controllers to their views. This approach is even suggested in the Umbraco documentation.

    What do you consider best practice for passing data from custom controllers to views?

  • Shannon Deminick 1444 posts 4861 karma points hq
    Apr 22, 2016 @ 18:48
    Shannon Deminick
    0

    That is not the problem being discussed on this thread. You can subclass RenderModel all you want to pass to your views, you can create and use any model that you want to pass to your views.... The problem listed here is that people are subclassing RenderModel to model bind to controller actions.

    We broke that by accident because that isn't a documented practice and not something that I'd recommend. It will work again with the latest release but it sort of works by fluke.

  • Steven Harland 78 posts 491 karma points c-trib
    Apr 22, 2016 @ 19:00
    Steven Harland
    0

    Ah OK, my mistake. Yes, I thought that was a little weird as well.

    Still interesting to hear that the RenderModel is going to go in v8 though. What will be the alternative approach?

  • Andy Westley 16 posts 86 karma points
    Apr 04, 2016 @ 15:01
    Andy Westley
    0

    Hi there,

    No this is brand new code - well, new for me, anyway. Actually I was following this tutorial

    https://cultiv.nl/blog/whats-this-umbraco-route-hijacking-all-aboutl/

    which is by the same guy as that other blog post reference.

    I'm going to have to have a sit down with a cup of coffee to work out how I should be using IPublishedContent. Any examples anywhere I can look at?

    Thanks again.

  • Sebastiaan Janssen 4659 posts 13035 karma points MVP admin hq
    Apr 04, 2016 @ 15:21
    Sebastiaan Janssen
    3

    This guy.. ;-) has updated his blog post (the one on umbraco.com was a crossposted copy) and the related gist to make sure all code examples are now correct.

  • Shannon Deminick 1444 posts 4861 karma points hq
    Apr 05, 2016 @ 09:20
    Shannon Deminick
    0

    Since this is brand new code, if you use the technique outlined in Sebastiaan's updated code and then ensure that you set the correct Model on your view @inherits UmbracoViewPage<BlogOverview> it should work... pretty sure that is the part you are missing.

  • Andy Westley 16 posts 86 karma points
    Apr 05, 2016 @ 09:28
    Andy Westley
    0

    Thanks @Shannon and @Sebastiaan. I'll have a look into that.

    Sorry, @Sebastiaan I hadn't spotted out you were "the guy" who had written both posts. :-)

    I guess if RenderModel is going away in v8 I should look at the IPublishedContent solution anyway, but for the meantime I'll try this. What's the worst that could happen - I build it, it goes live, and we forget all about it...

    Cheers Andy

  • Andy Westley 16 posts 86 karma points
    Apr 05, 2016 @ 10:54
    Andy Westley
    0

    Sorry, me again. Have confirmed my code now matches Sebastiaan's, and the line that's causing the problem is this one in the view:

    @inherits UmbracoViewPage<RssFeed>
    

    (apols to everyone if that's blindingly obvious). I've renamed my model from RssFeedModel to just RssFeed, in case there was any dependency on the name like there is with controller, but that made no difference.

    With the above line in my view I get this error message:

    Cannot bind source type Umbraco.Web.Models.RenderModel to model type EandT2016.Models.RssFeed.
    

    Without it (and of course without trying to use anything in the model), a simple page will render.

  • Andy Westley 16 posts 86 karma points
    Apr 15, 2016 @ 13:31
    Andy Westley
    0

    Sorry, meant to come back to this and admit humbly that I'd not spotted that my controller name didn't match with the document type name. Schoolboy error!

  • Doug Mackay 56 posts 155 karma points
    Apr 18, 2016 @ 15:44
    Doug Mackay
    1

    @Sebastiaan With your updated code in the blog post and the related gist I fail to see how paging can work

    public class BlogOverviewController : RenderMvcController
    {
        public ActionResult BlogOverview(RenderModel model)
        {
            var blogOverviewModel = new BlogOverview(model.Content);
    
            if (blogOverviewModel.Page == 0) // default value for int
                blogOverviewModel.Page = 1;
    
            // do paging 
    
            return CurrentTemplate(blogOverviewModel);
        }
    }
    

    As you're creating a new BlogOverview, Page is always going to be 0.

    The view is passing the page number in the query string and this was previously automatically bound to the model.

  • Sebastiaan Janssen 4659 posts 13035 karma points MVP admin hq
    Apr 18, 2016 @ 16:42
    Sebastiaan Janssen
    0

    Sure, just use Request.Querystring["Page"] for now, I don't know of a better way quickly to fix this some other way.

  • Shannon Deminick 1444 posts 4861 karma points hq
    Apr 20, 2016 @ 09:47
    Shannon Deminick
    0

    In MVC & WebApi, query strings are bound to action parameters... that is the very nature of how MVC binds things. You can just add a page parameter to your action. For example, Articulate does paging, it's just a p querystring which gets mapped to an action's parameter:

    https://github.com/Shazwazza/Articulate/blob/master/src/Articulate/Controllers/ArticulateTagsController.cs#L47

    I always suggest doing some very simple MVC sites and tutorials outside of Umbraco since all of this stuff is covered and is quite a fundamental part of MVC.

  • Doug Mackay 56 posts 155 karma points
    Apr 24, 2016 @ 21:25
    Doug Mackay
    1

    Thanks Shannon, I was pointing out that the updated blog post / gist are not correct as stated above.

    Ideally they need updated again so that paging still works i.e. something like :-

    public ActionResult BlogOverview(RenderModel model, int? page)
        {
            var blogOverviewModel = new BlogOverview(model.Content);
            if (page.HasValue) blogOverviewModel.Page = page.Value;
    
            blogOverviewModel.BlogPosts = GetPagedBlogPosts(blogOverviewModel);
    
            return CurrentTemplate(blogOverviewModel);
        }
    
  • Carl Bussema 38 posts 140 karma points
    May 10, 2016 @ 21:38
    Carl Bussema
    0

    I'm glad I stumbled on this as I was fumbling around for hours trying to figure out what should be relatively simple.

    Coming from a vanilla MVC world, it's very common:

    1. Design your model
    2. Create a GET controller method that returns a new() instance of that model
    3. Write a view that renders an appropriate form template using Razor helpers like @Html.TextBoxFor() etc
    4. Post that data to a POST controller method (often with the same name, just the [HttpPost] attribute) that accepts an object of type (model). MVC ModelBinding takes care of the magic.

    So given that the Umbraco docs tell us to subclass RenderModel for returning custom views, why wouldn't we then proceed to just post data back and rely on modelbinding to do the heavy lifting?

    And yes, the example posted is clearly wrong because there's no model binding going on anywhere in there and so it never actually reads the page parameter. Having to manually bind model parameters is just... ew.

    Now that I see we're supposed to use Surface controllers for posting data, and there's a whole Html.BeginUmbracoForm, I will have to adjust all my code, but man, if the documentation and all the code examples for RenderModel would just say "GET ONLY, NOT FOR POSTING, SEE ..." you might be able to avoid some of this confusion in the future.

  • Shannon Deminick 1444 posts 4861 karma points hq
    May 10, 2016 @ 21:58
    Shannon Deminick
    0

    As this thread is quite long can you please answer the following:

    And yes, the example posted is clearly wrong because there's no model binding going on anywhere in there and so it never actually reads the page parameter. Having to manually bind model parameters is just... ew.

    Can you please link to what is wrong so it can be fixed?

    if the documentation and all the code examples for RenderModel would just say "GET ONLY, NOT FOR POSTING, SEE ..." you might be able to avoid some of this confusion in the future.

    Can you please link to the documentation you think should be updated please - or create a task here: https://github.com/umbraco/umbracodocs/issues

  • Carl Bussema 38 posts 140 karma points
    May 11, 2016 @ 12:48
    Carl Bussema
    0

    On https://umbraco.com/follow-us/blog-archive/2014/1/27/a-practical-example-of-route-hijacking-in-umbraco/

    The last code block has a controller. This controller method never binds the input search parameter. A possible fix is this (changed method signature, added line to assign page; although I'd probably make a new constructor for the model if I was doing this from scratch). I also then removed the assignment that checks for page 0, since the default parameter handles it.

    public ActionResult BlogOverview(RenderModel model, int page = 1)
    {
        var blogOverviewModel = new BlogOverview(model.Content) { Page = page };
    
         // do paging 
         return CurrentTemplate(blogOverviewModel);
        }
    

    If I have time later, I'll submit a bug on the documentation, but as is, I'm behind several hours from trying to figure out how to make a simple GET-POST-REDIRECT custom search form.

  • Shannon Deminick 1444 posts 4861 karma points hq
    May 11, 2016 @ 12:52
    Shannon Deminick
    0

    Thanks for the links.

    I'm assuming you've seen these docs on forms - there's some simple tutorial links there: https://our.umbraco.org/Documentation/Reference/Templating/Mvc/Forms

  • Carl Bussema 38 posts 140 karma points
    May 11, 2016 @ 16:10
    Carl Bussema
    0

    I saw it eventually; it just wasn't the first things I found in a search.

    What I really want to see is documentation on how to use a custom model with model binding in a GET scenario. I shouldn't have to POST for a search form (read operations should use GET); and I shouldn't have to manually wire up all the complex search parameters that I have.

  • Carl Bussema 38 posts 140 karma points
    May 11, 2016 @ 19:22
    Carl Bussema
    0

    I just finished writing some very basic code, and I'm running into trouble where something is not model binding correctly.

    I took a simple

    public class SearchModel: RenderModel {
       [Required]
       public string Keyword { get; set; }
    }
    
    
    public class SearchSurfaceController : SurfaceController
    {
        public ActionResult DoSearch(SearchModel m)
        {
        ... 
        } 
    }
    

    View (called correctly on GET)

      @using (Html.BeginUmbracoForm<SearchSurfaceController>("DoSearch", null, new { @class="form-horizontal"}))
    {
            <div class="form-group">
                <div class="col-sm-4 text-right">
                    @Html.LabelFor(x => x.SearchTerm, new { @class = "control-label" })
                </div>
                <div class="col-sm-8">
                    @Html.TextBoxFor(x => x.SearchTerm, new { @class = "form-control" })
                </div>
    </div>
            <div class="form-group">
                <div class="col-sm-offset-4"><button type="submit" class="btn btn-default">Search</button></div>
    </div> 
     }
    

    And I can put a breakpoint on DoSearch(); it does get routed there, but the search term is not bound correctly. It's always null, so ModelState.IsValid is false. Request.Form["SearchTerm"] has the correct value; so it's just model binding that's failing. Aggravating.

  • Shannon Deminick 1444 posts 4861 karma points hq
    May 11, 2016 @ 20:48
    Shannon Deminick
    0

    So you are doing the thing that this whole thread is about, you are sub-classing RenderModel and then trying to model bind it on the server. This will work in some cases but it's just not intended. If you are POSTing, then use a small custom model and post what you need. If you are GETing, then just get normally with query strings. In any case, I'm pretty sure your model binding isn't working about because you have a property called Keyword on your model and you are passing up a property from some model called SearchTerm.

    What you want to do is quite simple - you really just want to get query strings to your page. You can just use normal html for that, only thing is the action will depend on what url your search page is. If it's the current page you can just supply no action, otherwise supply the one you want

    <form method="get" action="">
        <input type="text" name="Keyword" />
    </form>
    

    If your search page is a hijacked route page you can just add those query strings as parameters to your Action, MVC automatically binds query strings to action parameters, for example see here in Articulate source: https://github.com/Shazwazza/Articulate/blob/master/src/Articulate/Controllers/ArticulateSearchController.cs#L53

    It's also worth noting that BeginUmbracoForm has lots of overloads, just like the normal BeginForm and contains a FormMethod method so you can give it a GET

  • Carl Bussema 38 posts 140 karma points
    May 11, 2016 @ 20:58
    Carl Bussema
    0

    The keyword/searchTerm was just copy & paste laziness; I was trying to abstract out my code and make it as simple as possible, forgot to change one example.

    I know how to write basic HTML forms and MVC forms; I have 6+ years of MVC development, so it's not my first rodeo. I don't want to have to put in 10 extra parameters to a method. That's why we have models and model binding!

    If I want to make a search form with 10 different ways to search, I should be able to just model-bind, and to write a good Razor form, I want to use Razor helpers like LabelFor and TextBoxFor, so that I have proper intellisense and so that they will model-bind when I submit the form to my handler (which should be agnostic whether it's GET or POST).

    Yes, you can add 10 different parameters to the ActionMethod, but that's very bad MVC practice! What if you add a new parameter to your model? Now you have to update the ActionMethod. Normally you'd just update the service layer / function call that's doing something with the data, but now you have to tell the controller to expect new data... ugly.

  • Shannon Deminick 1444 posts 4861 karma points hq
    May 12, 2016 @ 08:55
    Shannon Deminick
    0

    Hi, some things to note, firstly yes, we need to get some docs up on GET forms. I'll make a note here for now: https://github.com/umbraco/UmbracoDocs/issues/336

    Coming from an MVC background i realize that it might seem strange that by default you have this RenderModel bound to a GET request as a parameter when using a custom controller for an umbraco page (hijacked routes). The reason for this is because it's just easier for the majority of users (which are generally not MVC devs) to use that parameter since its the standard data source/model for the page.

    In a normal MVC scenario (as you mentioned above) you have GET action that creates a model - which is normally based on a data source. In Umbraco, the page data source is RenderModel or more accurately IPublishedContent. For simplicity this data source is model bound (custom binder) as an action parameter. This is totally optional.

    Your custom controller action could just look like this and instead of having RenderModel as an action parameter you can create your model based on the umbraco page data source.

    public ActionResult Index()
    {
        var model = new Umbraco.Web.Models.RenderModel(CurrentPage);
        return CurrentTemplate(model);
    }
    

    In this scenario the base class exposes CurrentPage (the instance of IPublishedContent) as the data source for the page. You can use this data source to build up any model you want to pass to your view.

    Regarding model binding a search model, i mentioned query strings and individual parameters because that's simple for a single search field. If you want to bind a complex model, you certainly can. Whether you want your data source (RenderModel) model bound as a parameter or if you just want to use CurrentPage as your data source you can do both and it will bind:

    public ActionResult Index(SearchModel searchModel)
    public ActionResult Index(RenderModel render, SearchModel searchModel)
    

    This would be based on GET requests to the current page without using a SurfaceController. But you could also use a SurfaceController which could make sense if you wanted to have this possible with both a POST (+ validation) and a GET. For example, you could do this:

    public class SearchSurfaceController : SurfaceController
    {        
        public ActionResult DoSearch(SearchModel search)
        {
            //If you wanted to support POST with some validation you would
            // put that logic here
    
            return CurrentUmbracoPage();
        }
    }
    

    in conjunction with a partial view like:

    @model SearchModel
    @using(Html.BeginUmbracoForm<SearchSurfaceController>("DoSearch", FormMethod.Get)) 
    {
        <div>@Html.TextBoxFor(x => x.SearchTerm)</div>
        <input type="submit" />    
    }
    

    and the search model would still bind to either of these action definitions for your hijacked controller:

    public ActionResult Index(SearchModel searchModel)
    public ActionResult Index(RenderModel render, SearchModel searchModel)
    
  • PierreD Savard 183 posts 289 karma points
    Jun 07, 2016 @ 18:33
    PierreD Savard
    0

    Hi, Sorry about posting on that again. I just need to have a custom Model to fill up the page with more data. Not for passing data back to the controller. I try to be "more umbraco way" and follow Shannon post.

    I finally get: Controller:

    public class StoreController : RenderMvcController
    {
        public ActionResult Store(RenderModel model)
        {
            var storeModel = new StoreModel(model.Content);
    
            // adding some stuff
    
            return CurrentTemplate(storeModel);
        }
    }
    

    Model

    public class StoreModel: RenderModel
    {
        public StoreModel(IPublishedContent content) : base(content) { }
    
    
        public int Page { get; set; }
    }
    

    And in my page:

    @using umbLVC2.models @inherits Umbraco.Web.Mvc.UmbracoViewPage

    My umbraco version is at 7.3.1, but I still get the error: Cannot bind source type Umbraco.Web.Models.RenderModel to model type umbLVC2.models.StoreModel

    I am lost... ;-) Any suggestion?

  • PierreD Savard 183 posts 289 karma points
    Jun 07, 2016 @ 19:50
    PierreD Savard
    0

    HO! I found it. I need to change the action in the custom controller from:

     public ActionResult Store(RenderModel model)
    

    to:

    public override ActionResult Index(RenderModel model)
    

    Now that work. It is a "normal" way to achieve that?

  • Shannon Deminick 1444 posts 4861 karma points hq
    Jun 07, 2016 @ 19:58
    Shannon Deminick
    0

    Hi, if you want to use your custom StoreModel then you'll want to use that model in your view, currently your view is:

     @inherits Umbraco.Web.Mvc.UmbracoViewPage
    

    Which is not using your StoreModel at all, you'd want

         @inherits Umbraco.Web.Mvc.UmbracoViewPage<StoreModel>
    

    The docs for this is here: https://our.umbraco.org/Documentation/Reference/Routing/custom-controllers

    If you want to have a custom Action for a specific template, then Action Name --> Template Name

    Otherwise if you just want one Action to execute for all templates for that doc Type, then Index is what you want

    If you want to read further about the many ways of having custom models, here's the Options: http://issues.umbraco.org/issue/U4-8357#comment=67-28909

    I need to get these things in the docs but this just emphasizes that if you want to work with totally custom models, bound action parameters, etc... you certainly can.

  • BartKrul 8 posts 88 karma points
    Oct 04, 2016 @ 14:23
    BartKrul
    0

    Hello,

    I've been stuck the entire day on the same problem. I get that this isn't the correct way of using RenderModel, all I want to do is to insert dynamic data from my model into the view.

    ATM I have thos code:

    Model:

    public class VerzoekIndienenModel : RenderModel
    {
        public IEnumerable<string> Project { get; set; }
        public IEnumerable<string> Categorie { get; set; }
        public List<string> Test { get; set; }
        public string TestString { get; set; }
    
        public VerzoekIndienenModel(IPublishedContent content, CultureInfo culture) 
            : base(content, culture)
        {}
    }
    

    Controller:

     public class VerzoekIndienenController : RenderMvcController
    {
    public override ActionResult Index(RenderModel model)
        {
            //throw new Exception("bang");
            var vmodel = new VerzoekIndienenModel(model.Content, model.CurrentCulture);
            vmodel.TestString = "test";
            return CurrentTemplate(vmodel);
        }
    }
    

    View:

         @inherits UmbracoViewPage<VerzoekIndienenModel>
    @{
        Layout = "Master.cshtml";
    }
    @{
    }
    
    <body>
    HTML STUFF
    </body>
    

    Can anyone help me? Thanks in advance.

  • Steven Harland 78 posts 491 karma points c-trib
    Oct 07, 2016 @ 08:08
    Steven Harland
    0

    Hi BartKrul,

    That looks fine to me. What is the problem you're having?

    You should be able to access your TestString property in the view as follows:

    @Model.TestString
    

    @Model.Content will be your current content as usual.

  • BartKrul 8 posts 88 karma points
    Oct 12, 2016 @ 07:35
    BartKrul
    0

    Hi Steve,

    I can't even try to acces TestString. The problem I got was the exact same one as this thread is about: Cannot bind source type Umbraco.Web.Models.RenderModel to model type .....

    I switched to doing everything with AJAX now and not using a seperate controller for the page.

    It's sort of confusing as the official umbraco page has documentation about how to implement this (https://our.umbraco.org/documentation/reference/routing/custom-controllers). But here I'm reading RenderModel isn't supposed to be used in this way.

  • Shannon Deminick 1444 posts 4861 karma points hq
    Oct 12, 2016 @ 07:45
    Shannon Deminick
    0

    The problem you are having is most likely because you have different views and layouts defined with different models.

    Your view is declared with:

     @inherits UmbracoViewPage<VerzoekIndienenModel>
    

    which means it's @Model = VerzoekIndienenModel

    What is your Master.cshtml declared as?

    If it's forms you are having issues with (i.e. you've switched to AJAX), you can follow these tutorials to get a form working: https://our.umbraco.org/documentation/Reference/Templating/Mvc/forms

  • Norbert Haberl 30 posts 112 karma points
    Nov 15, 2016 @ 13:29
    Norbert Haberl
    0

    Hey, I have the problem that I'm calling a partial view

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<UmbracoGitHubReportViewer.Models.ReportSettingsModel>
    

    and additionally I have a hijacked controller

    public class SSRSReportController : RenderMvcController
    {
        public override ActionResult Index(Umbraco.Web.Models.RenderModel model)
        { ..
    

    and I call it like this

    switch (docTypeAlias)
            {
                case "ssrsreport":
                    <h1>Textpage for Reports</h1>                                
                    @Html.Partial("SSRSReport", new UmbracoGitHubReportViewer.Models.ReportSettingsModel(@component));
    
                break;
            }
    

    The Controller is never called !!!

  • Shannon Deminick 1444 posts 4861 karma points hq
    Nov 15, 2016 @ 13:44
    Shannon Deminick
    1

    Html.Partial does not execute any controllers, it just renders a partial view.

  • Norbert Haberl 30 posts 112 karma points
    Nov 15, 2016 @ 14:22
    Norbert Haberl
    0

    So Route hijacking is not possible for Partial Views ?

  • Shannon Deminick 1444 posts 4861 karma points hq
    Nov 15, 2016 @ 14:45
    Shannon Deminick
    0

    Route hijacking has nothing to do with Partial Views https://our.umbraco.org/documentation/reference/routing/custom-controllers - that is for dealing with a request to a content item.

    Partial Views are purely based an ASP.NET MVC and Umbraco doesn't have anything to do with that pipeline.

    https://docs.microsoft.com/en-us/aspnet/core/mvc/views/partial

    If you want to use a controller for a Partial View then you would use a Child Action, which is also just an ASP.NET MVC process (not directly related to Umbraco)

    http://stackoverflow.com/questions/12530016/what-is-an-mvc-child-action

Please Sign in or register to post replies

Write your reply to:

Draft