.NET 时代里的AOP

   我们将再次访问与第一个程序结构相关的许多概念,以及具体代码都放到了本文中。如果已经很熟悉MVC设计模式,可以直接看后面的内容。

MVC设计模式
   在这个模式下面,应用程序中有三个清晰分明的关注点。
   • Model(模式):这是此程序结构的一部分,此程序结构包含你的商业实体的数据以及商业实体的行为。这是唯一一个管理数据库的模式,例如:执行某种行为。
   • View(视图):这是显示(或者输出)模式信息的部分,被窗体以及它的控件代表。
   • Controller(控制器):视图和模式之间的交流被控制器隔离。因此当视图需要执行模式上的操作时,它将向控制器请求它。当它需要显示模式的数据时,它将向控制器请求模式。

   这个分离导致创建了一个松散连接着的程序结构,构件可以在此程序结构里独立发展,此程序结构上的其中一个改变不会影响其它改变,并且会增加可维护性。更重要的是:差别性视图重新使用模式中的同一个代码。依靠设计控制器的方法,它也可以被重新使用。我们将实现的程序结构将拥有以下交流:


    这个模式已经被实现且修改了多次,仍然有一些追求者肯定会承认它是一个“真的”MVC。在更传统的概念中,这个视图管理模式中的数据显示,以pull-方式来显示这些数据。在我们的实现中,实际上,控制器将数据推向视图 。在web方案中这一点可能更有效,因为不需要打扰桌面应用程序的可适应性。

   为了使这个模式能在Windows窗体 和Web窗体视图中都是可以利用的,我们实现另一个模式,转接器,它将处理更新正确的用户界面:



   这个方法的好处就是:在视图技术间能重新使用同一个控制器。通过实现这个谜语的核心部分:控制器和视图之间的视图映射,我们将开始实现,并且研究集成开发环境功能。

.NET 时代的面向方面的程序设计

    这里有许多映射两个构件的方法,其中一个是使用带有数据的XML文件,另一个就是把这些映射放到数据库中。然而,两者(其它也是一样)有一些非常重要的缺点。
   • 映射文件/储存成为可维护性的另一方面。普通
  • 映射的下载和解析成为一个高负荷的问题
  • 与一般的拖曳&拖曳-控制-设置-属性-运行开发模式有很大的背离
   避免这些问题的一个方法就是扩展嵌入式控件,实现控件自己的映射设置。仅仅数出嵌入式Web 和Windows窗体控件的数量就是一项艰巨的工作外,映射功能中的任何改变将要求对控件代码作出修改,可是这好像不是一个好办法。除了这些,可能我们不能继承第三方已经获得的控件。

    VS.NET支持一个扩展器的概念,扩展器的概念是一个构件,它扩展来自于外部的现有构件的功能集,既不要求继承,也不要求内含包容,或者甚至会访问被扩展构件的任何内部信息。这个扩展性模式通常被称为面向方面程序设计,因为它允许我们简单地增加,并且从外面将方面移出到现有构件。这种技术有它自己的网页,网络上可能有很多关于它的文章。在MSDN上可以找到与此主题相关的一篇有趣的文章,尽管它的方法接受自定义属性的途径。

   面向方面的程序设计的组件方法是System.ComponentModel命名空间里的IExtenderProvider接口和ProvidePropertyAttribute类型。这个属性必须被应用到构件,此构件将扩展其它构件。

  
  
[ProvideProperty("WebViewMapping", typeof(System.Web.UI.Control))]
public class BaseController : Component, IExtenderProvider

   这个属性对VS.NET所说的就是:构件(Base控制器)给根组件 (一个Web 窗体)里的类型System.Web.UI.Control 的所有构件提供一个属性WebViewMapping。我们可以提炼控件:

bool IExtenderProvider.CanExtend(object extendee) 
{
return true;
}

   我们总是说支持Control-继承对象(在这个层,早已传递了ProvideProperty过滤器)。对于根组件,映射没有什么意义,也就是说,Page对象它自己是从Control继承而来的。正如我们在开始所谈到的,为了证明这个情况,我们可以利用IDesignerHost服务,通过调用GetService方法,我们可以从构件直接访问IDesignerHost服务,正如在开始所讨论的一样。

bool IExtenderProvider.CanExtend(object extendee) 
{
//Retrieve the service.
IDesignerHost host = (IDesignerHost) GetService(typeof(IDesignerHost));
//Never allow mappings at the root component level (Page or Form).
if (extendee == host.RootComponent)
{
return false;
}
else
{
return true;
}
}

   GetService的Component类型的实现简单地向Component.Site属性值对象发出了要求,此要求就是DesignSite,DesignSite包含几个其它服务。

    为了使它能够有效工作,必须实现接口和属性,意识到这一点非常重要。最后的部分就是大量附带有特殊命名惯例的方法,这些方法必须存在在扩展器构件中。

/// <summary> 
/// Gets/Sets the view mapping that is used with the control.
/// </summary>
[Category("ClariuS MVC")]
[Description("Gets/Sets the view mapping that is used with the control.")]
public string GetWebViewMapping(object target)
{
//Retrieve a mapping
}

/// <summary>
/// Sets the mapping that applies to this control.
/// </summary>
public void SetWebViewMapping(object target, string value)
{
//Store the mapping
}

    命名惯例就是:Get/Set +[使用在ProvidePropertyAttribute中的属性名称]。GetXX返回值必须与SetXX方法的value参数相匹配。如果只实现目前为止三所显示的代码(外加private领域来保存值),我们将Base控制器放在Web窗体上,在属性浏览器中将看到下面的新属性被放到了页面的任何控件中。



   注意:使用被应用在GetWebView Mapping方法的Category 以及Description属性时,就好像它们被应用到一个常规属性一样。Get总是对所有通常属性特性负责。

    因为属性的状态被保存在控件外面,在构件内部,target参数(两种方法都接受到的)允许我们决定属性被访问的对象。我们通常用一个被配置的属性为每一个控件保存散列表。而且,仅仅一个单值不足以保存我们的信息,因此我们创建另一个类型来保存设置。在我们的情况中,此类型就是ViewInfo类型。这个类型包含下面的属性:ControlID, ControlProperty, Model 以及ModelProperty.它们都是字符串,将被用于与被配置的控件来同步化模式值。如果属性不是一个简单的类型,通过指定一个附带有类型的特殊类型传换器,我们可以提供改良的属性浏览器集成。

[TypeConverter(typeof(ExpandableObjectConverter))] 
public class ViewInfo

   System.ComponentModel命名空间里有这个传换器,这个转换器促使属性浏览器显示如下属性:


可以扩展这个属性,并且在属性浏览器里直接配置此属性。属性名称下面被显示的自定义字符串表示法是一个简单的覆盖类型ToString方法的方法。

public override string ToString() 
{
if (_controlproperty == String.Empty &&
_model == String.Empty && _modelproperty == String.Empty)
return "No mapping configured.";
else
return _model + "." + _modelproperty + " -> " +
_controlid + "." + _controlproperty;
}

   为了访问当前控件的属性可以使用下面的代码。

public ViewInfo GetWebViewMapping(object target) 
{
return ConfiguredViews[((System.Web.UI.Control)target).ID] as ViewInfo;
}

public void SetWebViewMapping(object target, ViewInfo value)
{
ConfiguredViews[((System.Web.UI.Control)target).ID] = value;
}

   Configured Views是类型Hashtable的一个属性,此类型Hashtable能保存映射。注意被扩展的属性仅仅像集成开发环境的另一个成员,也像代码中的可持续性。因此现在集成开发环境不知道怎样保存控件中新的Web Views Mapping属性,它也不知道怎样保存构件自己的Configured Views属性。在下一章中将讨论怎样发送自定义代码来保存这些值。为了通知VS.NET,它应该忽视持续性(被称为序列化)过程中的这些属性:

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]

  
  

   这个属性仅仅应用到GetXX方法,此GetXX方法是对属性负责的方法。
   过去具体使用的ProvideProperty属性声明扩展web控件:

[ProvideProperty("ViewMapping", typeof(System.Web.UI.Control))] 
[ProvideProperty("ViewMapping", typeof(System.Windows.Forms.Control))]
public class BaseController : Component, IExtenderProvider

   实际上这一点是行不通的。即使让第一个扩展器使用“wins”.也就是说,如果打开一个附带有控制器的Windows窗体,并且定义一些映射,Web窗体控件将不再为整个VS.NET过程查看被扩展的属性。为了再一次得到Web窗体中的被扩展的属性,我们必须关闭并且重新打开VS.NET。但是,如果Web 窗体“wins”, Windows窗体就失败了。因此为每一个实现Get/Set。

[ProvideProperty("WinViewMapping", typeof(System.Windows.Forms.Control))] 
[ProvideProperty("WebViewMapping", typeof(System.Web.UI.Control))]
public class BaseController : Component, IExtenderProvider

   所有效地完成的就是为现有的控件增加功能,但是实际上并没有接触它们。我们需要一个方法来保存这些被配置的值,因为现在关闭窗体时,就失去了所有的值。

用户代码序列化:CodeDom的力量

   VS.NET中的主要对象持续性机制是通过直接的代码发送来处理的。在所有Web和Windows Forms包含的InitializeComponent方法中,你已经看到了这一现象。在Component-继承类型中也显示了这一过程。两个属性决定了这一行为:System.ComponentModel.Design.Serialization.RootDesignerSerializerAttribute以及System.ComponentModel.Design.Serialization.DesignerSerializerAttribute.,正如在开始讨论到的DesignerAttribute,在根和标准串化器之间有一个明显的区别。但是不像DesignerAttribute,总是利用标准(非-根)串化器,当构件同时是被设计的根构件时,要额外利用根串化器。通常只是定制非-根串化器。此非-根串化器的指示器就是:IComponent接口已经包含根串化器。

[RootDesignerSerializer(typeof(RootCodeDomSerializer), typeof(CodeDomSerializer))] 
public interface IComponent

   但是它没有提供设计常规串化器的属性。但是,IComponent的特殊实现提供这个属性。例如:

[DesignerSerializer(typeof(Microsoft.VSDesigner.WebForms.ControlCodeDomSerializer)), 
typeof(CodeDomSerializer))]
public class System.Web.UI.Control : IComponent

   以及

[DesignerSerializer(typeof(System.Windows.Forms.Design.ControlCodeDomSerializer)), 
typeof(CodeDomSerializer))]
public class System.Windows.Forms.Control : Component

   注意:两者都有它们独特的串化器,因为Windows窗体被保存到代码的方法与Web 窗体被保存到代码的方法有很大的区别。前者串行化InitializeComponent方法的所有值和设置,然而后者仅仅储存在代码隐藏事件处理程序连接附件中,因为控件属性被保存在aspx页面中。

   你肯定注意到不管控件是用在VB.NET项目中还是用在一个C#项目(或者是可用于此目的的其它语言)中,InitializeComponent总是用正确的语言发送代码。因为.NET中一个新的功能(此功能被称为CodeDom. )CodeDom是一个类型集,此类型集允许我们输写对象层次结构 ,这些对象层次结构代表更加的普通语言构造,例如:类型,领域以及属性宣告,事件连接附件,try..catch..finally块等等。它们允许我们创建一个所谓的预期目标代码的abstract syntax tree (AST)。abstract syntax tree (AST)是抽象的,它不代表VB.NET或者C#代码,但是代表constructs它们自己。

    串化器传递到集成开发环境的东西就是包含代码的ASTs,这正是它们期望保存的。集成开发环境依次创建一个System.CodeDom.Compiler.CodeDomProvider -继承类型,System.CodeDom.Compiler.CodeDomProvider -继承类型与当前项目完全匹配,例如:Microsoft.CSharp.CSharpCodeProvider 或者Microsoft.VisualBasic.VBCodeProvider.此对象最后负责以具体语言代码传输AST,而这些具体语言代码早已被嵌入到了InitializeComponent方法中。

    CodeDom并不是非常复杂,让我们来迅速地学习一下CodeDom。

CodeDom句法

    最好的方法是通过例子来学习CodeDom,因此来看一下某种C#代码,以及它对等的CodeDom语句(我们假设它们都发生在类型内部)。被下载的代码包括一个项目,以此来检查CodeDomTester文件夹中的CodeDom。它是一个简单的控制器应用程序,在此控制器应用程序上有两个骨架方法:GetMembers和GetStatements。可以把样本CodeDom代码放到这两个方法中,看一下输出的结果。
C#:

private string somefield;

  
  

CodeDom:

  
  
CodeMemberField field = new CodeMemberField(typeof(string), "somefield");

   所有类型层成员表示法CodeMemberEvent, CodeMemberField, CodeMemberMethod and CodeMemberProperty,都是从CodeTypeMember继承来的,默认地拥有private和final属性。

C#:

public string somefield = "SomeValue";

  
  

CodeDom:

  
  
CodeMemberField field = new CodeMemberField(typeof(string), "somefield");
field.InitExpression = new CodePrimitiveExpression("SomeValue");
field.Attributes = MemberAttributes.Public;

C#

this.somefield = GetValue();

  
  

CodeDom:

CodeFieldReferenceExpression field = new CodeFieldReferenceExpression( 
new CodeThisReferenceExpression(), "somefield");
CodeMethodInvokeExpression getvalue = new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(), "GetValue", new CodeExpression[0]);
CodeAssignStatement assign = new CodeAssignStatement(field, getvalue);

   注意:实际上冗长的程度按指数倍增加。并且注意C#代码中的GetValue()方法对此有一个隐式引用,在CodeDom中必须是显示的。

C#

  
  
this.GetValue("Someparameter", this.somefield);

CodeDom

CodeMethodInvokeExpression call = new CodeMethodInvokeExpression(); 
call.Method = new CodeMethodReferenceExpression(
new CodeThisReferenceExpression(), "GetValue");
call.Parameters.Add(new CodePrimitiveExpression("Someparameter"));
CodeFieldReferenceExpression field = new
CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "somefield");
call.Parameters.Add(field);

   我们调用同一方法的一个假定超载。注意首先创建方法调用表示法,然后为它(指向this)指定一个方法引用和一个方法名称。下面附加两个参数,第二个单元是这个领域的引用。

    如果想避免无尽的而且没用的变量声明,不采用临时变量就可以创建语句。这使得代码不那么清晰易读,但是更加紧凑。创建这些语法的一个好技术就是考虑目标代码,从内部向外部生成目标代码。例如在上面的代码中。

this.GetValue("Someparameter", this.somefield);

  
  

   首先创建参数,然后考虑方法引用,一旦想做这些工作,写下下面这些东西:

CodeMethodInvokeExpression call = 
new CodeMethodInvokeExpression(new CodeThisReferenceExpression(),
"GetValue",
new CodeExpression[] { new CodePrimitiveExpression("Someparameter"),
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),
"somefield")});

    我们看到的最后情况是一个this.somefield,然后是原始表示法。这是作为方法调用的参数阵列的初始化表示法而传递的。然后你得到this.somefield,最后this.somefield引发实际调用。

    注意正确的缩排可以提供巨大的帮助,但是大部分工作还是需要你自己完成,尤其是一些附带有巢状层次的工作。为了达到一些合法性,最重要的巢状就是阵列初始化(前面所提到的)。也推荐把预期C# (or VB.NET)输出代码放到多线语句上面,因此每个人都知道你试图发送什么。

    这些是定义所有跨语言功能的类型。但是让我们看一下需要给被扩展属性提供的具体持续性代码。

发送CodeDom

    我们将需要把一个自定义串化器与Base控制器连接起来,为了定制持续性,以及发送代码来保存视图 映射以及我们需要的任何潜在代码。

  
  
[DesignerSerializer(typeof(ControllerCodeDomSerializer),
typeof(CodeDomSerializer))]
public class BaseController : Component, IExtenderProvider

    自定义串化器必须从CodeDomSerializer继承而来。这个基本抽象类型(此基本抽象类型位System.ComponentModel.Design序列化)包含必须实现的两个抽象方法。

public abstract class CodeDomSerializer 
{
public abstract object Serialize(
IDesignerSerializationManager manager, object value);
public abstract object Deserialize(
IDesignerSerializationManager manager, object codeObject);
}

    无论什么时候,对象需要被保存时,Serialize方法都会被调用。返回值必须是类型CodeStatementCollection(此类型包括代码来保存)的一个对象。同样的,Deserialize方法中的codeObject参数包括前面被发送的语句。

    在概述中,我们就谈到示例根组件,通过根设计器来设计它。在根组件世界中,几乎所有构件(以及控件)中都会发生这一过程。真正发生的却是集成开发环境 执行InitializeComponent中的大部分代码,重新创建对象,就像它们处在运行库里一样。我们说大部分而不是所有是因为仅仅修改问题中的构件的语句才被调用:例如:它们中的属性设置以及方法调用.通过定制Deserialize方法,我们有机会在设计时期重建过程中互相作用。通常这是没必要的,因此大部分时间我们就把球传到初始构件串化器ComponentCodeDomSerializer,此ComponentCodeDomSerializer基本上实现此代码。为了得到类型的串化器,我们使用我们接受到的IDesignerSerializationManager参数的GetSerializer方法。此对象有其它有用的方法,我们将在后面使用它们。

    因此Deserialize实现通常是这样的:

public override object Deserialize( 
IDesignerSerializationManager manager, object codeObject)
{
CodeDomSerializer serializer =
manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer));
return serializer.Deserialize(manager, codeObject);
}

    重新找回构件初始串化器是管理器的普通使用。因为反序列化通常是一样的。我们将把它放在基本类型,并且将从控制器串化器生成基本类型。

  
  
internal abstract class BaseCodeDomSerializer : CodeDomSerializer
{
protected CodeDomSerializer GetBaseComponentSerializer(
IDesignerSerializationManager manager)
{
return (CodeDomSerializer)
manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer));
}

public override object Deserialize(
IDesignerSerializationManager manager, object codeObject)
{
return GetBaseComponentSerializer(manager).Deserialize(manager,
codeObject);
}
}

   后面将为这个类型补充其它的公共方法。现在需要进行串行化过程,我们需要通过Hashtable的所有DictionaryEntry元素来迭代,发送下面的代码,为了保存ConfiguredViews属性。

controller.ConfiguredViews.Add("txtID", 
new ViewInfo("txtID", "Text", "Publisher", "ID"));

   另一个公共的方法就是让初始构件串化器执行它自己的工作。然后补充我们的自定义语句。通过这种方法,我们避免了自己保存公共构件属性。因此串化器开始执行这些任务:

internal class ControllerCodeDomSerializer : BaseCodeDomSerializer 
{
public override object
Serialize(IDesignerSerializationManager manager, object value)
{
CodeDomSerializer serial = GetBaseComponentSerializer(manager);
if (serial == null)
return null;
CodeStatementCollection statements = (CodeStatementCollection)
serial.Serialize(manager, value);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值