BeginUmbracoForm not working
# help-with-umbraco
c
Umb 10.6.1 Two questions here:.... 1) In a ViewComponent I have a form starting with:
Copy code
@using (Html.BeginUmbracoForm<MyFormSurfaceController>(nameof(MyFormSurfaceController.Submit))) {
....theForm....
}
It's on page called "test". However, when I look at the HTML produced it's just:
<form action="/test/" enctype="multipart/form-data" id="form796f5d50fb3440b4b4c611bbe6f4e54d" method="post">
and never hits the Surface Controller, for obvious reasons. However, if I use
Copy code
@using (Html.BeginForm("Submit", "MyFormSurface", FormMethod.Post)) {
...theForm...
}
The HTML is:
<form action="/umbraco/surface/myformsurface/Submit" method="post">
And the Surface Controller is hit. So what's wrong with BeginUmbracoForm or shouldn't it be used in a ViewComponent? 2) However, if the model is invalid I then
return CurrentUmbracoPage()
and I get the error:
InvalidOperationException: Can only use UmbracoPageResult in the context of an Http POST when using a SurfaceController form
What should I do to return to the form page? Thanks.
h
Iirc this is a .net bug because you are using nameof
c
Interesting because those are the instructions in the docs:- https://docs.umbraco.com/umbraco-cms/fundamentals/code/creating-forms
If the model is valid and I let it run through to
return RedirectToCurrentUmbracoPage()
then I get:-
InvalidOperationException: No UmbracoRouteValues feature was found in the HttpContext
Not sure what using BeginUmbracoForm gives you over plain old BeginForm tbh.
h
It automatically adds the validation key etc
c
Ah, ok. Be nice to know now to use it then 😉
s
Sorry to be the channel police again but #1127946593786675331 is the place for Umbraco Forms questions.
c
Sorry to argue back, but this isn't about using the UmbracoForms package. It's specifically about building a bespoke form using a ViewComponent and Surface Controller. 😉
s
You're totally correct, misread, sorry!
j
> and never hits the Surface Controller, for obvious reasons. Except, it's not obvious... Surface controllers using
BeginUmbracoForm
post back to the current page and are routed internally. Following the example from the docs, on a fresh site, renders this for me (it's on my homepage)
Copy code
html
<form action="/" enctype="multipart/form-data" id="form2e825402a9af4a058dca5849724c4b25" method="post">
...
</form>
But my surface controller gets hit just fine
AFAIK it's this... behaviour... that makes this kind of form/controller combo a "Surface Controller" as opposed to regular ol' MVC Controller.
So... There must be something else going on that is preventing your form from posting to the correct controller.
c
To me, the "obvious reason" was that it was routing to /test/ and not to a Surface Controller. But you're probably right. I converted to BeginForm it hits the Surface Controller, does it's stuff and then blows up when return RedirectToCurrentUmbracoPage or return CurrentUmbracoPage is called 😦 The form's ViewController View is rendered as a BlockGrid block, so not sure if that's part of the issue.
Yes, but what was it you put in the View to make it output that HTML and hit your Submit action on that particular SurfaceController? I just followed the instructions and it doesn't work, so if I can find out why it failed I can update the docs 🙂
j
Oh, exactly what's in the docs. Literally just copied and pasted it.
c
Then it must be to do with the fact that the form is in a block grid block. Can't see it being anything else. It's not a complex page. It only had the form on it. The error is a 400.
One more question..... Did you try it in a View Component or just directly off a Razor file?
j
Directly in a template. I feel like there should be no reason for a surface controller to not to work in a View Component or in block grid etc. Not at a computer at mo so can't check.
c
That's what I thought. But it seems otherwise. 😦
h
I had problems with forms in viewcomponents, I ended up overriding the form action with "/" then everything worked fine
Copy code
csharp
@using (Html.BeginUmbracoForm<MembershipSurfaceController>("SubmitSigninForm", null, new { id = "submitsigninform", @action = "/" }))
{

}
controller method
Copy code
csharp
        [HttpPost]
        [ValidateUmbracoFormRouteString]
        [IgnoreAntiforgeryToken]
        public IActionResult SubmitSigninForm(PortalSigninViewModel model)
        {
          ...
         }
My View
Copy code
cs
@{
    Layout = "_LayoutBody.cshtml";
    var redirect = TempData["Redirect"];
    var result = TempData["FormResult"];
}

@await Html.CachedPartialAsync("Layout/_pageBanner", Model, TimeSpan.FromHours(1),true)
<section our-if="@result != null">
<div class="container text-center " >
        @result
    </div>
</section>
<section our-if="@result == null">
    <div class="container">
        <div class="row">
            <div class="col-6 offset-3">
                @(await Component.InvokeAsync("Membership", new { viewname = "Signin" }))
            </div>
        </div>
    </div>
</section>
<script type="text/javascript" our-if="@redirect != null">
    setTimeout(() => {
        location.href = "@redirect";
    }, 500);
</script>
c
Isn't that weird?! I'll give that a go. I presume it reenables CurrentUmbracoPage etc. or is that what you used TempData for, because it didn't?
h
I'm sure I have also used CurrentUmbraco page too, will do some digging (too many projects on the go 🤣)
c
It works! BUT.... because of the action being set to "/" return CurrentUmbracoPage() returns to the home page.
It's probably worth a note in the "Creating Forms" docs. It would have saved me a day, lol
h
mmm, I just tested another viewcomponent, this one doesn't reset the action and it works fine! (U10.6.1)
this is the form def in the viewcomponent
Copy code
@using (Html.BeginUmbracoForm<PortalProfileSurfaceController>("SubmitForm", null, new { }, FormMethod.Post))
This is the controller method
Copy code
cs
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult SubmitForm(ProfileModel model)
        {

            if (ModelState.IsValid)
            {
                var currentUser = _membermanager.GetCurrentMemberAsync().Result;
                var member = _memberservice.GetByUsername(currentUser.UserName);
                member.SetValue("firstname", model.Firstname);
                member.SetValue("lastname", model.Lastname);
                member.Email = (model).Email;
                member.SetValue("mobile", (model).Mobile);
                _memberservice.Save(member);

                if (!string.IsNullOrEmpty(model.Password) && model.Password == model.ConfirmPassword)
                {
                    var token = _membermanager.GeneratePasswordResetTokenAsync(currentUser).Result;
                    _membermanager.ChangePasswordWithResetAsync(member.Id.ToString(), token, model.Password);
                    _memberservice.Save(member);
                }
                

                new Contacts( _httpContext,_crmWebClient,_sess).Update( model.Firstname, model.Lastname, model.Email, model.Mobile);

                return RedirectToCurrentUmbracoPage();
            }

            return CurrentUmbracoPage();
        }
c
That is a thing of beauty, even if undocumented. It works! I'll see about rewording the docs later today if I get time. Thanks very much 🙂