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

    DependencyInjectionMicrosoft Unity will act as a DI container , but this is not important, 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 the dependencies of interest to us. There are some tools at Microsoft Unity to do this without much effort: we can inject into a property of the control that interests us roughly like this:
    1. 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 ;
                  }
              }
      }

    2. The control can be initialized as follows
      protected  override  void  OnInit (EventArgs e)
      {
          base .OnInit (e);
          _сontainer.BuildUp (GetType (),  this );
      } 

    3. 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

    The solution is quite suitable, but purist views do not leave the solution at this stage, and I think that it is just necessary to replace the property injection with an injection into the constructor, especially since this approach is far from what we can squeeze out of Unity. Those. our interest is to make the MyUserControl class look something like this (I think the page  builder will not like it)
    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:

         extension = ". aspx" type = "System.Web.Compilation.PageBuildProvider" />
         extension = ". ascx" type = "System.Web.Compilation.UserControlBuildProvider" />
        ...


    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(control.GetType ());
        }
      }

    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
      {
        /// SapphireUserFriendlyException.
        public  IMethodReturn Invoke (IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
          var result = getNext () (input, getNext);
          if  (result.Exception ==  null )
            return  result;
          throw  new  SapphireUserFriendlyException ();
        }

        public  int  Order  {  getset}
      }

    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( new  VirtualMethodInterceptor ())
            .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

    Also popular now: