Can I use controller name in controller destination - asp.net

I want a route that will take requests from
/api/v1/job
to
Controller that lives in
/api/v1/jobController
where the controller name is jobv1Controller
configuration.Routes.MapHttpRoute(
"v1",
"Api/v1/{controller}/{id}",
new { controller = "{controller}v1", id = RouteParameter.Optional });

short version:
the in-built DefaultControllerFactory may not be able to create the controller you need, since it treats the value as a plain name string, and doesn't do any token replacement.
the solution would be to extend DefaultControllerFactory and override the CreateController method. Change the controllerName value and then use the base functionality to get you that controller.
Register your custome factory in Global.asax
ControllerBuilder.Current.SetControllerFactory(
typeof(YourCustomControllerFactory));
long version:
As you know the flow is as follows:
Request >> Routing System >> MVC's Default Controller Factory >> Create Controller >> Invoke it.
I don't think it is possible to give dynamic values in the default route data dictionary and expect the default controller factory to find that controller.
This is because the built-in controller factory works off the direct value of the controller key, given in the route data dictionary.
The MVC Handler does no magic there of trying to parse the controller value provided as some formatted string, and associating Url Fragment values (controller/action etc.) in this format. it sees it as a straight up value.
By default, the DefaultControllerFactory is invoked to get the controller type, and it uses the string value as-is to activate a controller type.
So one way to solve your problem is to define a custom factory implementing IControllerFactory
Once you do that, your factory's
CreateController method will be called, with the RequestContext and ControllerName string as the 2 parameters.
The RequestContext has the RequestContext.RouteData.Values dictionary, which has all the routing data, tokens, constraints, namespaces etc.
Once you have the incoming controller name, you can massage it to whatever format you need (controllername + "v1") and then instantiate that controller (using DI containers or Service Locator or Activator.CreateInstance etc. whatever you wish) and return it. Try to use some sort of look-up cache for this.
Once you have implemented your custom factory, you can register your custom controller factory with MVC as follows in Global.asax:
ControllerBuilder.Current.SetControllerFactory(
typeof(YourCustomControllerFactory));

Related

asp.net web api route for controllers with same name

I am integrating my project with another one
Multiple types were found that match the controller named 'XXXXX'.
This can happen if the route that services this request ('api/{controller}/{action}/{id}') found multiple controllers defined with the same name but differing namespaces, which is not supported.
The request for 'XXXXX' has found the following matching controllers:
COM.example.host.XXXXXController
COM.example.parasite.XXXXXController
Is there a way to make this somehow work with the least amount of edit from either side?
I would like to avoid having to make modifications to all of my controllers.
Thank you
Unfortunately, that is not very simple because default controller selector (DefaultHttpControllerSelector) does not look for namespace in the full controller name when it selects controller to process request.
So, there are at least two possible solutions to your problem:
Write your own IHttpControllerSelector which takes controller type namespace into account. Sample can be found here.
Rename one of controller types to make then unique.
TL;DR Default controller selector uses the cache of controller types (HttpControllerTypeCache) which holds something like:
{
"Customers" : [
typeof(Foo.CustomersController),
typeof(Bar.CustomersController)
],
"Orders" : [
typeof(Foo.OrdersController)
]
}
So it uses controller name as dictionary key, but it can contain several types which have same controller name part. When request is received, controller selector gets controller name from route data. E.g. if you have default route specified "api/{controller}/{id}" then request api/customers/5 will map controller value to "customers". Controller selector then gets controller types which are registered for this name:
if there is 1 type, then we can instantiate it and process request
if there is 0 types, it throws NotFound exception
if there is 2 or more types, it throws AmbiguousControllerException exception (your case)
So.. definition of another named route will not help you, because you can only provide controller name there. You cannot specify namespace.
Even if you'll use Attribute Routing and specify another route for one of the controller actions
[RoutePrefix("api/customers2")]
public class CustomersController : ApiController
{
[Route("")]
public IHttpActionResult Get()
//...
}
you will still face the controller selector problem because attribute routes are applied to actions - there will be no controller value in route data for those requests. Attributed routes are treated differently - they are processed as sub-routes of the "MS_attributerouteWebApi" route.
The easiest path for you might be to add action based routing to your web api:
RouteTable.Routes.MapRoute(
"WithActionApi",
"api/{controller}/{action}/{id}"
);
To your WebApiConfig.cs, be sure to put it above the default rule. Then, change how you call your controllers by including the method name, which at this point should be or should be made unique.

How to gain control of what is returned after a route matches?

With ASP.NET MVC, when a route matches, the framework will determine the Controller type and then use the ControllerActivator class to create a new instance of it and then execute the Action method and finally return it's result to the request caller.
Just out of curiosity, what if I wanted to intervene exactly between the route and the Controller. Let's say controllers don't fit my needs. Let's say I just need to execute a random method in a class and return it's result.
What would I do then? What would I have to override? Can you please provide the path? A sample code would be appreciated too.
Create your own controller factory which implements IController factory. Something like below.
public class MyControllerFactory : IControllerFactory
Implement IControllerFactory interface
Register your new controller factory in Global.asax Application_Start event.
ControllerBuilder.Current.SetControllerFactory(typeof(MyControllerFactory));
You can check code of DefaultControllerFactory as reference for implementing your own factory.

Find out the name of the controller and action before the request is serviced

I want to intercept every request coming into my MVC app after the ControllerName and Action have been resolved, but before the request is serviced, i.e. before it reaches the appropriate action.
I have thought of two ways of doing this:
1) I could write an HttpModule to intercept every incoming HttpRequest, read the HttpContext.Request.AbsoluteUrl (or some such) property, then assume that the route I have configured will never be changed (and that is the assumption I want to avoid or I would have gone this way), and infer the names of the controller and action that are being invoked.
However, I am looking for a more reliable way. Hence (2) below.
2) I assume that MVC already does this for me at one time or another before instantiating the right controller. I want to know where it does that so I may be able to re-use its work. At which point may I get this information, intercept the request and do something about it before it reaches the action?
You could create a global action filter where you will have access to the controller and action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string controller = filterContext.RouteData.GetRequiredString("controller");
string action = filterContext.RouteData.GetRequiredString("action");
// do something with those variables ...
}
}
The OnActionExecuting method will be invoked before every controller action is ran and you will be able to retrieve the information you are looking for from the RouteData.
And then simply register your global action filter in your Application_Start:
GlobalFilters.Filters.Add(new MyGlobalActionFilter());

Formatting uri template string values in ASP.NET WebApi

I have an ASP.NET WebApi application that has some controller methods that expect certain strings to be passed in as method parameters (declared as part of the route template).
On all the methods, the strings passed in are base64-encoded -- which means each controller method must base64-decode them before doing anything with them. While I can obviously have each method do this easily enough, I was wondering if there was a way to perform the decoding before the string actually gets passed to the controller method. I presume this is something along the lines of an action filter or custom formatter, but I'm not familiar enough with asp.net web api to know where to start on that?
Summary:
I've got route templates like : {controller}/{encodedString}/whatever
where {encodedString} is always a base64-encoded string.
and controllers with methods like
GetWhatever(string encodedString)
{
Base64Decode(encodedString);
// do other stuff...
}
I would like to use some part of the asp.net webapi pipeline to decode {encodedString} before the controller method is actually called. What path should I start down in order to do this?
You can create a custom model binder and attach it to the parameters using the ModelBinderAttribute. In the model binder you then do the base64 decoding.
For a reference on parameter binding in Web API check:
How WebAPI does Parameter Binding

In asp.net mvc is it possible to make a generic controller?

I'm attempting to create a generic controller, ie:
public class MyController<T> : Controller where T : SomeType
{ ... }
However, when I try to use it, I'm running into this error everywhere...
Controller name must end in 'Controller'
So, my question, Is it possible to make a generic controller in asp.net mvc?
Thanks!
If I understand you properly, what you are trying to do, is route all requests for a given Model through a generic controller of type T.
You would like the T to vary based on the Model requested.
You would like /Product/Index to trigger MyController<Product>.Index()
This can be accomplished by writing your own IControllerFactory and implementing the CreateController method like this:
public IController CreateController(RequestContext requestContext, string controllerName)
{
Type controllerType = Type.GetType("MyController")
.MakeGenericType(Type.GetType(controllerName));
return Activator.CreateInstance(controllerType) as IController;
}
Yes you can, it's fine and I've used them lots myself.
What you need to ensure is that when you inherit from MyController you still end the type name with controller:
public class FooController : MyController<Foo>
{
...
}
The default controller factory uses "convention" around controller names when it's trying to find a controller to dispatch the request to. You could override this lookup functionality if you wanted, which could then allow your generic controller to work.
This MSDN article...
http://msdn.microsoft.com/en-us/magazine/dd695917.aspx
... has a good writeup of what's going on.
This is a duplicate of asp.net mvc generic controller which actually contains the correct answer. Jeff Fritz's answer is absolutely not correct. Creating your own IControllerFactory will not get past the limitation in ExpressionHelper.GetRouteValuesFromExpression which is generating the error you are seeing. Implementing your own IControllerFactory will still leave you with errors whenever you call RedirectToAction, BuildUrlFromExpression, ActionLink, RenderAction, BeginForm, any any methods that call those.
What is interesting to me, is that Microsoft's "restriction by convention" is already enforced by the constraint "where TController : Controller" that is placed upon the type in the ExpressionHelper.GetRouteValuesFromExpression method. No generic will ever satisfy the convention validation:
string controllerName = typeof(TController).Name;
if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentException(MvcResources.ExpressionHelper_TargetMustEndInController, "action");
}
unless it is inherited by a class ending in "Controller" because typeof(AnyGeneric).Name will never end with "Controller".
If i was you, i'd get the MVC source and create a test MVC project with the source code so you can examine where the exception is generated and see what you can do about your generic idea and the enforced "*controller" naming convention.

Resources