How to POST to SurfaceController with a view model that inherits from content model.
The error is:
Cannot bind source content type MyApplication.Models.Login to model type MyApplication.Core.Models.ViewModels.LoginViewModel
My structure looks like this:
Login.html
@await Component.InvokeAsync("RenderLogin");
RenderLogin ViewComponent
LoginViewModel model = new(currentPage, new
PublishedValueFallback(_serviceContext, _variationContextAccessor));
return View("LoginForm.cshtml", model );
LoginForm.cshtml
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<LoginViewModel>
@using (Html.BeginUmbracoForm("Login", "Member", new {}, new {},
FormMethod.Post))
{
<input asp-for="@Model.Username" />
<input asp-for="@Model.Password" />
<button type="submit">Login</button>
}
MemberController (inherits from SurfaceController)
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult HowAboutThis(LoginViewModel model)
{
return CurrentUmbracoPage();
}
LoginViewModel
public class LoginViewModel : Login
{
...
public LoginViewModel(IPublishedContent content,
IPublishedValueFallback publishedValueFallback) :
base(content, publishedValueFallback)
{
}
}
Login is a content model from the back office, used to output content on LoginForm.
Using <form asp-action="Login" asp-controller="Member" method="POST"> hits the controller, but the model doesn't bind and CurrentUmbracoPage(); is not available.
Removing the model from the Login method works , but I need its content.
How should I do this? I guess it's due to LoginViewModel inheriting Login, but I understand this is okay to extend the view model.
It sounds like it's not binding because what is being posted (eg username and password) doesn't match the properties of the LoginViewModel, which has all the properties of the 'Login' Document Type, eg Name, Path ContentType etc, and you are not posting those...
... normally your model that you post to a surface controller doesn't inherit from the generated modelsbuilder model.
I like to think of the modelsbuilder models as readonly, a convenient way to get hold of properties set in Umbraco, but try to avoid using them when Posting data etc...
.. The way a SurfaceController works is quite weird, in a way, in sits on top of the MVC Controller (on it's surface) so it doesnt' need to have the full model of the page, when you return CurrentUmbracoPage, that is handing back the execution to the underlying MVC Controller which builds the model to display the template.
... in short, I think if you 'just' make your LoginViewModel have two properties, Username + Password, and keep it plain, eg not inherit from anything else, then you should be able to receive the postback in the surface controller without any modelbinding errors...
... do your thing with the username and password, and either return CurrentUmbracoPage if there is a validation error, or redirect the person to the protected area of the site if it is successful.
I sort of figured that's why. It was actually the inherited class, the other properties (username, password) on the model are fine. So once the back office model is removed, everything works.
In this case, I've just dropped the inheritance and and rejigged the markup, as the data from the back office can sit outside of BeginUmbracoForm.
But let's say in the future I have content that needs to be displayed within the form. How am I supposed to do that?
Is it just a case of building the duplicating the properties I need on the ViewModel and populating them in the ViewComponent? Or is there a better way?
I tend to do this now with my ViewModel that is going to be used on a block list view.
public class ContactEnquiryBlockViewModel
{
public bool IsLoggedIn { get; set; }
public ContactSubmission Form { get; set; }
public string ButtonTitle { get; set; }
public string AlreadyLoggedInTitle { get; set; }
}
"Form" contains just my form model that I'm going to post back to a surface controller.
I output the form parameters with the form suffix - e.g.
This will then generate input field names like "Form.Username" - c#'s auto binding stuff can sometimes cause more issues that it solves... but you can just decorate the surface controller POSTed model with this bind prefix ([Bind(Prefix = "Form")]) so it binds correctly:
[HttpPost]
[ActionName("ContactEnquiryBlock")]
public IActionResult ContactEnquiryBlockPost([Bind(Prefix = "Form")] ContactSubmission model)
No reason why you couldn't do the same in the page controller and add a form to a page view model?
How to POST to SurfaceController with a view model that inherits from content model.
The error is:
My structure looks like this:
Login.html
RenderLogin ViewComponent
LoginForm.cshtml
MemberController (inherits from SurfaceController)
LoginViewModel
Login
is a content model from the back office, used to output content on LoginForm.Using
<form asp-action="Login" asp-controller="Member" method="POST">
hits the controller, but the model doesn't bind andCurrentUmbracoPage();
is not available.Removing the model from the Login method works , but I need its content.
How should I do this? I guess it's due to
LoginViewModel
inheritingLogin
, but I understand this is okay to extend the view model.Any help would be much appreciated.
Hi zimmertyzim
It sounds like it's not binding because what is being posted (eg username and password) doesn't match the properties of the LoginViewModel, which has all the properties of the 'Login' Document Type, eg Name, Path ContentType etc, and you are not posting those...
... normally your model that you post to a surface controller doesn't inherit from the generated modelsbuilder model.
I like to think of the modelsbuilder models as readonly, a convenient way to get hold of properties set in Umbraco, but try to avoid using them when Posting data etc...
.. The way a SurfaceController works is quite weird, in a way, in sits on top of the MVC Controller (on it's surface) so it doesnt' need to have the full model of the page, when you return CurrentUmbracoPage, that is handing back the execution to the underlying MVC Controller which builds the model to display the template.
... in short, I think if you 'just' make your LoginViewModel have two properties, Username + Password, and keep it plain, eg not inherit from anything else, then you should be able to receive the postback in the surface controller without any modelbinding errors...
... do your thing with the username and password, and either return CurrentUmbracoPage if there is a validation error, or redirect the person to the protected area of the site if it is successful.
regards
Marc
Thanks Marc.
I sort of figured that's why. It was actually the inherited class, the other properties (username, password) on the model are fine. So once the back office model is removed, everything works.
In this case, I've just dropped the inheritance and and rejigged the markup, as the data from the back office can sit outside of BeginUmbracoForm.
But let's say in the future I have content that needs to be displayed within the form. How am I supposed to do that?
Is it just a case of building the duplicating the properties I need on the ViewModel and populating them in the ViewComponent? Or is there a better way?
Hopefully this makes sense.
Yes, duplicate them in the viewmodel
I tend to do this now with my ViewModel that is going to be used on a block list view.
"Form" contains just my form model that I'm going to post back to a surface controller.
I output the form parameters with the form suffix - e.g.
This will then generate input field names like "Form.Username" - c#'s auto binding stuff can sometimes cause more issues that it solves... but you can just decorate the surface controller POSTed model with this bind prefix ([Bind(Prefix = "Form")]) so it binds correctly:
No reason why you couldn't do the same in the page controller and add a form to a page view model?
is working on a reply...