Copied to clipboard

Flag this post as spam?

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


  • andrew shearer 506 posts 653 karma points
    Aug 19, 2013 @ 07:53
    andrew shearer
    0

    Umbraco 6 MVC form action advice

    Hi I’m following the guides below to create an MVC form in Umbraco 6.1.3. I have a SurfaceController with an action for rendering out my form, and also an HttpPost action for handling form submission. My question is in regards to showing a confirmation message in the view at the conclusion of the submission i.e. “thanks for contacting us” etc. The example uses “TempData” to set a flag but then proceeds to do a full redirect using RedirectToCurrentUmbracoPage(). Can anyone explain how the TempData is persisted across round trips and/or if this is still actually the recommended approach. I couldn't get it working this way.

    Thanks

    http://umbraco.com/follow-us/blog-archive/2013/7/14/moving-from-webforms-to-mvc.aspx

    http://our.umbraco.org/documentation/reference/Mvc/surface-controllers

  • Ismail Mayat 4511 posts 10090 karma points MVP 2x admin c-trib
    Aug 19, 2013 @ 08:29
    Ismail Mayat
    0

    Andrew,

    I had similar issue see http://our.umbraco.org/forum/templating/templates-and-document-types/43163-Umbraco-with-mvc and also http://issues.umbraco.org/issue/U4-1339 so becuase its page redirect tempdata is empty for the time being I have put stuff in httpcontext.current.items and then reference that in the view. Not ideal but could not figure out how to get round this.

    Regards

    Ismail

  • andrew shearer 506 posts 653 karma points
    Aug 19, 2013 @ 09:55
    andrew shearer
    0

    thanks for the links, Ismail.

    the comments in http://issues.umbraco.org/issue/U4-1339 are interesting reading. "When a form submission is successful, you redirect ..." that's definitely a new concept to me. A conventional MVC site wouldn't operate in this way would it?

    Maybe the issue is that the use of RedirectToCurrentUmbracoPage() is flawed in this scenario? unless you do actually want to navigate away from the current process flow (by redirecting to the current url)...

  • Ismail Mayat 4511 posts 10090 karma points MVP 2x admin c-trib
    Aug 19, 2013 @ 10:50
    Ismail Mayat
    0

    Andrew,

    I to would NOT want to redirect so what i have is

    [HttpPost]
        public ActionResult HandleSearchForm(SearchFormModel model)
        {
            //model not valid, do not save, but return current umbraco page         
            if (ModelState.IsValid)
            {         
                //do a search add results to tempdata
    
                string examineQuery = string.Empty;
    
                var query = new Dictionary<string, string>();
    
                var displayDictionary = new Dictionary<string, string>();
    
                query = CreateQuery(model, out displayDictionary);
    
                var results = _searchService.Search(query, out examineQuery).ToList();
    
                SaveSearch(displayDictionary, results);
    
                //not the best way to do this see http://issues.umbraco.org/issue/U4-1339
                HttpContext.Items.Add("SearchResults", results);
    
                HttpContext.Items.Add("generatedQuery", examineQuery);
    
                AddToSession(displayDictionary, results);
    
                return CurrentUmbracoPage();
            }
    
            TempData.Add("SearchFormError", true);
    
            return RedirectToCurrentUmbracoPage();
    
        }
    

    Regards

    Ismail

  • andrew shearer 506 posts 653 karma points
    Aug 19, 2013 @ 11:51
    andrew shearer
    0

    thanks, Ismail. I have to say using Session doesn't appeal to me much either :p I'll try out your example tomorrow. thanks!

  • Ismail Mayat 4511 posts 10090 karma points MVP 2x admin c-trib
    Aug 19, 2013 @ 12:00
    Ismail Mayat
    0

    Andrew,

    The session stuff I need because after a search users can email the results to themselves and that is done via contour and a workflow. The actual display of results is done by passing stuff back using HttpContext.Items ideally it should be in TempData or ViewBag.

    Regards

    Ismail

  • andrew shearer 506 posts 653 karma points
    Aug 20, 2013 @ 13:41
    andrew shearer
    0

    I tried varying combinations of ViewBag/ViewData/TempData and RedirectToCurrentUmbracoPage() / CurrentUmbracoPage(), but nothing worked. I had a look through the 6.1.3 source for those two methods and although they are remapping ViewData content I couldn't see any obvious bug. I'm not sure what i'm missing here.

  • andrew shearer 506 posts 653 karma points
    Aug 21, 2013 @ 01:41
    andrew shearer
    0

    It turns out my issue was mistakenly using an action to render my form rather than a partial. (as per this doc http://our.umbraco.org/documentation/reference/Mvc/forms/turorial-child-action)

    So I changed:

        [ChildActionOnly]
        public ActionResult ShowCommentForm()
        {
            var addCommentViewModel = new AddCommentViewModel();
    
            return PartialView("Forms/AddComment", addCommentViewModel);
        }
    

    to this in my View:

    @Html.Partial("Forms/AddComment", new AddCommentViewModel())
    

    It appears that the form render Action was conflicting with how Umbraco processes my form httpPost Action i.e. ViewData was being reinitialized somewhere in the pipeline?

    Edit: I would also argue that the model for my Form isn’t “Umbraco RenderModel” or a derivative of. So, given the independent model i'm using in my example, I think using an Action rather than a Partial to render the form in my template view is actually more correct.

  • andrew shearer 506 posts 653 karma points
    Aug 21, 2013 @ 01:57
    andrew shearer
    0

    Also, I would’ve thought the preference would be to stay in the context of the current HttpPost when processing a form action rather than navigating away at the conclusion using RedirectToCurrentUmbracoPage()? I tried to mock up how I wanted my Action to work solely using CurrentUmbracoPage(), but noticed that the master/template view that this form Action sits inside wouldn’t reflect the content that was just added during my form action. This is the sequence I was trying to achieve:

    1. Render my template view which contains a Partial with a “BeginUmbracoForm” form
    2. Submit the umbraco form (i.e. HttpPost) to the SurfaceController action that the form is wired to.
    3. The SurfaceController Action “does some stuff” (e.g. add content to CMS)
    4. The original View is rendered again with a confirmation message that the form action is complete. (ViewBag being used to pass this ‘state’ from the action to the view..)
    5. This template View also contains logic that lists out the content just added in the form action i.e.

      var last3comments = Model.Content.Children
                      .Where(x => x.DocumentTypeAlias == "Comment" && x.IsVisible())
                      .OrderByDescending(d=>d.CreateDate)
                      .Take(3)
                      .ToList();  
      

    Given this scenario, #5 doesn’t reflect the new content just added. (NB using a breakpoint i could see that this logic is run after the form action)

    Won't Umbraco MVC work unless we follow the Post/Redirect/Get (PRG) Pattern?

  • Sebastiaan Janssen 5045 posts 15477 karma points MVP admin hq
    Aug 21, 2013 @ 09:18
    Sebastiaan Janssen
    0

    After posting make sure to:

    return RedirectToCurrentUmbracoPage()
    

    And in order to use ChildActions you need to do (if your controller is named CommentController):

    @Html.Action("Comment", "ShowCommentForm")
    

    So: perform the action ShowCommentForm in the Comment controller. In ShowCommentForm you would do something like this to get your comments in a list:

    [ChildActionOnly]
    public ActionResult ShowCommentForm()
    {
        //return PartialView("StatusUpdate", new StatusUpdateModel());
    
        var currentNode = Umbraco.TypedContent(UmbracoContext.PageId);
        var lastComments = currentNode.Children
                                .Where(x => x.DocumentTypeAlias == "Comment" && x.IsVisible())
                                .OrderByDescending(d => d.CreateDate)
                                .Take(3)
                                .ToList();
    
        return PartialView("Comments", lastComments);
    }
    

    The Comments view can iterate through the List

    @model List<IPublishedContent>
    
    <ul>
    @foreach (var comment in Model)
    {
        <li>@comment.Name</li>
    }
    </ul>
    

    Of course your CommentViewModel has other properties so you might want to consider adding LastComments as a property to your Model.

  • Ismail Mayat 4511 posts 10090 karma points MVP 2x admin c-trib
    Aug 21, 2013 @ 09:47
    Ismail Mayat
    0

    Sebastiaan,

    This is still http://issues.umbraco.org/issue/U4-1339 and doing return RedirectToCurrentUmbracoPage() wont get round it?

    Regards

    Ismail

  • andrew shearer 506 posts 653 karma points
    Aug 21, 2013 @ 10:26
    andrew shearer
    0

    thanks for that info Sebastiaan, but if i use:

    return RedirectToCurrentUmbracoPage()
    

    then I need to use TempData (aka Session) to send state/messages across round trips?!

  • Ismail Mayat 4511 posts 10090 karma points MVP 2x admin c-trib
    Aug 21, 2013 @ 10:29
    Ismail Mayat
    0

    Andrew,

    You will but that data will be lost as the model is recreated which is http://issues.umbraco.org/issue/U4-1339

    Regards

    Ismail

  • Sebastiaan Janssen 5045 posts 15477 karma points MVP admin hq
    Aug 21, 2013 @ 11:24
    Sebastiaan Janssen
    0

    @Ismail The issue you're referring to has to do with ModelState not TempData

    So yes, when redirecting use TempData (as usual in MVC):

    TempData.Add("CustomMessage", "Your form was successfully submitted at " + DateTime.Now);            
    return RedirectToCurrentUmbracoPage();
    

    In your view (remember, you can only read TempData ONCE, so might be wise to put it in a variable):

    <p>Message will appear here: @TempData["CustomMessage"]</p>
    
    @Html.Action("ShowCommentForm", "Comments")
    
  • andrew shearer 506 posts 653 karma points
    Aug 21, 2013 @ 11:34
    andrew shearer
    0

    "(as usual in MVC)"

    I think my point/question has always been in regards to having to redirect with RedirectToCurrentUmbracoPage(). Why is this compulsory in Umbraco MVC? The PRG pattern is optional in a standard MVC website, and having to use Session definitely doesn't sound like convention. Or is it?

  • Sebastiaan Janssen 5045 posts 15477 karma points MVP admin hq
    Aug 21, 2013 @ 12:01
    Sebastiaan Janssen
    0

    @Andrew.. you don't HAVE to use RedirectToCurrentUmbracoPage. But you need to do something, you can't just return a view because we're in an Umbraco context, we need to "go to a page" somehow so that the view knows about the currentpage it's running on and can do querying on it. Also make sure to read up on "Understanding the routing process":
    http://our.umbraco.org/documentation/reference/mvc/forms

    If you want to use "pure" MVC you could consider hijacking routes:
    http://our.umbraco.org/documentation/reference/mvc/custom-controllers

  • andrew shearer 506 posts 653 karma points
    Aug 21, 2013 @ 12:09
    andrew shearer
    0

    yep, I appreciate I'm working in an Umbraco context, and also could use a custom/hijacked route instead.

    The "something" I want to do is stay on the current umbraco page:

        [NotChildAction]
        [HttpPost]
        public ActionResult AddComment(AddCommentViewModel addCommentViewModel)
        {
            //model not valid, do not save, but return current umbraco page
            if (!ModelState.IsValid)
                return CurrentUmbracoPage();
    
            //persistence
            SaveAndPublishCatWallComment(addCommentViewModel);
    
            ViewBag.CommentSuccess = true;
    
    
            return CurrentUmbracoPage();
        }
    

    but when doing this "ViewBag.CommentSuccess" is null in the umbraco view i try to use it in.

    I'll keep thinking about this further before replying again in case i'm missing something obvious :) thanks for the help so far!

  • Sebastiaan Janssen 5045 posts 15477 karma points MVP admin hq
    Aug 21, 2013 @ 12:12
    Sebastiaan Janssen
    0

    I think I've suggested TempData this a few times now.. ;-)

    [NotChildAction]
    [HttpPost]
    public ActionResult AddComment(AddCommentViewModel addCommentViewModel)
    {
        //model not valid, do not save, but return current umbraco page
        if (!ModelState.IsValid)
            return CurrentUmbracoPage();
    
        //persistence
        SaveAndPublishCatWallComment(addCommentViewModel);
    
        TempData.Add("CommentSuccess", true);
    
    
        return CurrentUmbracoPage();
    }
    
  • andrew shearer 506 posts 653 karma points
    Aug 21, 2013 @ 12:15
    andrew shearer
    0

    Edit: to confirm, yes I have tried TempData and it works as per the Umbraco examples.

    yes, but by NOT navigating away i.e. using CurrentUmbracoPage instead of RedirectToCurrentUmbracoPage, then I shouldn't need to use Session (aka TempData) and therefore ViewData/ViewBag should be available in Views. Unless the umbraco pipeline is preventing this for some reason (bug).

  • andrew shearer 506 posts 653 karma points
    Aug 22, 2013 @ 02:30
    andrew shearer
    0

    In summary (for anyone reading back through this thread later trying to make sense of me)... :o)

    I think my issue is trying to house all of the form “process flow” under a single content-managed Umbraco url. Maybe that’s the part that’s not ideal and where all the confusion/ambiguity arises i.e. Not all Routes need to fall under the guise of Umbraco content pages, if you have specific processes in your website then these can have their own dedicated Routes, that's more than acceptable. You can always use hijacked routes to relate these back to CMS template views if there’s content-managed aspects, and there inevitably always is.

    Using the earlier “Comments Wall” and “Add Comment” as examples I would suggest this as a typical scenario:

    1. A Template View that incorporates a form. (Can be included via Partial or Action, although I believe Action is semantically correct).

      Eg. Url: /comments-wall

    2. This “BeginUmbracoForm” form HttpPosts to an Umbraco Surface controller action.
    3. The surface controller action will return CurrentUmbracoPage() until validation passes.
    4. After validation is successful, the appropriate business logic is performed.
    5. The final step of RedirectToAction of my defined route. This route is wired up to a system defined content page (Umbraco View) used to display a summary. In my example, a confirmation message and a list of the latest comments added.

      Eg. Url: /comments/success

    This achieves these goals:

    • PRG pattern as per conventional MVC application

    • removes the dependency on TempData (Session)

    happy days

  • Shannon Deminick 1525 posts 5271 karma points MVP 2x
    Aug 23, 2013 @ 09:59
    Shannon Deminick
    0

    So in the grand scheme of things you just don't want to use TempData to display stuff right ?

    You can just use CurrentUmbracoPage if you don't want to redirect, like good old webforms, this just means people can resubmit your form by doing F5 then you can just use ViewData to store whatever information you are trying to pass. 

  • andrew shearer 506 posts 653 karma points
    Aug 30, 2013 @ 02:14
    andrew shearer
    0

    Yeah it was just a personal aversion to using TempData, although im sure its not an inefficient data structure to use these days?

    You can just use CurrentUmbracoPage if you don't want to redirect, like good old webforms, this just means people can resubmit your form by doing F5 then you can just use ViewData to store whatever information you are trying to pass.

    I mentioned above that I tried this but couldn’t get ViewData/ViewBag to work under Umbraco MVC, although that was in 6.13 and i haven't tried in 6.1.4 yet.

  • Shannon Deminick 1525 posts 5271 karma points MVP 2x
    Aug 30, 2013 @ 03:02
    Shannon Deminick
    0

    Hi Andrew,

    You should really just use TempData when redirecting, yes it uses Session but that is ok, if you move to LB environments you can just use the SQL session state provider. I've fixed up a few issues relating to this though, see:

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

    ViewData has always worked when not redirecting so you can always use that instead and should solve all your issues. Some people forget though that if you are trying to access ViewData in a child action you need to reference it from the Parent view context... which is why you may have though that ViewData wasn't working. See the comments in that issue.

    I'm going to create a nice attribute today that you can use on child actions: [MergeParentContextViewData] which will automatically merge in the parent view context's view data to the child action (but keeping all custom keys in the child action ViewData). This will automatically be applied to the macro controller that executes for partial view macros.

Please Sign in or register to post replies

Write your reply to:

Draft