2

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?

Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137
  • It is hitting this route template `"B2b/Shopify/{controller}/{action}/{id}"` – Nkosi Feb 22 '20 at 22:06
  • @Nkosi: The routes work as expected, I mean if I put the url: `B2b/Controller/Action/Id` in the browser, it would hit the correct controller... and if I put `B2b/Shopify/Controller/Action/Id` in the browser, it would hit the correct action inside the shopify sub-folder... but I don't know why `Html.BeginForm` always includes `shopify` subfolder in the url that it generates (when I use it inside the area of course) – Hooman Bahreini Feb 22 '20 at 22:33
  • When browsing sure it will work, BeginForm is generating a URL so that is different. Hold on posting an answer now – Nkosi Feb 22 '20 at 22:34

1 Answers1

2

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">
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • thanks a lot... I understood how to solve the problem. I am not sure if I understood your first sentence though, how is `BeginForm` form matching both conventions? Is it because I have defined 2 conventions in area registration? – Hooman Bahreini Feb 22 '20 at 22:41
  • Both of your area route conventions conflict with each other for URL generation. if I ask to generate a URL and I give the route table a controller, action, and area, which route would match first? – Nkosi Feb 22 '20 at 22:43
  • so it cannot figure out the correct convention based on the path where the form is located? that was my assumption... – Hooman Bahreini Feb 22 '20 at 22:48
  • 1
    @HoomanBahreini Nope. It relies on what you give. You could be posting to a form located outside of the area, so it does not assume you meant within the same location of the current form. It will only default to the current location if you do no provide route values. – Nkosi Feb 22 '20 at 22:49