Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding / Extending an MVC Controller / Area

I'm currently working on an MVC project and I'm trying to figure out how I might go about extending the routes of an existing Controller within an Area, specifically from another project.

For instance, I have a Controller with an area that looks like the following :

namespace MyProject.Areas.Foo.Controllers
{
    [Authorize]
    public class FooController : ApplicationController
    {
          //code
    }
}

And what I would like to do, is be able to define another Controller, within a separate project that could extend this like so :

namespace MyOtherProject.Areas.Foo.Custom.Controllers
{
    public class FooController : ApplicationController
    {
          public string Bar()
          {
               return "Bar";
          }
    }
}

Basically, I would like the controllers to almost function as if I was using the partial keyword (so that I could call any of the actions in the original or the new one).

The Main Problem

What I am really trying to accomplish is that I have a main project with several areas and another area of my solution with various client folders. I want to be able to essentially extend the base controllers for my main project and add client-specific actions within these client folders so that they can be used in the main project. I'm already doing this with certain MVC Views, but I was hoping I could accomplish it with controllers as well.

What I've tried

  • I tried using the partial keyword on both declarations of the class, but since they are in different projects / assemblies, I don't think that works.
  • I defined a build event that would move the custom DLL into the bin directory of the main MVC project, but that didn't seem to work as expected.
  • I've tried various approaches for inheritance, hoping the new class would get picked up, but those didn't work (received the duplicate controller declaration error).
  • I've read about trying to use a custom ControllerFactory but I wasn't sure how to implement it.
  • I've tried defining custom namespace routing parameters in the AreaRegistration section to pick up the new controller like the following example.

Routing Example (AreaRegistration)

context.MapRoute(
    AreaName,
    String.Format("{0}/{{action}}/{{id}}", AreaName),
    new { controller = AreaName, action = "Index", id = UrlParameter.Optional },
    new[] { 
        String.Format("MyProject.Areas.{0}.Controllers", AreaName),
        String.Format("MyOtherProject.Areas.{0}.Custom.Controllers", AreaName)
    }
);

Update

I attempted an approach seen here as per some of the comments discussion that involved simply handling this via inheritance :

// Main Project
namespace MyProject.Areas.Foo.Controllers
{
    [Authorize]
    public class FooController : ApplicationController
    {
          public ActionResult Index()
          {
              return View();
          }
    }
}

// This is in another project / namespace / assembly
namespace MyOtherProject.Foo.Controllers
{
    public class CustomFooController : MyProject.Areas.Foo.Controllers.FooController
    {
        [Route("Foo/Bar")]
        public string Bar()
        {
            return "Bar";
        }
    }
}

So my current steps are as follows :

  • Inherited from the base FooController in the main project within another project / solution.
  • Set up attribute routing to access the custom controller to avoid conflicting routes from the main project.
  • Created a Build Event that moves the custom DLL into the main project when built (so it will be accessible) from the new custom project.

This didn't seem to make any difference. I tried going to the Foo/Bar url but it just threw a 404 as if it didn't see it at all. The CustomFooController.cs file is in it's own separate project and is just a class file and not an MVC project. Is this correct? Do I need to set the routing rules in the main project?

like image 935
Thomas Johnson Avatar asked Jun 06 '16 13:06

Thomas Johnson


People also ask

What is the role of the defaultcontrollerfactory in MVC?

Controller factory is responsible for handling the incoming request and mapping it to specific controller. Here is how it works: In this figure, I tried to explain where the role exists of controller factory. By default, ASP.NET MVC uses DefaultControllerFactory class for creating controller after receiving request from Route Handler.

What is an area in MVC?

An ASP.NET Core MVC app can have any number of areas. Each area has its own controllers, models, and views. Areas allow you to organize large MVC projects into multiple high-level components that can be worked on independently. Areas support multiple controllers with the same name, as long as they have different areas.

What is the use of controller class in MVC?

The controller class we use in our MVC application is derived from Controller ( abstract) class which implements ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter and IResultFilter . With the help of the above mentioned interfaces and ControllerBase class, the Controller class helps to create a controller easily.

How do you extend an existing controller?

If you actually want to extend the functionality of an existing controller, then inheritance might be the way to go. Inheriting from the controller within your main application will allow you to take advantage of any existing attributes, overridden methods, or underlying base controllers that might already place.


1 Answers

Controller Inheritance

Using inheritance as Chris mentioned in the comments section will likely be the best way of going about this as well. This is especially true if you are already deriving from another base controller class like ApplicationController in your example :

// ProjectA is assumed to be your "main" MVC application
public class CustomFooController :  ProjectA.Controllers.FooController
{
    [Route("Foo/Bar")]
    public ActionResult Bar()
    {
        return Content("Bar");
    }
}

The attribute routing here is extremely important as you don't want your existing routes to confuse your two controllers or overlook them.

Registering Attribute Routes

Since you are using attribute routing via the [Route] attribute within your ProjectB section, you'll want to ensure that you explicitly set it within the RouteConfig.cs of your ProjectA project so that it can properly identify it through the Routes.MapMvcAttributeRoutes() method as seen below :

public static void RegisterRoutes(RouteCollection routes)
{
    // This is important to set up your Route Attributes
    routes.MapMvcAttributeRoutes();

    // Route declarations omitted for brevity
}

Likewise, if you are using Areas, you'll want to configure this within the respective AreaRegistration.cs file as well :

public override void RegisterArea(AreaRegistrationContext context) 
{
    // Wire up any attribute based routing
    context.Routes.MapMvcAttributeRoutes();

    // Area routing omitted for brevity
}

Scoping Routes

Finally, the last thing you'll want to make sure to do is properly "scope" your routes to prioritize your main namespace within the RouteConfig.cs of your main ProjectA application :

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapMvcAttributeRoutes();
    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Foo", action = "Index", id = UrlParameter.Optional },
        // This will prioritize your existing Controllers so they work as expected  
        namespaces: new[] { "ProjectA.Controllers"}
    );
}

Getting References Across

You mentioned using a Build Event to copy over the DLL from your ProjectB project into your main ProjectA project, which should be fine in this case. You will basically need some way to access it and a simply xcopy like the following should be fine in most scenarios :

xcopy /E /Y /S  "$(ProjectName).dll" "$(SolutionDir)\ProjectA\Bin\"

Putting It All Together

If you have wired up all of these steps correctly, you should be able to Clean / Rebuild your existing solution. After doing so, double-check to ensure that you have the appropriate DLL within your ProjectA bin directory :

enter image description here

If that is there, then you are on the right track and should be able to run your main application and navigate to ~/Foo to see the following :

enter image description here

Likewise, navigating to ~/Foo/Bar should pick up the appropriate attribute route that was defined in your other Controller and serve the proper content :

enter image description here

like image 114
Rion Williams Avatar answered Oct 09 '22 10:10

Rion Williams