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.
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)
{
}
}
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?
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();
}
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.
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; }
}
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
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
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
On the view I then have this
This is to post the form to a SurfaceController (OrderWizardController)
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?
Your viewmodel for the rendercontroller should inherit PublishedContentWrapped similar to below
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?
In my render controller method I do the following
notice it is being passed CurrentPage and returning using CurrentTemplate
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"
What umbraco version are you using? I will do some tests tomorrow
I'm running version 12.2.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...
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
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();
Then I have the rendercontroller
and my wrapped viewmodel
Then I have the view
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
is working on a reply...