Incorrect Form Routing, posting to the wrong controller

I have page which is loaded from controller MyFormController, which in turn loads a view component. This view component renders a form.

I need the form to submit to controller SecondaryFormController, but it always submits back to MyFormController instead.

I can see in the markup that the generated <form action="/"> outputs the route for MyFormController but I understand this to be normal behaviour. To my knowledge Umbraco will use the generated and hidden upfrt to decide where to route to. This is how it behaves on my other forms.

What’s happening here that triggers Umbraco to route to MyFormController rather than SecondaryFormController?

Here’s trimmed down version of the code:

MyFormController:

public class MyFormController: RenderController {
  public MyFormController() {}

  public override IActionResult Index() {

    var viewModel = new MyFormControllerViewModel() {
      ...
    }

    return CurrentTemplate(viewModel);
  }
}

The template for MyFormController.Index:

@inherits UmbracoViewPage < MyApp.MyFormControllerViewModel >
  @(await Component.InvokeAsync < MyApp.MyFormControllerMainViewComponent > ())

MyFormControllerMainViewComponent looks like:

public class MyFormControllerMainViewComponent: ViewComponent {
  public MyFormControllerMainViewComponent() {

  }

  public async Task < IViewComponentResult > InvokeAsync() {
    return View(new MyViewComponentViewModel())
  }
}

The view/template for MyFormControllerMainViewComponent:

@model MyApp.MyViewComponentViewModel
@using(Html.BeginUmbracoForm < MyApp.SecondaryFormController > (
      nameof(MyApp.SecondaryFormController.MyTest),
      "Update",
      null,
    ) {

      <
      button type = "submit" >
        Update <
        /button> <
        /div>
    }

SecondaryFormController.MyTest:

    public class SecondaryFormController: SurfaceController {
      public SecondaryFormController() {}

      [HttpPost]
      [ValidateAntiForgeryToken]
      [UnsupportedOSPlatform("browser")]
      public async Task < IActionResult > MyTest(MyViewComponentViewModel model) {
        // do something
        return CurrentUmbracoPage();
      }
    }

What’s happening here? I’ve been pulling my hair out for a while.

Is there a way a can decode/decrypt upfrt ?

Just bumping this for fresh eyes, as I remain no closer to fixing.

I think it might be how you’re constructing your BeginForm. I don’t think you need the “MyApp.” bit in @using(Html.BeginUmbracoForm <MyApp.SecondaryFormController> and the nameof bit doesn’t look quite right. Also, not too sure what "Update " is for as I think you’re trying to call the “MyTest” method?

Have you tried something like:

@using (Html.BeginUmbracoForm<SecondaryFormController>(c => c.MyTest(Model)))
{
    <button type="submit">Update</button>
}

or if you want to do it similar to how you have it there, something like:

@using (Html.BeginUmbracoForm<SecondaryFormController>(
      actionName: "MyTest",
      controllerName: "SecondaryForm",
      routeValues: new { /* any additional values */ }))
{
    @Html.AntiForgeryToken()
    <button type="submit">Update</button>
}

Might help get you on track, although I’ve not actually tried it with your example code.

Thanks, sadly it’s a no go.

The only thing I can think of at this point is that whatever route data is generated for upfrt is incorrect.

I thought maybe because my initial controller is a RenderController, which is messing up the context, as it’s that controller that returns the initial view and corresponding view component.

As a test, I removed the view component, and moved the form so its directly loaded via the view. Tt still posts back to MyFormController rather than SecondaryFormController.

If I bypass Umbraco and use a normal HTML form, it’ll post to the correct place, but I lose the Umbraco context which is no good.

I am clearly misunderstanding something fundamental here.

Just to add, this is actually v13.4.1

And to clarify my word salad

  1. Controller MyFormController which returns view MyMainView
  2. MyMainView calls MyFormControllerMainViewComponent and renders MyComponentView
  3. MyComponentView renders a form using UmbracoBeginForm
  4. The form needs to submit to SecondaryFormController – no matter what I do, it always posts back to MyFormController instead.

The only way to avoid this, is to not use UmbracoBeginForm, but I then lose context which I need.

Is there a reason Umbraco decides it must post back to MyFormController?

I tried to implement what I think you’re doing, it seems to be working:

After submitting the form it’s using the secondary controller:

Of course you will return to the MyFormController as it is a RenderController responsible for rendering the page after you submit the form, but the SurfaceController gets hit first. I threw an exception in it on purpose to show we were getting there.

The code is here:

Log in with [email protected] / test123456 and make a Document Type named ‘MyForm’ that’s allowed at root:

Create a root node of this type and go to the URL to test it out.

Note: I’ll admit, I tested this on 13.7.0 and you should definitely upgrade to 13.8.0 (the latest v13) but this will work on 13.4.1 as well.

Do note as well, that I removed null here, the parameter indicates htmlAttributes and can not be null.

@sebastiaan - Thanks.

So this must be down to model binding somehow. If I use my exact same setup, without any model values within the form, it’ll post correctly. The moment I have some actual form data (pulled in from a model) it’ll post back to the wrong controller.

@model MyApp.MyModel

This works, and posts back to SecondaryFormController as expected:

@using (Html.BeginUmbracoForm<MyApp.SecondaryFormController>(nameof(MyApp.SecondaryFormController.Update), "Update"))
{
    <button type="submit">Submit</button>
} 

However, if I add some form data, it posts back to MyFormController.

@using (Html.BeginUmbracoForm<MyApp.SecondaryFormController>(nameof(MyApp.SecondaryFormController.Update), "Update"))
{
    <div>
        @Html.HiddenFor(m => m.SomeHiddenField1)
        @Html.HiddenFor(m => m.SomeHiddenField2)
        @Html.HiddenFor(m => m.SomeHiddenField3)
         
        <div>
                @Html.DropDownListFor(m => m.MyDropDownList)
        </div>

        <div>
            <h3>TextBox:</h3>
            @Html.TextAreaFor(m => m.MyTextBox)
        </div>

        @if (Model.OtherDetails != null)
        {
            <p>@Model.OtherDetails</p>
        }
        </div>

        <button formnovalidate name="@Html.NameFor(m => m.Action)"
                value="@MyApp.SecondaryFormController.ActionType.Cancel">
            Back
        </button>

        <button name="@Html.NameFor(m => m.Action)"
                value="@MyApp.SecondaryFormController.ActionType.Submit">
            Update
        </button>
    </div>
}

Maybe!

So just to be clear: when you submit the form, it will go to the SurfaceController, then the page either reloads or redirects back to the current page, that’s why you think it only hits the MyFormController but MyFormController is just the controller that’s responsible for providing data to the page that reloads or gets redirected to.

Anyway, I’ve updated the repo with a working sample:

Submitting a value for the dropdown:

Shows which value was selected in the exception I throw (on purpose, just to prove that I’ve captured a value in the Submit action):

An error message states that an unhandled exception occurred while processing a request, specifically a NotImplementedException indicating that the submitted value for the MyDropDownList property is 1. (Captioned by AI)

I’ve found the problem, and it’s a stark reminder of why detail matters.

I have a second view component which renders another form. The problem being, it tries to render a form within a form (which is invalid HTML for a start). if I remove this, all areas of the form work and submit as expected. I wasn’t seeing this, as the resulting markup didn’t include the second form in full, although does add the ufprt field. I figure Umbraco was falling over with confusion here,

I was trying to keep code sample as simple as possible, had I been a touch more detailed someone with fresh eyes would’ve spotted this right away.

A touch embarrassing to say the least. This is not an Umbraco problem, but a me problem, well I’ll blame the original developer!

Thanks for your help.

1 Like

I thought something like that might be happening. This is basic functionality that has been in Umbraco for ages and ages so I would be surprised if we broke it in a version from months ago. Glad you got it to work now!

For future problems, I can recommend spinning up a small sample of the thing you’re trying to get to work in a clean solution, that usually helps me spot the differences.

As noted before, please do upgrade to v13.8.0, many bugfixes and improvements since your version from July last year!

https://our.umbraco.com/download/releases/compare?from=13.4.1&to=13.8.0

Glad you found the problem!