So I want to do some profiling on a bunch of controllers which inherit from System.Web.Http.ApiController. In the project that's doing the profiling, we're registering them with
builder.RegisterApiControllers(typeof(Web.Modules.AutofacModule).Assembly)
.As(type => new Autofac.Core.KeyedService("api", type));
Later on, I'm trying to register a decorator for each with
builder.RegisterDecorator<ApiController>(original => Decorate(original, profiler),
fromKey: "api");
where Decorate injects some profiling code via a DelegatingHandler and returns the original.
I can resolve the controllers just fine:
scope.ResolveKeyed<RegistrationController>("api");
but the profiling code is never invoked, nor is Decorate.
My first thought was that maybe I need to register the controller components as ApiController's, but dropping an .As<ApiController>() just below first snippet wasn't successful.
Any help would be super. This probably just boils down to my lack of Autofac-fu.
So I want to do some profiling on a bunch of controllers which inherit from System.Web.Http.ApiController.
This is impossible. This has nothing to do with Autofac, but with the way ASP.NET Web API is designed.
Even though Web API Controllers derive from a common base class, ASP.NET Web API requires the original controller type to be used. In other words, when it requests a HomeController from the IHttpControllerActivator, it expects that exact type (or a sub type), but not a sibling type (another ApiController derivative).
I think this limitation exists because Web API uses reflection to find the actual action methods. But when you return a decorator, those methods are gone, because a decorator applies composition instead of inheritance. This is very different from how ASP.NET MVC is designed. MVC actually does allow controllers to be decorated, as it always invokes the IController.Execute method. Implementation IController is therefore MVC's only requirement for controllers.
So even if you configure Autofac to wrap any ApiController derivatives in a Decorator, Web API simply won't let you.
The way to apply Cross-Cutting Concerns around the invocation of action methods in Web API is through the use of delegating handlers.
Related
I'm in a very specific situation where I'd like to override the default ASP.NET Core ControllerFactory. I'd like to do this because I want to be in full control of what type of controller I handle each request with.
The scenario is:
Request comes in with a specific subdomain
Based on this subdomain, I want to resolve a Generic type controller in the factory
For example:
category.website.com is called
We see it's of type category and will use the generic HomeController<T> , using DI to inject the category so the type is of HomeController<Category>
The HomeController<Category> will use some generic methods on type Category methods to render the homepage.
If I'm led to believe this link, a factory of type DefaultControllerFactory is registered on startup of the application. This seems to not be overridable.
Any idea how I would go by this? The most logical options for us is using the old ASP.NET MVC version which allows you to set your own ControllerFactory, but we'd lose features like being able to use SpaServices to prerender our Angular application.
Register your own implementation in ConfigureServices, after calling AddMvc:
services.AddSingleton<IControllerFactory, MyCustomControllerFactory>();
This way it will get called whenever a controller is to be built.
For completeness the best way is to actually implement an IControllerActivator and register it, since the default controller factory is not public. It will use whatever implementation of IControllerActivator is registered to actually create the controller class.
I have this situation where the fact that the user is not logged in is preventing the construction of my controller's dependencies.
[Authorize]
class MyController : Controller
{
public MyController(MyService service)
{
}
}
class MyService
{
public MyService()
{
// requires information which only
// becomes known after the user logs in
}
}
My question is: can I do something to cause the MVC framework to first look at the Authorize attribute and then resolve the controller instance?
The above would be much more preferable to me than either of the following:
Changing MyService to be able to handle being created before user log in
Injecting Func
Switching to service location
Ideally, I'd like to flip a switch in the MVC framework that says, "before you resolve the controller, check that you're actually going to use it and not toss it out due to lack of authorization and direct anyway..."
I have this situation where the fact that the user is not logged in is preventing the construction of my controller's dependencies.
That's the root of your problem.
// requires information which only becomes known after the user logs in
This means that the constructor of that service does too much. Constructors should not do more than store the incoming dependencies. This way you can compose object graphs with confidence.
The building of object graphs should in general be static. This means that the resolved object graph should not change based on runtime conditions (there are exceptions, but this is a good rule of thumb). It shouldn't matter whether or not a user is authorized or not, the service class should still be composed and injected. This means that authorization and loading of data is done at a later point in time; the moment that the request is actually executed (which is directly after the object graph is composed).
Your problem is that you don't really understand how the MVC Request pipeline works.
The first thing you have to realize is that Attributes are, in essence, designed to match up with identical events in the controller base class. There is an OnAuthorization method in Controller, and this is called either just before or just after the attributes OnAuthorization method is called.
So, in order for it to call both methods at roughly the same time, that means the Controller class must have been constructed to do so. What's more, a lot of other stuff goes on before Authorization filters, such as model binding, because your authorization filter may need information from the model to make its decision.
I suggest you check out this pipline chart.
https://www.simple-talk.com/dotnet/.net-framework/an-introduction-to-asp.net-mvc-extensibility/
We are trying to replace previous DefaultControllerFactory with new ASP.NET Web API to better handle REST/XML calls. Searching the web always pointed to implementating System.Web.Http.Dependencies.IDependencyResolver and then handling the GetService() and few other methods required by the interface.
However it seems to be caching the ApiController instance and any new controller, it does not seem to be resolving. Its difficult to provide all possible ApiController instances during startup due to performance issues.
DefaultControllerFactory allows providing controller instances and caches the "hit" entries, but throws errors when the an instane could not be delay loaded.
Are there are other overloads/controller factory methods that requires to be implemented?
Search did not yield any hits so far, but any pointers will be great. Thank you for your time.
It is IHttpControllerActivator implemented by DefaultHttpControllerActivator.
You may replaces it using:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator),
new MyOwnActivatior());
Oops!
I meant IHttpControllerSelector!
It is IHttpControllerSelector implemented by DefaultHttpControllerSelector.
You may replaces it using:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),
new MyOwnActivatior());
I found this Post (How to extend where MVC looks for views) about changing the location of the View.
I was wondering if there's something similar for changing the location of the controller.
I just want to change the location of the class inside project and don't want to affect the url.
For example Instead of placing the Controller into
MyMvcProject\Controllers\
MyController1.cs
MyController2.cs
MyController3.cs
I want to achieve something like
MyMvcProject\MyGroup1\
MyController1.cs
MyController2.cs
MyMvcProject\MyGroup2\
MyController3.cs
and also support Areas:
MyMvcProject\Areas\MyGroup3\
MyController4.cs
Is it possible to achieve this? And if yes, where can I find documentation about it?
You can do what you want, and it doesn't require any special configuration, because ASP.NET MVC does not care about where you put your controllers. First, controllers are located using reflection, so the name of the folder where you put your controllers is irrelevant. Controllers are searched by type name and optionally by namespace (for disambiguation). You can even have controllers in separate projects/assemblies. As long as the controller ends up in an assembly in the bin folder then it's searchable by the framework.
As mentioned above, you'll need to create a controller factory to support your custom resolution. Here's an example:
http://develoq.net/2010/custom-controller-factory-in-asp-net-mvc/
As others have already stated you need to do one of the following:
Derive from IControllerFactory interface and provide an implementation of the CreateController and ReleaseController methods.
Derive from DefaultControllerFactory and override the default behaviours.
Here are some links to get you started:
Custom controller factory in ASP.Net
Inside the ASP.NET MVC Controller factory
Dive deep into MVC - IControllerFactory
Also, if you're willing to spend a bit of money I would also recommend the book Pro ASP.NET MVC 3 Framework as this explains almost every aspect of how the MVC framework can be customised (including an example on how to create a custom controller factory - the source code for which can be freely downloaded from the publishers website).
I think it is impossible to do this. ASP.NET MVC have defined the convention that we have to follow.
Controllers are in Controllers folder, views are in Views{ControllerName}\
I believe you cannot change the convention unless you create your own ControllerFactory.
If you really want to do that, just implement IControllerFactory interface (or try to derive from DefaultControllerFactory).
Then your Application_Start register your controller factory using ControllerBuilder.Current.SetControllerFactory method.
Look at the ControllerFactory documentation and to the MVC source code for details.
What you're asking and what your example shows are two different things; depending on which one you want to achieve, you may or may not need to do any work.
There are two requirements for a class to be a controller in the MVC Framework:
It has to have a class name of Name + "Controller"
It has to have a parameterless public constructor.
Your sample "normal" MVC layout is actually not valid:
MyMvcProject\Controllers\
MyController1.cs
MyController2.cs
MyController3.cs
Those classes wouldn't be found by MVC because they don't have the correct name, regardless of which folder they are in.
If all you want to do is change the namespace/folder names, that "just works", assuming you name them the same as the appropriate route segment(s):
MyMvcProject\MyGroup1\
Page1Controller.cs
Page2Controller.cs
MyMvcProject\MyGroup2\
Page3Controller.cs
MyMvcProject\Areas\Area1\
Area1Page1Controller.cs
This walkthrough (written for MVC 2 but works just as well in MVC3) shows you how to support Areas with the default controller behavior.
If you actually want to name them SomethingController1 or SomethingElseController5, or otherwise change the route -> classname mappings, then you do need to implement a custom ControllerFactory, and inject it into the MVC pipeline.
There are plenty of examples on the web on how to do this, including the one posted earlier.
I like ASP.Net MVC Authorize attribute, I can extend it and build my own logic and decorate my controller with it. BUT,
In my architecture, I have one common service layer(C# Class Library). End user can access my application via ASP.Net MVC web site or via my exposed REST WCF Webservice layer.
My asp.net MVC application and REST WCF service layer both in turn access my common service layer.
I want authorization to happen in this common service layer and not in ASP.Net MVC Controller or in my exposed REST Service layer.
Can I create ASP.Net MVC Authorize attribute like thing to decorate my methods in the common C# class library? This attribute will take parameters and will decide if the current user has access to perform that function or not?
Thanks & Regards,
Ajay
What you're looking for can be achieved using AOP library, like PostSharp (http://www.postsharp.org/). It's more complex than using Authorize attribute in mvc, but is still quite simple.
Another way to handle this is to use the [PrincipalPermission] attribute in your service layer. This can prevent callers from executing a method (or accessing an entire class) without the defined authorization.
No, AuthorizeAttribute works because the MVC framework explicitly invokes it before calling the method. A similar feature for your service layer would only work if your clients explicitly invoked it, as well. It would not be reasonable to presume that even a well-intentioned client would always remember to look for the attribute and invoke it. WCF has its own security. You should use that instead of writing your own.
This shouldn't be too hard to do - there are a couple of places that you could reflect out the attribute and handle it accordingly:
On application start in Global.asx you can customise routing and locations for views
Underlying ASP.Net request events still fire, so you could override one of them
Create your own base controller and override OnActionExecuting
Update following comment
Ahh, I see. In that case if you're making direct calls then you should check out Code Access Security, which I think covers what you mean.
Alternatively a custom attribute might make sense as long as you are using some kind of factory pattern - then the reflection call that gets the factory could check the attributes.
If you're not using reflection to retrieve your classes or call your methods (which is essentially what routing does in MVC) then you won't get the chance to check your attributes.