Copied to clipboard

Flag this post as spam?

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


  • Jan A 59 posts 264 karma points
    Nov 08, 2023 @ 15:04
    Jan A
    0

    Maintain extended ViewModel from RenderController after SurfaceController

    What I want to do is to have a form on a page that is submitted to a surfaceController. If it fails I want to go back to the original page with the form still intact with the data the user posted. I want to use stongly typed models and a viewmodel for the page itself and not carry data by ViewBag etc. So this is what I got so far

    I use model builder to produce typed models for my view, but I also use a RenderController to make a typed viewmodel and load some additional data about the user

    So what I have is

    OrderContactInfoPage // generated PublishedContentModel
    
    // my viewmodel for the page that extends the contentmodel with some
    public class OrderContactInfoModel : OrderContactInfoPage {
        public OrderContactInfoModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback)
        {
        }
    
        public OrderContactInfoForm Form { get; set; }
    }
    
    // a class to store form information
    public class OrderContactInfoForm
    {
        public string FirstName { get; set;}
    }
    

    What I do is that when the RenderController for the OrderContactInfoPage is hit, I create the extended class and populate a OrderContactInfoForm with some basic information about the user

    public async Task<IActionResult> OrderContactInfoPage(OrderContactInfoPage pageModel)
        {
            var model = new OrderContactInfoModel(pageModel, new PublishedValueFallback(serviceContext, variationContextAccessor));
    
            model.Form = new OrderContactInfoForm() { 
                FirstName = "Testname" 
            };
    
            return View(model);
        }
    

    On the view I then have this

    // I do inherit from the extended class
    @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<OrderContactInfoModel>
    
    @using (Html.BeginUmbracoForm<OrderWizardController>(nameof(OrderWizardController.ContactInformation)))
    {
    <input name="FirstName" type="text" value="@Model.Form.FirstName" />
    <button type="submit" class="btn btn-primary">Lägg in din order</button>
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    }
    

    This is to post the form to a SurfaceController (OrderWizardController)

        [HttpPost]
        public async Task<IActionResult> ContactInformation(OrderContactInfoForm formData)
        {
            // here I have the formData loaded from the form on the view
            // I also noticed that the CurrentPage is not the extended, but the "base" class for the page
    
            return CurrentUmbracoPage();
        }
    

    I can recieve the data and post the form. But if something isn't valid I would like the user to be displayed the page with the form data once again to fill in any missed field

    When i make the return CurrentUmbracoPage the form is reseted (since the RenderController just populates the basic info. I also see that CurrentPage on the SurfaceController is not the extended one.

    How should I go about to make this?

  • Huw Reddick 1749 posts 6114 karma points MVP c-trib
    Nov 08, 2023 @ 16:47
    Huw Reddick
    0

    Your viewmodel for the rendercontroller should inherit PublishedContentWrapped similar to below

    public class VerifyViewModel : PublishedContentWrapped
    {
        public IMember ValidatedMember;
        public string NewPassword;
        public string ConfirmPassword;
        public string ResetToken;
        public string MemberId;
        public VerifyViewModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback)
        {
        }
    }
    
  • Jan A 59 posts 264 karma points
    Nov 09, 2023 @ 07:44
    Jan A
    0

    Even if I do inherit from PublishedContentWrapped the surfacecontroller still has the OrderContactInfoPage as CurrentPage, not the OrderContactInfoModel, so I don't see how this would make the form data persistant?

  • Huw Reddick 1749 posts 6114 karma points MVP c-trib
    Nov 09, 2023 @ 14:04
    Huw Reddick
    0

    In my render controller method I do the following

        VerifyViewModel viewModel = new VerifyViewModel(CurrentPage,
            new PublishedValueFallback(_serviceContext, _variationContextAccessor))
        {
            ValidatedMember = null
        };
        return CurrentTemplate(viewModel);
    

    notice it is being passed CurrentPage and returning using CurrentTemplate

  • Jan A 59 posts 264 karma points
    Nov 09, 2023 @ 14:14
    Jan A
    0

    Yeah, the RenderController works fine (both with my original and your version) but when the SurfaceController is called that does a postback to same page, but hitting the ContactInformation the CurrentPage isn't the wrapped instance.

    So if the validation of data failes I want to go back to original page with a model that is populated with the data user already entered.

    In my example the user change the FirstName to "Huw" and post it. In OrderWizardController (SurfaceController) I get the "Huw" in my formData (thats what is wrapped) but CurrentPage is a OrderContactInfoPage.

    So when I do return CurrentUmbracoPage() all the wrapped data is lost and "Huw" is reseted back to "Testname"

        [HttpPost]
        public async Task<IActionResult> ContactInformation(OrderContactInfoForm formData)
        {
            var model = CurrentPage as OrderContactInfoPage;
    
            // validate form
    
    
            // go to next if availabe
    
            return CurrentUmbracoPage();
        }
    
  • Huw Reddick 1749 posts 6114 karma points MVP c-trib
    Nov 09, 2023 @ 18:09
    Huw Reddick
    0

    What umbraco version are you using? I will do some tests tomorrow

  • Jan A 59 posts 264 karma points
    Nov 09, 2023 @ 20:52
    Jan A
    0

    I'm running version 12.2.0

  • Marc Goodson 2142 posts 14345 karma points MVP 8x c-trib
    Nov 09, 2023 @ 18:28
    Marc Goodson
    0

    Hi Jan

    At this point, when your SurfaceController handles the PostAction, your 'RenderController' hasn't yet run...

    (they were called Surface Controllers because they sit on the Surface of the RenderControllers... )

    Because you used BeginUmbracoForm, a hidden token will have been submitted along with the data, which lets Umbraco know which page the form was posted from.

    So in your SurfaceController action when you return CurrentUmbracoPage() this then returns you back to your GET route, and your RenderController will run, but ModelState will remain entact so you'll be able to see any validation errors on your form.

    If you return RedirectToCurrentUmbracoPage, then the page the form is on will be reloaded, your RenderController will run, but modelstate will be emptied...

     [HttpPost]
            public IActionResult PostMethod()
            {
                if (!ModelState.IsValid)
                {
                    return CurrentUmbracoPage();
                }
    
                return RedirectToCurrentUmbracoPage();
            }
    

    But basically in the context of your SurfaceController because the RenderController hasn't run yet, the underlying CurrentPage is the Modelsbuilder/IPublishedContent one... and not yet your enriched viewmodel.

    regards

    Marc

  • Jan A 59 posts 264 karma points
    Nov 09, 2023 @ 20:52
    Jan A
    0

    Thanks for reply and this is what I thought. I notice now since I set the data in the RenderController, maybe it would be overwritten when it's run again, so I changes things up to verify this and I still can't get it to work

    The surface controller is what you suggested, just returning CurrentUmbracoPage();

        [HttpPost]
        public async Task<IActionResult> ContactInformation()
        {
            return CurrentUmbracoPage();
        }
    

    Then I have the rendercontroller

     public async Task<IActionResult> OrderContactInfoPage(OrderContactInfoPage pageModel)
        {
            var model = new OrderContactInfoModel(pageModel, new PublishedValueFallback(serviceContext, variationContextAccessor));
    
            return View(model);
        }
    

    and my wrapped viewmodel

    public class OrderContactInfoModel : OrderContactInfoPage
    {
        public OrderContactInfoModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback)
        {
        }
    
        public string FirstName { get; set; }
    }
    

    Then I have the view

    @using (Html.BeginUmbracoForm<OrderWizardController>(nameof(OrderWizardController.ContactInformation)))
    {
    <label asp-for="FirstName"></label>
    <input asp-for="FirstName" />
    
    <button type="submit" class="btn btn-primary">Submit</button>
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    }
    

    This test does nothing with the data and I was expecting since I return CurrentUmbracoPage() that if I write "MyName" into the FirstName input field, the form should keep that data, but it's cleared and set to null.

    Edit: reading my post I saw that I did not extend the PublishedContentWrapped class above. I did try this to, without success

Please Sign in or register to post replies

Write your reply to:

Draft