
How to make friends ASP.NET Controls and the DI container
Intro
Recently, I decided to refresh my knowledge in ASP.NET a little, in connection with which I went deep into the processes of generating markup control code (* .ascx, * .aspx) and found that I can make very interesting decisions about which I want to tell about. So today we will learn how to make friends with our Dependency Injection container with the code generated by the controls.Go
![DependencyInjection_Solution [1] DependencyInjection](https://habrastorage.org/getpro/habr/post_images/66d/527/6bb/66d5276bb0c144c76a27b3140bf60afb.gif)
- Mark the required property of the
public class MyControl with the Dependency attribute : UserControl
{
[Dependency]
public MyPresenter Presenter
{
get { return _presenter; }
set
{
_presenter = value ;
_presenter.View = this ;
}
}
} - The control can be initialized as follows
protected override void OnInit (EventArgs e)
{
base .OnInit (e);
_сontainer.BuildUp (GetType (), this );
} - To take care of the container location in your application, I suggest using HttpApplication for this , having inherited from it and making small modifications to the global.asax file , we get the storage we need for the container, we need to handle it like this ((Sapphire.Application) HttpContext.Current .ApplicationInstance) .Container
public class MyControl: UserControl
{
public MyControl (MyPresenter presenter)
{
_presenter = presenter;
_presenter.View = this ;
}
}
I propose to do this. Let's start with the fact that the controls described in the page layout, when generating the page, indicate their constructors without parameters, I wonder how you can control this process, initially, digging into web.config, I assumed to do this through:
...
However, the implementation of my PageBuildProvider is a rather serious task, I think I should postpone it for a serious need. However, thanks to BuildProviders, for example, you can generate a data access layer, for this you need to: Write and register a handler for some extension, for example * .dal and do something like http://www.codeproject.com/ KB / aspnet / DALComp.aspx, by the way, similar logic is implemented in SubSonic http://dotnetslackers.com/articles/aspnet/IntroductionToSubSonic.aspx is also an interesting implementation of page inheritance from generic types http://stackoverflow.com/questions/1480373/generic -inhertied-viewpage-and-new-propertyyou can still, for example, generate exceptions, data transfer objects, and much more, the limitation is only your imagination. In general, this option does not suit us, it is necessary to make something easier, and there is a great solution, using the ControlBuilder attribute we can specify our control logic for the control from the markup, it will look something like this
[ControlBuilder (typeof (MyControlBuilder))]
public class UserControl: System.Web.UI.UserControl
{
}
Now we’ll deal with the implementation of MyControlBuilder, this type should inherit from ControlBuilder and with the help of the ProcessGeneratedCode overload we can tell the collector to use our code instead of calling the constructor without control attributes:
public override void ProcessGeneratedCode (CodeCompileUnit codeCompileUnit,
CodeTypeDeclaration baseType,
CodeTypeDeclaration derivedType,
CodeMemberMethod buildMethod,
CodeMemberMethod dataBindingMethod)
{
codeCompileUnit.Namespaces [0] .Imports.Add ( new CodeNamespaceImport ( «Sapphire.Web.UI»));
ReplaceConstructorWithContainerResolveMethod (buildMethod);
base .ProcessGeneratedCode (codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod);
}
most interestingly hides the ReplaceConstructorWithContainerResolveMethod
private void method ReplaceConstructorWithContainerResolveMethod (CodeMemberMethod buildMethod)
{
foreach (CodeStatement statement in buildMethod.Statements)
{
var assign = statement as CodeAssignStatement;
if ( null ! = assign)
{
var constructor = assign.Right as CodeObjectCreateExpression;
if ( null ! = constructor)
{
assign.Right =
new CodeSnippetExpression (
string.Format ("SapphireControlBuilder.Build <{0}> ()",
ControlType.FullName));
break ;
}
}
}
}
following the code, you can notice that it replaces the constructor call with the Build generic method call, in which we turn to our container with a request to call our control and initialize its constructor with the necessary dependencies. However, this is not the solution to the task, because there is a method for dynamically loading the Page.LoadControl () control, for it you have to write your own version of the public static class PageExtensions
{
public static UserControl LoadAndBuildUpControl ( this Page page, string virtualPath)
{
var control = page.LoadControl (virtualPath);
return SapphireControlBuilder.Build
}
}
So we coped with the task, but that's not all. Why not take full advantage of Unity now and inject runtime into our AOP control using Unity Interception ? For example, we can do the following public class MyControl: UserControl
{
[HandleException]
public override void DataBind ()
{
base .DataBind ();
}
}
This will mean that exception handling should be added on the fly, besides giving us the opportunity to change it at runtime, for a start let it be implemented as follows [AttributeUsage (AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = true )]
public class HandleExceptionAttribute: HandlerAttribute
{
public override ICallHandler CreateHandler (IUnityContainer container)
{
return new ExceptionHandler ();
}
}
public class ExceptionHandler: ICallHandler
{
///
{
var result = getNext () (input, getNext);
if (result.Exception == null )
return result;
throw new SapphireUserFriendlyException ();
}
public int Order { get ; set ; }
}
Well, of course, we need to configure the container to create our proxy handlers public static T Build
{
return (T) ((Application) HttpContext.Current.ApplicationInstance)
.Container
. AddNewExtension
.Configure
.SetInterceptorFor
.Container
.Resolve
}
Resources
Sapphire.Application - for what it was all implemented http://github.com/butaji/Sapphire/tree/master/trunk/Sapphire.Application/David offers implementations of the next generation data binding “Databinding 3.0” based on a similar http: / /weblogs.asp.net/davidfowler/archive/2009/11/13/databinding-3-0.aspx