Search code examples
c#asp.net-mvcroutesasp.net-mvc-5asp.net-routing

ASP.NET MVC model nesting routes


Consider an ASP.NET MVC application with two models: let's say Company and Person. Each company has a list of persons. Each person belongs to one company only.

If you set up the model and use Visual Studio to generate the controllers/views, you get the ability to edit the companies at /Company/{id} etc. and the ability to edit the persons at /Person/{id} etc.

But I want it to be such that you can only add a person inside a company, i.e. you would edit the persons at /Company/{id}/Persons/{id}.

How can I set up this sort of routing in ASP.NET MVC 5?


EDIT:

So I did this in my routes:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        
routes.MapRoute(
    name: "CompanyPerson",
    url: "Company/{CompanyId}/Person/{PersonId}/{action}",
    defaults: new { controller = "Person", action = "Index", PersonId = UrlParameter.Optional }
);

routes.MapRoute(
    name: "Company",
    url: "Company/{id}/{action}",
    defaults: new { controller = "Company", action = "Index", id = UrlParameter.Optional }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/",
    defaults: new { controller = "Home", action = "Index" }
);

but it still isn't working. If I go to /Company/7/Person I get an index of persons, but /Company/7/Person/Create just gives the same index, and the "Create New" link points to /Person/Create instead of /Company/7/Person/Create

Is there just a way to set up all the routes explicitly, like Node or most other MVC frameworks?


Solution

  • Well, it doesn't work the way you're thinking. The reason is that you have two different controllers, Company and Person. You can't call both of them at the same time.

    You are going to have to decide whether you want to edit a person at the Person controller, or edit them at the Company controller. Personally, I'd edit the person at the person level, but that will mean your Person.Edit method will have to take the companyId as well as the personId.

    So you would then create a route like this:

    routes.MapRoute(
        "CompanyPerson",                                // Route name
        "Company/{companyId}/Person/{personId}",        // URL with parameters
        new { controller = "Person", action = "Edit" }  // Parameter defaults
    );
    

    Then your Person.Edit method would look like this:

    public ActionResult Edit(int companyId, int personId) {}
    

    EDIT:

    You are going to have to use constraints to do what you want.

    routes.MapRoute(
        name: "CompanyPerson",
        url: "Company/{companyId}/Person/{personId}/{action}",
        defaults: new { controller = "Person", action = "Index", PersonId = UrlParameter.Optional },
        new {personId = @"\d+" }
    );
    

    However, this now means you can do /Company/7/Person/15/Create, and that just won't make any sense. The personId will simply get ignored.

    As for why your Create Link isn't working, is because of the id being before the action. You'll also need another route.

    routes.MapRoute(
        name: "CompanyPerson",
        url: "Company/{companyId}/Person/Create",
        defaults: new { controller = "Person", action = "Create" }
    );
    

    and this

    @Html.ActionLink("Create", "Person", new { companyId = Model.companyId })
    

    It would be a lot easier if you just stuck to the Id after the action. You also need to be careful of capitalizing variable names, as these are C# variables and are case sensitive.