Copied to clipboard

Flag this post as spam?

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


  • Xander 7 posts 38 karma points
    May 29, 2013 @ 09:25
    Xander
    1

    return CurrentUmbracoPage() results in 'Error loading Partial View script'

    Overview of the problem:

    I've created a Surface controller with an action that is called using @Html.Action(...).
    The @Html.Action call is done within a Macro partial view and the macro is included within the content of a page using the rich text editor.
    (I'm new to this so if i'm going about things the wrong way then please let me know.)
    The Surface controller has a GET and a POST action but it's the get action called within the macro partial.
    Get action renders fine, entering no data into the form will invalidate the model state (which is what i'm currently testing).
    submitting the form (with no entered data) means i can step into my POST action, ModelState.IsValid is set to false and CurrentUmbracoPage() is returned.
    All fine... No Exceptions encountered when debugging...
    It's at this point that the error text "Error loading Partial View script" apears on the page.
    All I'm trying to do is return the same page with the validation messages showing.

    Details:

    Umbraco v6.0.5

    The Controller I'm currently working on is used to reset a user's password. I also have a login conroller that is getting around this issue by using RedirectToCurrentUmbracoPage().

    to access the page that contains the macro i use the address http://{testhost}/Reset-Password
    the error text returned reads: Error loading Partial View script (file: ~/Views/MacroPartials/ResetPassword.cshtml)

    code is within a seperate solution and views and bin directories are copied accross.
    nuget package UmbracoCMS.Scaffolding is used.

    Controller code:

    public class ResetPasswordSurfaceController : SurfaceController {        
            [ChildActionOnly]
            [HttpGet]
            public ActionResult Reset(string tokenstring email) {
                 // Validation Code Omited             
    var user = Membership.GetUser(username);              return PartialView("Reset"new ResetPasswordSurfaceModel { UserID = user.ProviderUserKey.AsInt() });         }         [HttpPost]         public ActionResult PostReset(ResetPasswordSurfaceModel model) {             if (ModelState.IsValid) {                  //Password reset code omited
    return RedirectToCurrentUmbracoPage();              } //works but only partial view content is rendered             // return PartialView("Reset",model);             return CurrentUmbracoPage();         }     }

     

    View - ~\Views\ResetPasswordSurface\Reset.cshtml:

    @model UmbracoExt.Models.ResetPasswordSurfaceModel
    @using (Html.BeginUmbracoForm("PostReset""ResetPasswordSurface")) {
          @Html.EditorForModel() 
        <input type="submit" value="Submit" />
    }

     

    Macro Partial View - ~\Views\MacroPartials\ResetPassword.cshtml:

    @inherits Umbraco.Web.Macros.PartialViewMacroPage   
    @Html.Action("Reset""ResetPasswordSurface")
    



    Any help is appreciated.


    Edit:

    Removing the [HttpGet] attribute from the Reset Action has revealed that after the PostReset action is called the Reset action is also called.

    Renaming PostReset to Reset and re-adding the httpget attribute to the original Reset Action results in the post action being called twice.
    the second time it is called causes the exception:

    Can only use UmbracoPageResult in the context of an Http POST when using a SurfaceController form


    I have reverted the changes so i'm back at Reset ([HttpGet]) being called after the PostReset action.

    So the problem still stands. How can i get around this issue?
    I need to return the result from the PostReset Action.

     

  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    May 31, 2013 @ 02:15
    Shannon Deminick
    1

    Hi,

    This is a complicated description :) I'll start with some basics:

    [ChildActionOnly] shouldn't be used with [HttpGet]. The reason is because when you mark an action with [ChildActionOnly] you are telling MVC to make this not publicly routable (i.e. it doesn't have a URL) and can only be rendered using Html.Action so applying [HttpGet] is irrelavent.

    Next, when you post to a SurfaceController, you are posting directly to the surface controller, it is the same as if you were in a normal MVC app and posted to a controller. Therefore, even in a normal MVC app you'd never just return a PartialView because all you'll get is the markup for the partial view. This is exactly the same when working within Umbraco.

    It's also very important to NOT cache your macro in your macro definition in the Umbraco back office as this will cache validation messages, etc...

    Next, the message you are getting: "Error loading Partial View script" is due to an exception being thrown during the rendering of your macro. You can change the exception handling of macros by changing a config setting in your umbracoSettings.config file, by default you should have this:

    <MacroErrors>inline</MacroErrors>

    but you can change the value to: "silent", "inline" or "throw". For debugging it is best to change this to "throw" so you can see the real exception that is being thrown. My guess is that you are getting a Null reference exception in this code:

    varuser=Membership.GetUser(username);
                 
    returnPartialView("Reset",newResetPasswordSurfaceModel{UserID=user.ProviderUserKey.AsInt()});

    Since you are not doing a null check on 'user' and then trying to access it's properties. And the reason it will be null is because you are not passing in any route parameters to your child action like 'token' or 'email' in your call:

    @Html.Action("Reset","ResetPasswordSurface")

    You should be doing something like this:

    @Html.Action("Reset","ResetPasswordSurface", new {token="sometoken", email="someemail"})

    Cheers! Shannon 

  • Xander 7 posts 38 karma points
    May 31, 2013 @ 04:00
    Xander
    0

    Thanks for that quality response.
    I didn't mean for my request to be complicated, just thorough. I was hoping to avoid responses like "what version are you using?", "What are you trying to achieve?" or "can you post your source code?". (the more info i include the less back-and-forth is required.)
    Please forgive any ignorance on my part as i'm a webforms developer attempting to familiarize myself with both Umbraco and MVC.

    Steps i've taken so far:

    1. removed [HttpGet] from Reset action
    2. changed umbracoSettings.config with  <MacroErrors>throw</MacroErrors>
    3. Handled exception in controller
      side note: When the page with an encrypted token in the query string is posted back the token seems to be UrlDecoded a second time resulting in an exception when decrypting.

    This does not seem to have resolved the issue.


    What Is currently occurring:

    When the form is submitted the PostReset action is executed, but then the Reset action is executed and the result of this action is displayed in the response.

    What i'm trying to achieve:

    When the form is submitted the PostReset action is executed and the resut of this action is displayed in the response.

     

    I've tried calling the PostReset action from the macro when the HttpMethod is POST but an exception is thrown:

    an only use UmbracoPageResult in the context of an Http POST when using a SurfaceController form

    It definately IS a POST as you can see from the modified Macro partial code (Exception thrown on the PostReset action line):

    @inherits Umbraco.Web.Macros.PartialViewMacroPage
    @if (Request.HttpMethod == "POST") { 
        @Html.Action("PostReset""ResetPasswordSurface")
    } else {
            @Html.Action("Reset""ResetPasswordSurface")
    }

     

    From everything i've read Return CurrentUmbracoPage should allow me to display the validation messages for my model.

    What am i doing wrong?

  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    May 31, 2013 @ 04:29
    Shannon Deminick
    103

    Here's what is occuring:

    Before POST

    1. Umbraco is rendering your page
    2. During this page rendering, it renders your partial view macro
    3. During the partial view macro rendering you are rendering a ChildAction... and from your current code you are not passing any route parameters to this child action
    During POST that is valid
    1. Data is POSTed to your own controller action "PostReset" and accepts a model of type ResetPasswordSurfaceModel
    2. The data is valid, we return "RedirectToCurrentUmbracoPage()"
    3. The request is completely redirected so the whole Before POST process starts again
    During POST that is not valid
    1. Data is POSTed to your own controller action "PostReset"
    2. The data is not valid, and therefore errors will automatically be added to ModelState (and if you want you can add additional errors)
    3. we return "CurrentUmbracoPage", this does not redirect but sends the request back through the Umbraco pipeline but maintains ModelState and ViewData
    4. The entire process of Before POST process starts again
    So yes, the ChildAction "Reset" will always execute because it's part of the rendering process. ChildActions are simply used to render a view, they are not part of the POST logic, they're part of your view logic. The PostReset action is ONLY for HttpPost, you should never render that as a ChildAction it's purely to handle the data that you've posted to the server.
    Still, nowhere in your code can i see you passing any parameters to the ChildAction "Reset".
    Here's a full working version of code that works as an example:
    Partial view macro markup to render child action form (with additional logic to simply display macro properties)
    @inherits Umbraco.Web.Macros.PartialViewMacroPage
    
    <p>
        @Model.MacroId <br />
        @Model.MacroName <br />
        @Model.MacroAlias <br />
        @Model.MacroParameters.Count() <br />
        @CurrentPage.Id <br />
        @Model.Content.Id
    </p>
    
    @Html.Action("TestForm", "LocalSurface", new { memberId = 1234 })
    The ChildAction called "TestForm":
    [ChildActionOnly]
    public ActionResult TestForm(int memberId)
    {
        var page = CurrentPage;
    
        //TODO: Perhaps you want to lookup this model from a database or something
        // for this example, we'll pre-populate the name
        return PartialView("Form", new TestFormModel { Name = "Some guy" });
    }
    The markup to render the form "Form"
    @model TestFormModel
    @{
        HtmlHelper.ClientValidationEnabled = true;
        HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
    }
    @using (Html.BeginUmbracoForm("TestForm", "LocalSurface"))
    {        
        if (TempData["SuccessMessage"] != null)
        {
            <strong>@TempData["SuccessMessage"]</strong>
        }    
        <div class="editor-label">
            @Html.LabelFor(x => Model.Name)
        </div>
        <div class="editor-field">
            @Html.TextBoxFor(x => Model.Name)
            @Html.ValidationMessageFor(x => Model.Name)
        </div> 
        <div class="editor-label">
            @Html.LabelFor(x => Model.Email)
        </div>
        <div class="editor-field">
            @Html.TextBoxFor(x => Model.Email)
            @Html.ValidationMessageFor(x => Model.Email)
        </div>    
        <div class="editor-label">
            @Html.LabelFor(x => Model.Comment)
        </div>
        <div class="editor-field">
            @Html.TextAreaFor(x => Model.Comment)
            @Html.ValidationMessageFor(x => Model.Comment)
        </div>    
        <input type="submit" />
    }

    The code for the [HttpPost] action:

    [HttpPost]
    public ActionResult TestForm(TestFormModel model)
    {
        //For this zany test, we are going to force an invalid post
        //when the Name == 'showerror' (case insensitive)
        if (model.Name.InvariantEquals("showerror"))
        {
            ModelState.AddModelError("Name", "Invalid name");
        }
        if (ModelState.IsValid)
        {        
            //Show a success message.
            TempData.Add("SuccessMessage", "Form submitted successfully with name: " + model.Name);
            return RedirectToCurrentUmbracoPage();
        }
        else
        {
            //add some stuff to ViewData if you want, you can extract it in your views.
            //if you are executing a child action you can extract it with 
            //ParentActionViewContext.ViewData
            ViewData["TestMessage"] = "There were errors";        
            return CurrentUmbracoPage();
        }
    }
  • Xander 7 posts 38 karma points
    May 31, 2013 @ 06:15
    Xander
    0

    Okay i think it's clicked with me now.

    The post action does not return anything for the response (that bit was new to me).
    After the post when the Reset action is run the second time, since the modelstate is maintained, by passing a newly instantiated model, this model will inherit the model state of the model processed in the POST action (PostReset).

    During the second time the Reset action was called, since the token data has been corrupted along the lines somewhere the token validation fails and it never gets to the point where it returns the partial view.

    i temporarily bypassed the token validation and sure enough the model validation messages were displayed.
    All I need to do now is get it to stop corrupting the encrypted token when calling returnCurrentUmbracoPage();.

    Thanks for all your help, It is very much appreciated.

    BTW. the parameters for the Reset method are received from the querystring the url generated and sent to the user by email.

  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    May 31, 2013 @ 06:21
    Shannon Deminick
    0

    Ok at least we're on to something here :)

    With regards to the parameters for the Reset method coming from the current query string of the request, are you passing in these parameters manually to your Html.Action call or is it magically binding these parameters based on the current query string of the request ? If it is the latter, then this is probably your issue. To pass parameters into a Child Action you should explicitly do this, it's more of a fluke if your parameters are being bound by the actual query strings of the request.

    Try this if that is the case:

    @Html.Action("Reset","ResetPasswordSurface",new{token=Request.QueryString["token"], email=Request.QueryString["email"]})

    You'll obviously need to use the correct query string keys.

  • Xander 7 posts 38 karma points
    May 31, 2013 @ 06:49
    Xander
    0

    Fluke! Wow. I was pretty sure that the model binding and rout data logic used to determine the appropriate controller action worked this way by design.
    Oh well, i replaced the Html.Action call to include the values from the query string.
    I was hoping that this might prevent the token from being sent unencoded when the form is submitted, no such luck.

  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    May 31, 2013 @ 06:53
    Shannon Deminick
    0

    The reason it's 'fluke' is because ChildActions are slightly different than normal actions. They are not publicly routable and used to just render views from views. Some call this an MVC anti-pattern (but whatevs). So yeah they'll follow the same binding rules as normal controllers but what would happen if you wanted to use that child action in a page that never has query strings. Anyways, IMHO its probably best to explicitly pass in the route parameters to child actions.

  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    Jun 01, 2013 @ 03:11
    Shannon Deminick
    0

    BTW, I've updated the docs here to explain the routing process:

    http://our.umbraco.org/documentation/Reference/Mvc/forms#UnderstandingtheRoutingProcess

    and added docs describing how to access the ViewData in child actions here:

    http://our.umbraco.org/documentation/Reference/Mvc/forms/turorial-child-action#AccessingViewData

  • Benoit 14 posts 34 karma points
    Jun 09, 2013 @ 23:52
    Benoit
    0

    Hello,

    I'm having the same problem and have no clue

    This are the controller actions

    [ChildActionOnly]
    public ActionResult SubscribeToSession(int trainingid)
    {
      return PartialView("Inschrijving", subscriptionViewModel);
    }
    
    [HttpPost]
    public ActionResult SubscribeToSession(SubscriptionViewModel model)
    {
      if (!ModelState.IsValid)
      {
         return CurrentUmbracoPage();
      }  
      // Handle post
    }
    

    This is the error i get

    Can only use UmbracoPageResult in the context of an Http POST when using a SurfaceController form
    

    Following your explanation, i do not see what i am doing wrong...

    thx for your help...

    Benoit

  • Andy Butland 422 posts 2334 karma points MVP 4x hq c-trib
    Jun 10, 2013 @ 17:02
    Andy Butland
    0

    Hi Benoit - assuming your controller actions are within a controller that inherits from SurfaceController, you may be running into an issue because your two methods are named the same.  See here and in particular the attribute [NotChildAction] that you can use to decorate the second of your methods.

  • Mark 255 posts 612 karma points
    Dec 11, 2013 @ 13:36
    Mark
    0

    I have also had some issues where I'd followed the ChildAction Form method provided by @Shannon. I kept  getting the error message:

    Can only use UmbracoPageResult in the context of an Http POST when using a SurfaceController form

    Even though I'd followed the example to the letter.

    What I had done was create a document type called Member Login (with alias "MemberLogin").

    I had called my surface controller class MemberLoginController, and was therefore accessing it using the same name as the document type, "MemberLogin".

    After pulling my hair out for a couple of hours I realised there might be a naming conflict, so I renamed the surface controller to MemberLoginFormController. Then it all worked as expected.

    So it would seem having a document type alias and a surface controller with the same name causes some issues. Just thought I'd point it out in case anyone else runs into the same issue.

  • simon kerr 31 posts 85 karma points
    Jul 15, 2014 @ 17:32
    simon kerr
    0

    that did it for me. nice one. i'm using the hybrid framework and using a macro to render a custom form. been banging my head against the wall all day and finally solved it thanks to you!

Please Sign in or register to post replies

Write your reply to:

Draft