📜 ⬆️ ⬇️

How to make friends ASP.NET Controls and 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 code (* .ascx, * .aspx) and found that you can make very interesting decisions that I want to tell about. So today we will learn how to make friends with our Dependency Injection container with the generated control code.

Go

DependencyInjection Microsoft Unity will act as a DI-container, but it doesn’t matter, everything that will concern DI does not depend on the container used. The problem is the following - there is some ASP.NET Control in which we want to inject dependencies, as well as use the services of the Service Locator to manage dependencies of interest to us. In Microsoft Unity, there are some means to do this without making any special efforts: we can inject into the property of the control that interests us roughly as follows:
  1. Mark Dependency attribute required property
    public class MyControl : UserControl
    {
    [Dependency]
    public MyPresenter Presenter
    {
    get { return _presenter; }
    set
    {
    _presenter = value ;
    _presenter.View = this ;
    }
    }
    }

  2. The control can be initialized as follows.
    protected override void OnInit (EventArgs e)
    {
    base .OnInit (e);
    _container.BuildUp (GetType (), this );
    }
    ')
  3. Taking care of the location of the container in your application, I propose to use HttpApplication for this, inheriting from which and making small modifications to the global.asax file , we get the storage we need for the container, we need to handle it in the following way ((Sapphire.Application) HttpContext.Current .ApplicationInstance) .Container

The solution is quite suitable, but the purist views do not allow to leave the solution at this stage, and I think that it is just necessary to replace the injection properties with the injection into the constructor, all the more so this approach is not something that we can squeeze out of Unity. Those. our interest is that the MyUserControl class looks something like this (I think the page builder doesn’t quite like it)
public class MyControl : UserControl
{
public MyControl (MyPresenter presenter)
{
_presenter = presenter;
_presenter.View = this ;
}
}

I suggest this and do. Let's start with the fact that the controls described in the markup of the page, when generating the page, indicate their constructors without parameters, I wonder how you can manage this process, initially, digging into web.config, I intended to do it through:
<buildProviders>
<add extension = ".aspx" type = "System.Web.Compilation.PageBuildProvider" />
<add extension = ".ascx" type = "System.Web.Compilation.UserControlBuildProvider" />
...
</ buildProviders>

However, the implementation of your PageBuildProvider is a rather serious exercise, I think to postpone it for a serious need. However, thanks to BuildProvider, you can generate, for example, a data access layer, for this you need to: Write and register a handler for some of your extensions, for example * .dal, and do something like http://www.codeproject.com/ KB / aspnet / DALComp.aspx by the way, this logic is implemented in SubSonic http://dotnetslackers.com/articles/aspnet/IntroductionToSubSonic.aspx is also an interesting implementation of the inheritance of the page from generic types http://stackoverflow.com/questions/1480373/generic -inhertied-viewpage-and-new- property is still possible, for example to generate an exception, data objects, and more, whether the restriction is s your imagination. In general, this option does not suit us, it is necessary to make something simpler, and there is an excellent solution. Using the ControlBuilder attribute, we can specify our control assembly logic from the markup, it will look something like this
[ControlBuilder (typeof (MyControlBuilder))]
public class UserControl : System.Web.UI.UserControl
{
}

Now let's deal with the implementation of MyControlBuilder, this type should inherit from ControlBuilder and with the help of ProcessGeneratedCode overload we can tell the builder 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 method
private void 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 note that it replaces the constructor call with the call to the generic Build method, in which we will ask our container to call our control and initialize its constructor with the necessary dependencies. However, this is not the solution of the task, there is a method for dynamically loading the Page.LoadControl () control, for it will have to write its own variant public static class PageExtensions
{
public static UserControl LoadAndBuildUpControl ( this page page, string virtualPath)
{
var control = page.LoadControl (virtualPath);
return SapphireControlBuilder.Build <UserControl> (control.GetType ());
}
}

So we coped with the task, but that's not all. Why not take advantage of all the benefits of Unity now, and not embed 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, first let its implementation be something like the following [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
{
/// <exception cref = "SapphireUserFriendlyException"> <c> SapphireUserFriendlyException </ c>. </ exception>
public IMethodReturn Invoke (IMethodInvocation input, GetNextHandlerDelegate getNext)
{
var result = getNext () (input, getNext);
if (result.Exception == null )
return result;
throw new SapphireUserFriendlyException ();
}

public int Order { get ; set ; }
}

And of course, you need to configure the container to create our proxy handlers public static T Build <T> ()
{
return (T) ((Application) HttpContext.Current.ApplicationInstance)
.Container
. AddNewExtension <Interception> ()
.Configure <Interception> ()
.SetInterceptorFor <T> ( new VirtualMethodInterceptor ())
.Container
.Resolve <T> ();
}



Resources

Sapphire.Application - what was all this implemented for http://github.com/butaji/Sapphire/tree/master/trunk/Sapphire.Application/

David proposes the implementation of the next generation data binding “Databinding 3.0” based on a similar approach http://weblogs.asp.net/davidfowler/archive/2009/11/13/databinding-3-0.aspx

Source: https://habr.com/ru/post/75353/


All Articles