Search code examples
c#asp.net-mvcasp.net-mvc-5asp.net-mvc-routingasp.net-mvc-areas

Html.BeginForm is automatically adding a sub-folder after the area


I have created an Area named B2b in my ASP.NET MVC application, and I have also created a sub-folder called Shopify under this area:

enter image description here

In order to register the Shopify sub-folder, I have created a CustomViewEngine as below (followed this tutorial):

public class ExpandedViewEngine : RazorViewEngine 
{
    public ExpandedViewEngine()
    {
        var extendedViews = new[] 
        {
            "~/Areas/B2b/Views/Shopify/{1}/{0}.cshtml",
        };

        var extendedPartialViews = new[]
        {
            "~/Areas/B2b/Views/Shopify/Shared/{0}.cshtml"
        };

        ViewLocationFormats = ViewLocationFormats.Union(extendedViews).ToArray();
        PartialViewLocationFormats = PartialViewLocationFormats.Union(extendedPartialViews).ToArray();
    }
}

And this is is my area registration code (I am using lowercase-dashed-route):

public class B2bAreaRegistration : AreaRegistration
{
    public override string AreaName
    {
        get
        {
            return "B2b";
        }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        // this route is for controllers and views inside Shopify sub-folder
        var shopifyDefaultRoute = new LowercaseDashedRoute(
            "B2b/Shopify/{controller}/{action}/{id}",
            new RouteValueDictionary(new { controller = "ProductMap", action = "Display", id = UrlParameter.Optional }),
            new DashedRouteHandler(),
            this,
            context,
            new[] { "Shopless.Web.Areas.B2b.Controllers.Shopify" }
        );
        context.Routes.Add("Shopify_default", shopifyDefaultRoute);

        // default area route which is not under Shopify subfolder
        var b2bDefaultRoute = new LowercaseDashedRoute(
            "B2b/{controller}/{action}/{id}",
            new RouteValueDictionary(new { action = "index", id = UrlParameter.Optional }),
            new DashedRouteHandler(),
            this,
            context,
            new[] { "Shopless.Web.Areas.B2b.Controllers" }
        );
        context.Routes.Add("B2b_default", b2bDefaultRoute);
    }
}

And I register the above in Global.asax:

protected void Application_Start()
{

    ViewEngines.Engines.Add(new ExpandedViewEngine());
    AreaRegistration.RegisterAllAreas();
    // more code ...
}

Everything works fine, except the following code:

@using (Html.BeginForm("update", "organisation", new { area = "B2b" }, FormMethod.Post))
{
    <input type="text" id="name" name="name">
}

Is generating the following HTML:

<form action="/b2b/shopify/organisation/update" method="post" novalidate="novalidate">
    <input type="text" id="name" name="name">
</form>

Notice that it is adding shopify after my area name, B2b. The above form is inside B2b area but it is not under shopify subfolder, so not sure why it is being added?


Solution

  • It is mapping to this route template "B2b/Shopify/{controller}/{action}/{id}" because it also matches the conventional values given to the BeginForm when generating the URL for the form.

    Both of the area route conventions conflict with each other for URL generation.

    If I ask the route table to generate a URL and I give it a controller, action, and area. Which route would match first given the following route templates within the same area

    • B2b/Shopify/{controller}/{action}/{id}

    • B2b/{controller}/{action}/{id}

    And since first match always win, it will map to the first one above, which would explain the current experience with your form.

    If you want to use a specific route to generate your URL for the Form use BeginRouteFormMethod

    @using (Html.BeginRouteForm(
        routeName: "B2b_default", 
        routeValues: new { action = "update", controller = "organisation", area = "B2b" }, 
        method: FormMethod.Post)
    )
    {
        <input type="text" id="name" name="name">
    }