Override MVC routes where applicable without inheriting controller - asp.net

Say I have a controller FooController which has a bunch of action methods. Some of these methods would I like to override, but none of the methods are marked as virtual and I do not want to change the code of FooController.
So I implement a CustomFooController (not inheriting from FooController) and write new versions of the methods that I want.
Now I want to route first to CustomFooController and if the action is not available there I want to default to FooController. I have set up this route config to override the route:
routes.MapRoute(
"customFoo",
"foo/{action}",
new { controller = "CustomFoo", action = UrlParameter.Optional }
);
Here is some example definitions:
public class FooController : Controller
{
public ActionResult Bar { ... }
public ActionResult Baz { ... }
}
public class CustomFooController : Controller
{
public ActionResult Bar { ... }
}
So when accessing /Foo/Bar we should hit CustomFooController.Bar() and when accessing /Foo/Baz we should hit FooController.Baz() since Baz() is not implemented in CustomFooController.
But I get "The resource cannot be found", I understand why, but can I somehow work around it without modifying FooController?

You might want to have a look at HandleUnknownAction() (see msdn reference page).
It is invoked when a request matches a controller, but no method with the specified action name is found in that controller.
So you could override that method in your CustomFooController to redirect to the appropriate action in FooController (or even other controllers based on your custom inspection of request related data).
Redirection can be done with RedirectToAction() (see msdn reference page)

I think I found a neat solution myself. Since I don't want to modify FooController and a bunch of other controllers from another application I'm extending (and relying on updates from) I decided to use extension method MapMvcAttributeRoutes() from System.Web.Mvc.RouteCollectionAttributeRoutingExtensions in my startup RouteConfig in order to first match with route attributes on e.g. my CustomFooController which takes precedence over the conventional routing.

Just to be on the safe side: HandleUnknownAction() is implemented only in CustomFooController. You don't have to modify FooController (or other controllers).

Related

ASP.NET MVC - Multiple controller types were found that match the URL

Using ASP.NET MVC 5.2 with attribute routing. I have 2 actions in separate controllers:
public class AccountController
{
[Route("login")]
public ActionResult Login()
{
}
}
public class HomeController
{
[Route("{category}")]
public ActionResult Category(string category)
{
}
}
When I run it, I get an error Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
category dynamic parameter in Category action is something that is checked against DB in the action and there are a lot of possible values so I don't think I can use route constraints to include all possible options, it's also a string, so something like category:int is not an option.
I think I also cannot use Order in routing since those actions are in different controllers.
I know I can change the route for Category action to be something like [Route("category/{category}")] but I'd prefer to not do that. What's the best way for me to go about it so both /login and /{category} work?

Symfony workaround for using of protected methods

Background: symfony3
I have just stuck in the fact that redirectToRoute and addFlash methods in controller are protected in symfony. I have a separate class for action.
namespace AppBundle\Action;
class Base {
public function __construct($controller) {
$this->controller = $controller;
}
}
As you can see base action class requires a controller. Basically it is logical because action class is part of a controller and should have access to all its methods. However I cannot call $this->controller->addFlash as it is protected. If it is protected then there might be some reason for it. I cannot find it. Can you please hint me how I can change my action class so that it could use controller methods.
The variant about extending action from a controller does not fit me as I have additional functionality in the main controller. It is configured in a proper way.
Update: my goal is to devide controller functionality by responsibility. I invented an action class. My end code look like following:
public function editAction() {
$instance = new \AppBundle\Action\MyController\Edit($this);
return $insance->run();
}
In this case I keep controller clean and not verbose.
Here is a link to Symfony Controller Trait, that you can duplicate, if you really want to work that way.
But since you are injecting a whole symfony controller into your own controllers, you will be better off with extending instead. Injection is used here for injecting separate service by their IDs.

How can I run a method every page load in MVC3?

With WebForms, if I wanted to run a method on every page load, then I would call this method in the Page_Load() method of the main Master Page.
Is there an alternative, perhaps better solution, when using MVC3?
you could create a class base controller
public class BaseController : Controller
{
public BaseController()
{
// your code here
}
}
and let every new controller of yours impelement the base controller like
public class MyController: BaseController
also i have found the basecontroller very usefull to store other functions i need a lot in other controllers
I think the most appropriate way to do this in MVC is with filters
MSDN provides a good description of them, and there are dozens of articles and explanatins about them on the net, such as this one
EDIT
This sample is even better: It provides a simple action filter, which is then registered in global.asax, and executed on every request, before the actual Action in the relevan controller executes. Such concept allows you to access the request object, and modify whatever you want before the actual controller is executing.
You could put the code in the constructor of the controller.
Like this:
public class FooController : Controller
{
public FooController()
{
doThings();
}

asp.net mvc controller for masterpage? [duplicate]

I'm using a masterpage in my ASP.NET MVC project. This masterpage expects some ViewData to be present, which displays this on every page.
If I don't set this ViewData key in my controllers, I get an error that it can't find it. However, I don't want to set the ViewData in every controller (I don't want to say ViewData["foo"] = GetFoo(); in every controller).
So, I was thinking of setting this in a base controller, and have every controller inherit from this base controller. In the base controller default constructur, I set the ViewData. I found a similar approach here: http://www.asp.net/learn/MVC/tutorial-13-cs.aspx. So far so good, this works... but the problem is that this data comes from a database somewhere.
Now when I want to Unit Test my controllers, the ones that inherit from the base controller call its default constructor. In the default constructor, I initialize my repository class to get this data from the database. Result: my unit tests fail, since it can't access the data (and I certainly don't want them to access this data).
I also don't want to pass the correct Repository (or DataContext, whatever you name it) class to every controller which in turn pass it to the default controller, which I could then mock with my unit tests. The controllers in turn rely on other repository classes, and I would end up passing multiple parameters to the constructor. Too much work for my feeling, or am I wrong? Is there another solution?
I've tried using StructureMap but in the end I didn't feel like that is going to fix my problem, since every controller will still have to call the base constructor which will initialize the repository class, so I can't mock it.
This is a similar question but I find no satisfactory answer was given. Can I solve this in a neat way, maybe using StructureMap as a solution? Or should I jsut suck it and pass a Repository to every controller and pass it again to the base controller? Again, It feels like so much work for something so simple. Thanks!
I see two options:
First:
Set the ViewData for MasterPage in YourBaseController.OnActionExecuting() or YourBaseController.OnActionExecuted():
public class YourBaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Optional: Work only for GET request
if (filterContext.RequestContext.HttpContext.Request.RequestType != "GET")
return;
// Optional: Do not work with AjaxRequests
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
return;
...
filterContext.Controller.ViewData["foo"] = ...
}
}
Second:
Or create custom filter:
public class DataForMasterPageAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Optional: Work only for GET request
if (filterContext.RequestContext.HttpContext.Request.RequestType != "GET")
return;
// Optional: Do not work with AjaxRequests
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
return;
...
filterContext.Controller.ViewData["foo"] = ...
}
}
and then apply to your controllers:
[DataForMasterPage]
public class YourController : YourBaseController
{
...
}
I think the second solution is exactly for your case.

ASP.NET MVC Route All Requests

We need to present the same site, with a different theme (and different data) depending on the "route" into the site.
www.example.com/Trade
www.example.com/Public
NB: These are not dynamic themes which the user can pick. Certain customers will be coming via a third party link which will always point them to one of the urls.
The value trade/public also needs to be used in queries from the UI to the database (to pull back varying data depending on the route into the site).
So what are my options?
Create custom view engine which uses the querystring (mvc route param) value to load the relevant master page.
In each controller action, grab the parameter (trade/public etc) and pass it off to the database queries.
public ActionResult List(string siteType){
products.ListFor(siteType);
}
The problem here is having to alter every controller action to pass the querystring value through.
This also presents an issue with any route defined in global.asax having to accept the parameter.
I'm wondering if there's another way, perhaps some combination of a custom controller base and host name e.g. trade.example.com, public.example.com?
First define routing:
routes.MapRoute(
"Default", // Route name
"{siteType}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
and then use it in controller:
RouteData.Values["siteType"]
How about this:
Create a base controller that all your controllers inherit from
Add a property: public string SiteType { get; protected set; }
Add an OnActionExecuting method to set it before any action is called, e.g.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//use filterContext to set SiteType somehow, e.g. you can look at the URL or the route data
}

Resources