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?
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 :
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 :
Likewise, navigating to ~/Foo/Bar should pick up the appropriate attribute route that was defined in your other Controller and serve the proper content :
Related
I have created a 'template' project A which has 10 controllers ( partial class ).
All these controllers are packed in a nuget package and are consumed by projects B, C & D. The nuget generates the controllers in the folder controllers/_core so they are nicely stored separately. The nuget package does not have a reference to the dll of project A.
Now to get down to the problem;
It could be that one of the controllers that are generated need to be modified. By adding a second partial we could add some logic, but it could be that we need to override an existing method to add logic inside the method. Because of this ( and because of the reason I added below ) I was thinking to inherit from these base controllers. When we inherit a base controller it should be excluded by MVC.
Example: Image PersonController was created in the controllers/_core folder by using the nuget package and every method is virtual.
We will then create a _PersonController in the controllers folder that inherits from PersonController and for simplicity sake only the index method is overriden. At this time we want to change the routing so localhost/Person/index ends up in the index method of the _PersonController and not that of the PersonController. PersonController should be completely ignored.
Is my only option to add a custom route each time we need to override? Or are there better solutions for this kind of problem ( custom MVC controller factory? )?
+ how do I define such routing?
Extra reason why I was thinking to inherrit:
Every time we do an update of the nuget package it'll try to override all the changes made to the controllers that were generated in the consumer projects B, C & D.
Kind regards,
Yannick
You can go with controller inheritance.
ASP.NET core
To ignore unnecessary controllers you need to implement IApplicationFeatureProvider<ControllerFeature> or derive from ControllerFeatureProvider :
public class MyControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
var isController = base.IsController(typeInfo);
if (isController)
{
//overriding isController value
}
return isController;
}
}
Then at your Starup.ConfigureServices you need to replace the default ControllerFeatureProvider:
services.AddMvc()
.ConfigureApplicationPartManager(manager =>
{
var controllerFeatureProvider =
manager.FeatureProviders
.Single(p => p.GetType() == typeof(Microsoft.AspNetCore.Mvc.Controllers.ControllerFeatureProvider));
manager.FeatureProviders[manager.FeatureProviders.IndexOf(controllerFeatureProvider)] =
new Attributes.MyControllerFeatureProvider();
});
ASP.NET MVC 5 and earlier
In your particular case you can go with namespaces list on the route registration and setting UseNamespaceFallback to false to ignore other namespaces:
var myRoute = routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new [] {"Namespace1", "Namespace2"}
);
myRoute.DataTokens["UseNamespaceFallback"] = false;
I'm using the SumoSoft.CMS.Mvc, which is a Nuget package that installs in your MVC project some Views and dlls in order to quickly build a CMS.
These dlls include the code of some controllers like "SumoSoft.CMS.BlogController". Is there any way to extend these Controllers in order to add a new Action?
For example, the SumoSoft.CMS currently provides the actions:
/Blog/Index
/Blog/Article
What if I want to create the action:
/Blog/Category
I would assume it would simply work like
public class YourController : SumoSoft.CMS.BlogController
{
public ActionResult YourAction
{
}
}
You could extend your routing table to add an explicit route for your custom action.
routes.MapRoute(
name: "CustomBlogRouteActionJackson",
url: "Blog/CustomAction",
defaults: new { controller = "YourControllerNameWhichInheritsFromThatLibrariesController", action = "YourAwesomeActionName" }
);
Make sure this goes above any other routes.
This was kind of asked at Web Api 2 global route prefix for route attributes?.
I'm using attribute routing and class level route prefixes already. However, from a configuration of some sort (could be code) I would like to add another prefix to all the attribute routes. I do not want to create custom route attributes to use throughout my code base, just the built in ones.
Is this possible?
Simply put, I would like to take my routes
/a/1/b/2
and
/x/3/y/2/z/1
and turn them in to (although it doesn't necessarily need to be a /api prefix)
/api/1/b/2
and
/api/x/3/y/2/z/1
Option 1
You could create an abstract base controller class that all other controllers inherit from and apply the RoutePrefix attribute to that. For example:
[RoutePrefix("/api")
public abstract class BaseController : ApiController
{
}
And then my normal controllers would look like this:
public class ValuesController : BaseController
{
[Route("/get/value")]
public string GetValue()
{
return "hello";
}
}
Option 2
A secondary option is to use a reverse proxy that will transparently route all incoming requests to the correct URL. You could set the proxy up with a rewrite rule such as "any request that matches /api/*, redirect to internalserver/*". You can use ARR for IIS to do this and it is free. I have used it in the past and it works very well for situations like this.
You could also read the Routes of the default HttpConfiguration and just create a new HttpConfiguration with the only difference that you apply a prefix to the routeTemplate. At the end you use this HttpConfiguration then.
Theoretically you could also create a new WebApi Startup class and your old one provides it's HttpConfiguration as a property in case you want to change routes in a seperate web project.
Something like:
HttpConfiguration oldCofiguration = OtherWebService.Startup.Config;
HttpConfiguration newCofiguration = new HttpConfiguration();
foreach(var oldRoute in oldCofiguration.Routes){
newCofigurationRoutes.MapHttpRoute(
"YourRouteName",
"yourPrefix" + oldRoute .routeTemplate ,
new
{
controller = oldRoute.Controller
},
null,
null
);
}
You need to adapt the code to your needs. (Sorry the code is untested, as I have no access to IDE just now)
I have two Web API projects and I have a MarketController and I need to extend the Api controller so I did it.
I created a BaseController class and inherit from ApiController like this:
public class BaseController:ApiController { }
so far so good, it's working fine:
public class MarketController : BaseController
{
public MarketController() : base()
{
}
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
But I wanted to do it in another class library called BLL, so I moved the BaseController class to the BLL class library and I referenced it in the Web API project.
When I did this, the api stopped working.
The response is :
{
"Message": "No HTTP resource was found that matches the request URI someurl/api/market.",
"MessageDetail": "No type was found that matches the controller named 'market'."
}
No need to implement custom ControllerFactory or AssemblyResolver classes.
This scenario will "just work" provided you add the Microsoft.AspNet.WebApi.Core nuget package to the assembly containing the base class.
In my case I'd just added a reference to the System.Web.Http.dll which will compile, but the controllers will not load properly. Adding the Nuget package got everything working with no code changes.
By default MVC looks for all controllers in same assembly of mvc application. The default controller factory creates controller instance based on string 'ControllerName+Controller' like MarketController where market comes from url market/actionname.It will look for MarketController in the same assembly as mvc application.
To put controller in separate assembly you will have to create your own controller factory or you will have to specify assembly name is app start.
Once you've created your own custom ControllerFactory, you add the following line to Application_Start in global.asax to tell the framework where to find it:
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());
Or for simple cases like yours you can do this :
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" },
new[] { "BLLAssembly.Controllers" }
);
Here BLLAssembly.Controllers is namespace for your BaseController in BLL assembly.
There is one more advanced way using custom assembly resolver ,i.e IAssembliesResolver
The below article tells how to do this with Web Api also,
http://www.strathweb.com/2012/06/using-controllers-from-an-external-assembly-in-asp-net-web-api/
I am currently working in a brownfield ASP.NET MVC 3 project in VS2010.
In this project, views and controllers are in separate projects. This is not something that I have seen before. In each action method there is no explicit stating of view name as below.
return View("viewName",passingModel);//projects where controllers and views are in same
I have done this implicitly in VS2012 by right clicking on the view and do add view. So I was not bothered about where is this connection between action method's return view and the view is stated.
Unlike in VS2012, in VS2010 I can not navigate to the view that is related to one particular action method by right clicking on View and doing go to view.
I tried to understand this by doing this small experiment. I created a Controller and created a Action Method call xxxx and I created a view for that implicitly as mentioned above and searched the word xxxx in entire solution but this word only appeared in controller and in the view.
So, I was unsuccessful in finding the answer. I think visual studio itself creating its own mapping to achieve this.
I would like to know who these implicit connections are created among action methods and views to understand what is going on in my project.
Edit:
Both the projects which contains controllers and views are class libraries. not asp.net mvc projects.
Global.aspx file contains this:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
protected void Application_Start()
{
DependenciesHelper.Register(new HttpContextWrapper(Context));
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RoutingHelper.RegisterRoutes(RouteTable.Routes);
}
protected void Application_End()
{
//Should close the index
//If this method is not executed, the search engine will still work.
SearchService.CloseIndex();
}
The mapping is fairly straightforward. For example if you have a controller called "MyBrilliantController" and an action method called "MyExcellentAction" which returned just return View(); it would map to (in the UI project) ~/Views/MyBrilliant/MyExcellentAction.cshtml
The only time where this is different is when you are working with "Areas" - but the mapping is effectively the same, it would just consider the area folder first (ie ~/Areas/MyArea/Views/MyBrilliant/MyExcellentAction.cshtml)
Hope that helps.
EDIT - You can also specify namespaces in the global.asax file on each route for the engine to find controllers
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {
controller = "Home",
action = "Index",
id = UrlParameter.Optional
}, // Parameter defaults
new string[] {
// namespaces in which to find controllers for this route
"MySolution.MyControllersLib1.Helpers",
"MySolution.MyControllersLib2.Helpers",
"MySolution.MyControllersLib3.Helpers"
}
);
}