Copied to clipboard

Flag this post as spam?

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


  • zimmertyzim 15 posts 105 karma points
    Feb 08, 2024 @ 22:41
    zimmertyzim
    0

    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.

    Any help would be much appreciated.

  • Marc Goodson 2155 posts 14406 karma points MVP 9x c-trib
    Feb 09, 2024 @ 08:53
    Marc Goodson
    0

    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

  • zimmertyzim 15 posts 105 karma points
    Feb 09, 2024 @ 10:01
    zimmertyzim
    0

    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.

  • Huw Reddick 1929 posts 6697 karma points MVP 2x c-trib
    Feb 09, 2024 @ 12:39
    Huw Reddick
    1

    Yes, duplicate them in the viewmodel

  • Steve Morgan 1348 posts 4457 karma points c-trib
    Feb 09, 2024 @ 13:03
    Steve Morgan
    1

    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.

     @Html.TextBoxFor(m => m.Form.Username, new { @id="Username", @class = "username", placeholder = "Email", tabindex = "1" })
    

    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?

Please Sign in or register to post replies

Write your reply to:

Draft