Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC Areas Routing - Controller Folder with Folder

Currently I have a folder structure like this:

Area (folder)
 - Toolkit (folder)
    - Controllers (folder)
        - AdminController.cs
    - Views (folder)
        - Admin (folder)
           - Privledges (folder)
              - Create.cshtml
              - Edit.cshtml
              - Delete.cshtml

Which translates to

/Toolkit/{controller}/{action}/{tool}/{id}

Is it a bad practice to set up the action to behave a like a controller that serves up a view based on the string {tool} parameter and parameter {id} passed to the action?

The implementation of what I am talking about:

    private const string FOLDER_PRIVILEGES = "./Privileges/";

    public ActionResult Privileges(string tool, string id = "")
    {
        dynamic viewModel = null;
        ToolViews view; // enum for the views
        // Parse the tool name to get the enum representation of the view requested
        bool isParsed = Enum.TryParse(tool, out view);

        if (!isParsed)
        {
            return HttpNotFound();
        }

        switch (view)
        {
            case ToolViews.Index:
                viewModel = GetIndexViewModel(); // call a function that gets the VM
                break;
            case ToolViews.Edit:
                viewModel = GetEditViewModelById(int.Parse(id)); // sloppy parse
                break;
            default:
                viewModel = GetIndexViewModel();
                break;
        }
        // The folder path is needed to reach the correct view, is this bad?
        // Should I just create a more specific controller even though it would
        // require making about 15-20 controllers?
        return View(FOLDER_PRIVILEGES + tool, viewModel);
    }

When I write a View, I have to make sure the Path name is used for the folder

@Html.ActionLink("Edit", "./Toolkit/Admin/Priveleges/Edit", "Admin", new { id = item.id })

This seems to be a poor practice, because if the folder structure changes at all it will require a lot of maintenance.

However, if I have to break out the actions into controllers there would be many of them (almost 20 with more added over time).

If what I am doing is a bad practice, what would be the best way to serve a route that looks like this?

/Toolkit/Admin/Privileges/Edit/1

I want to avoid doing the following:

/Toolkit/Admin/CreatePrivileges/1
/Toolkit/Admin/EditPrivileges/1
/Toolkit/Admin/DeletePrivileges/1

Please let me know if I'm not making any sense, because I am having a hard time putting this question into words.

like image 905
David East Avatar asked Nov 03 '22 11:11

David East


2 Answers

I think you're trying to force a convention into MVC that goes against its orignal intent.

With MVC, your controller is a noun, and your action is a verb. Using your examples, you have:

  • Toolkit (noun) - Area
    • Admin (noun?) - Sub-area? <-- this is the one that's a little funky
      • Privileges (noun) - Controller
        • Create (verb) - Action
        • Edit (verb) - Action
        • Delete (verb) - Action

So as you can see, if you can consider Toolkit + Admin as area + subarea, or combine them into one area (TookitAdmin), it would get you back to the original purpose for controllers and actions.

Based on the comments, it sounds like you may have decided to go this way. But I wanted to point out that the conclusion you came to in a round-about way is getting back to the roots of MVC.

As a side note, have you considered moving to MVC4? Its Web API provides better support for a RESTful API, which it sounds like you may be trying to get to.

like image 177
Jerad Rose Avatar answered Nov 15 '22 06:11

Jerad Rose


Not an answer to the original question, but the OP asked for a sample of an Enum constraint rather than having to check the enum in each action. ie:

// Parse the tool name to get the enum representation of the view requested
bool isParsed = Enum.TryParse(tool, out view);

if (!isParsed)
{
    return HttpNotFound();
}

Instead of having to accept the enum value (Tool, in this case) as a string, you can force the value to come into your action already cast as the appropriate enum. An added benefit to this is that the MVC framework will take care of returning the correct response (HttpNotFound) in this case.

This is your constrain method. It accepts any type of Enum. There is no need to create a separate constraint for each Enum.

public class EnumConstraint<T> : IRouteConstraint where T : struct
{
    private readonly HashSet<string> enumNames;
    public EnumConstraint()
    {
        string[] names = Enum.GetNames(typeof(T));
        this.enumNames = new HashSet<string>(from name in names select name.ToLowerInvariant());
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        return this.enumNames.Contains(values[parameterName].ToString().ToLowerInvariant());
    }

}

Then, in your RegisterRoutes method (MVC4) or global.asax.cs page (MVC3), you just register your route like this:

routes.MapRoute(
    url: "/Toolkit/Admin/{Action}/{id}",
    constraints: new { Action = new EnumConstraint<ToolViews>(), id = @"\d+" }
);

I also added a number constraint on the id parameter to save you having to parse that as well.

Let me know how that works out for you.

like image 20
Shai Cohen Avatar answered Nov 15 '22 06:11

Shai Cohen