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.
Related
I have an Administration Controller with a Users method. And I would like to add a “new” Subaction with a new View to this method. The URL should look like this: /administration/users/new
How can I do that?
Thanks for your help!
This is really just a question about routing. Just add a method in the Administration controller and tell MVC what the route is with a Route attribute. For example:
public class AdministrationController : Controller
{
public ActionResult Users()
{
}
[Route("users/new")] //This is the important part here
public ActionResult NewUser()
{
}
}
You could also configure the routing inside your Startup.cs class, but I find it easier to do with attribute routing. See here for more information.
I guess what you mean is "Area".
So, in Asp.Net Core 2 routing, there are areas, there are controllers, there are actions and optionally parameters.
You can have routing middleware configured something like. You can specify area attribute on the contorllers.
Administrator would be area - Users would be controller and New would be action.
This should keep the code clean as this is merely using the default routing middleware.
For better understanding of Areas, please refer: https://tahirnaushad.com/2017/08/25/asp-net-core-2-0-mvc-areas/
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=dashboard}/{action=index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=home}/{action=index}/{id?}"
);
You can use several methods to do it
Routes at .net core
try attributes
[Route("users/new")]
public IActionResult New()
{
return View();
}
I have a main project with some controllers, e.g. HomeController containing an action "Index". This action is reached by www.mysite.com/home/index.
Then I have another project called "plugin", which is referenced within the main project.
There is a controller, e.g. CustomerController. An action within this controller contains a routing attribute "[Route("edit")]".
This action is reached by www.mysite.com/customer/edit. But I want that this action can be reached by www.mysite.com/plugin/customer/edit containing the name of the project (or another name).
How can I do this without setting routing attribute for every controller in my "plugin" project?
Btw.. I'm using NopCommerce 4.1 if it's necessary to know..
This is scenario for Areas.
1) Inside your plugin create folder structure
Areas
..Plugin
....Controllers
....Views
2) Inside controllers create base plugin controller "PluginController" where you set Area attribute
[Area("Plugin")]
public class PluginController : Controller
{
...
}
3) Make all your plugin controllers inherit from PluginController
public class CustomerController : PluginController
{
...
}
4) Add support for areas into route builder
app.UseMvc(routes =>
{
routes.MapRoute(
name: "defaultWithArea",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Now all actions inside your plugin will require www.mysite.com/plugin/ ...
I'll also note that If you wish to retrieve action urls from outside the plugin you need to specify area of the controller like so:
#Url.Action("Edit", "Customer", new { Area = "Plugin" })
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;
So I have a HomeController, to access it along with Actions I have to type url.com/home/action.
Would it be possible to change this to something else like url.com/anothernamethatpointstohomeactually/action?
I suggest you to use attribute routing, but of course it depends on your scenario.
[Route("prefix")]
public class Home : Controller {
[HttpGet("name")]
public IActionResult Index() {
}
}
This will be found at url.com/prefix/name
There are a lot of options to attribute routing, some samples:
[Route("[controller]")] // there are placeholders for common patterns
as [area], [controller], [action], etc.
[HttpGet("")] // empty is valid. url.com/prefix
[Route("")] // empty is valid. url.com/
[HttpGet("/otherprefix/name")] // starting with / won't use the route prefix
[HttpGet("name/{id}")]
public IActionResult Index(int id){ ... // id will bind from route param.
[HttpGet("{id:int:required}")] // you can add some simple matching rules too.
Check Attribute Routing official docs
You can add new Routes in your Startup.Configure method within your app.UseMvc(routes => block:
routes.MapRoute(
name: "SomeDescriptiveName",
template: "AnotherNameThatPointsToHome/{action=Index}/{id?}",
defaults: new { controller = "Home"}
);
The code is quite similar to ASP.NET MVC.
For more info, see Routing in ASP.NET Core.
Below is for ASP.NET MVC (not ASP.NET Core MVC)
You can also add a new Route via routes.MapRoute in your RouteConfig:
routes.MapRoute(
name: "SomeDescriptiveName",
url: "AnotherNameThatPointsToHome/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Make sure, you insert the code before you define your Default route.
For more information, visit the docs.
Using the Route attribute on top of your controller will allow you to define the route on the entire controller.
[Route("anothernamethatpointstohomeactually")]
You can read more here.
In ASP.NET Core 6, we just do that in one line.
Go to your Controller and write before your action method:
[Route("YourController/YourAction/{YourParameter?}")]
In your example, you you need to write like this:
[Route("Home/Index/{name?}")]
You can change your url by modifying your routing configuration.
It is kind of like htaccess but not really.
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/creating-custom-routes-cs
Another solution is to create a page and do a server redirect.
Server.Transfer
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 :