So I have a legacy webforms site and am working on making it easier to maintain. Chucking it away and rewriting it isn't an option.
IoC is obviously one of the first things it got, but this leaves me with the service-locator pattern and a bad taste, and the wondering of whether it could be done better.
Various people I've talked to online and off tell me that I could do property-injection with an HttpModule that scans a Page class for properties decorated with an Inject attribute or similar, but that sounds like a Reflection hit (cached, but still) on every request. Not appealing.
So I was looking at other options, and came across System.Web.IHttpHandlerFactory, which has apparently been in the framework since v2. One can remove the default *.aspx handler and replace it with one that uses a custom implementation, in httpHandlers web.config section.
So, the people I've talked to aren't dumb; I thought I'd ask here. Are there any gotchas with replacing the webforms PageHandlerFactory with an IoC-based implementation...?
It looks like it has both a CreateHandler and ReleaseHandler method, so life-style related memory leaks from the container keeping a reference to created components shouldn't be a problem...
Because of the way ASP.NET is designed, Page
classes need to have a default constructor. When you want to use constructor injection, there is a way around this. You can make the default constructor protected and add a single public constructor that takes the dependencies as follows:
public partial class _Default : System.Web.UI.Page
{
private IUserService service;
protected _Default()
{
}
public _Default(IUserService service)
{
this.service = service;
}
}
This allows you to create a custom PageHandlerFactory
and inject the dependencies in the constructor.
So this works, but there is a catch though. The _Default
class you define is not the actual class ASP.NET uses. ASP.NET creates a new class that inherits from _Default
. This new class builds a control hierarchy based on the markup in the .aspx file. This class looks a bit like this:
public class ASPGeneratedDefault : _Default
{
public ASPGeneratedDefault() : base()
{
}
protected override void OnPreInit(object s, EventArgs e)
{
// Building up control hierarchy.
}
}
As you can see, the custom constructor hasn't been overridden in the ASPGeneratedDefault
by ASP.NET. Because of this there is no way of letting a DI framework create this type for us. The way around this is to let ASP.NET create this type for us and invoke the non-default constructor of the _Default
base class on that existing instance. Because this instance already exists, we must do this with reflection and this will fail when run in partial trust.
Besides that, this works for page classes, but not for user controls on the page. The code generator of ASP.NET news up those controls with their default constructor during the control hierarchy build up process. When you want this to work for them, you need your custom PageHandlerFactory
to hook to the PreInit
event of those controls, because during the time the page class is constructed, the related controls and user controls are not yet created. However, to register the PreInit
event on them, you need to find those controls within the page class, and again we need to reflect over the page class. Because controls are stored in non-public instance fields, again this won't work in partial trust.
Whether or not it is a problem your application can not run in partial trust is up to you, but since the security model of .NET 4 has been simplified considerably, it is very easy to run web apps in partial trust and it is something I strive to do.
TLDR; So in conclusion, it is possible to do so (see for instance this example), but because of the limitations of the ASP.NET Web Forms framework, you need to run in full trust to get it to work.
UPDATE Microsoft has obsoleted partial trust for ASP.NET starting with NET 4.0 (read here). So from that point of view, keeping away from full trust might not be that useful (you'll need it anyway).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With