2007年8月1日

ASP.NET 2.0:使用用户控件和定制的Web部件个人化你的门户网站

ASP.NET 2.0:
使用用户控件和定制的Web部件个人化你的门户网站

原著:Ted Pattison,Fritz Onion
翻译:汪泳
原文出处:ASP.NET 2.0 Personalize Your Portal with User Controls and Custom Web Parts

本文基于 ASP.NET 2.0 的预发行版本,文中提供的所有信息将来都可能发生变化。

本文将讨论以下内容:

  • 使用Web部件创建模块化的Web门户应用;
  • 个人化特性和自定义特性;
  • 将自定义用户控件作为Web部件使用;
  • 创建一个个人化特性的提供程序;

  现今门户应用非常流行,好的门户都有共同而显著的特点。那就是都会给访问者提供雅观的信息,并且这些信息都是通过模块化的、一致的、易于浏览的用户界面提供的。一些综合性的门户网站走得更远,它们甚至允许网站成员提供内容、上传文档以及个人化门户页面。
  微软为 Windows Server 2003 平台增加了一个可扩展的门户应用框架,随之发布了一个 Windows SharePoint 服务 ,这个框架提供了门户应用框架必须的一些基本元素,其中包括站点成员的支持、内容和文档管理、使用 Web 部件以模块化的形式展示数据等等。
  Web 部件提供了支持自定义特性和个人化特性的基础功能。在Windows SharePoint服务网站里面,通过配置站点,门户应用的用户能够添加、配置、删除Web部件,这样他们就能轻松地个人化或者定制页面了。基于Windows SharePoint服务的站点还提供了一种简便而且强大的方法扩展站点的功能,那就是:开发自定义Web部件。创建支持定 制特性和个人化特性的Web部件时,你只需要简单地在你的Web部件类里面增加一些属性以及设置几个特殊的标签就可以了。那些繁琐复杂的工作都由 Windows Sharepoint 服务的Web部件基础结构来完成,比如:序列化、存储和读取与站点自定义特性和成员个人化特性相关的数据。
  ASP.NET 2.0 引入了一套Web部件控件集,这套控件集与 Windows SharePoint 服务提供的功能很相似,它们 被设计用来完成序列化、存储和读取站点自定义特性和成员个人化特性相关数据等功能。但是它们更独特和更灵活,它们与 SQL Server 或 Active Directory 不是紧耦合的。对于那些希望使用基于表单验证技术建立门户,或者不想受限于某一特定数据库解决方案的公司来讲,这无疑是一个好消息。

 
图1:使用模块化Web部件设计的一个示例门户应用

  本文将向你展示一个用  ASP.NET 2.0 Web 部件开发的示例门户应用,其主要目的是让你了解为门户应用开发Web部件时 ,你将面临的一些重要的设计问题。首先,我们将着重介绍新的 ASP.NET 2.0 Web部件控件集涉及的一些基本概念和控件类型。例子参见图1。

Web部件基础

  用来放置Web部件的页面,我们可以称之为Web部件页面。如图2所示,一个Web部件页面需要一个WebPartManager 控件(只能有一个)和一个或多个的 WebPartZone 控件。还可以包括一个 EditorZone 控件或者 CatalogZone 控件(不是必需的)。需要注意的是,在.aspx文件中,WebPartManager 控件的标签必须出现在与Web部件 基础结构相关的任何其它控件标签之前,比如:WebPartZone 控件、EditorZone 控件、CatalogZone 控件。为了更好地控制Web部件页面的布局和表现形式,你还可以在aspx文件中使用HTML表格,将不同的 zone 控件布局到不同的地方。


图2:一个Web部件页面的典型布局

  我们先来看一个简单的Web部件页面例子,该页面包含 WebPartManager 控件和 WebPartZone 控件:

<asp:WebPartManager ID=" WebPartManager1" runat="server" />
            <asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1">
            <ZoneTemplate>
            <!-- time to add a Web Part -->
            </ZoneTemplate>
            </asp:WebPartZone>      

  在页面里放置了一个 WebPartZone 控件之后,就可以使用Web部件定义来创建Web部件实例了。有二种不同的方法可以创建Web部件定义。第一种方法是创建一个从 WebPart 类继承的类,第二种方法是创建一个用户控件。 本文稍后的部分,我们将详细探讨这两种方法之间的不同。现在,我们先创建一个从WebPart类继承的简单的类(参见图3)。
  每个Web部件实例都存在于一个页面特定的 WebPartZone 控件的某个索引位置上。一个 WebPartZone 控件可以包含多个Web部件。如图4所示,在 WebPartZone1 控件里面有二个Web部件实例。


图4:在一个特定zone控件中的Web部件

  你可以通过编程方式和声明方式将Web部件添加到某个区(zone)中。稍后你还可以看到如何将Web部件添加到部件目录中。当你创建了一个 CatalogPart 控件以后,用户就可以在运行时将一个新的Web部件添加到WebPartZone控件中。
  以编程方式将Web部件添加到 WebPartZone 控件里面的方法取决于需要添加的Web部件的类型。如果是一个从 WebPart 继承的类,需要以编程方式创建一个 该类的实例,然后调用 WebPartManager 类的 AddWebPart 方法。调用 AddWebPart 方法时,你需要传入的参数包括:Web部件的实例、 目标 WebPartZone 控件、以及Web部件在 WebPartZone 控件中的索引位置。代码如下:

// create Web Part instance from WebPart-derived class
            WebPart wp1 = new WingtipWebParts.HelloWorld();
            WebPartManager1.AddWebPart(wp1, WebPartZone1, 0);      

  此即通过编程方式将Web部件添加到 WebPartZone。声明方式是在Web部件页面的.aspx文件中定义控件标签。当用户访问页面时,如果你希望某个Web部件出现在特定的 WebPartZone 控件中,你可以在 WebPartZone控件中添加一个 ZoneTemplate。代码如下:

<%@ Register Assembly="WingtipWebParts" Namespace="WingtipWebParts"
            TagPrefix="Wingtip" %>
            <asp:WebPartManager ID=" WebPartManager1" runat="server" />
            <asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1">
            <ZoneTemplate>
            <Wingtip:HelloWorld runat="server" id="HelloWorld" />
            </ZoneTemplate>
            </asp:WebPartZone>      

  别忘了给Web部件“打扮”一下,因为如果不这样的话,你的Web部件看起来会很乏味的。内联门户网应用需要的一个通用特性就是能够给站点“换肤”,根据访问者的喜好改变Web部件的 外观。过去,这意味着你要自己创建一套架构去支持改变控件呈现方式的功能,而这需要做大量的工作。ASP.NET 2.0 引入了“主题”的概念,其中包括一系列的风格属性和控件属性,这些属性可以应用到单个控件、整个页面甚至整个应用的全局范围。
  为了让你的门户应用看起来显得优雅和专业,你需要定制所有 WebPartZone 控件、EditorZone 控件和 CatalogZone 控件的 外观。当你刚开始做这件事的时候,你会觉得这种工作非常乏味。因为你需要给Web部件、Editor部件以及Catelog部件的内容区、标题栏和动态菜单等等区域换肤和修改显示属性。幸运的是,新的ASP.NET 2.0 的主题特性可以将这些.aspx文件中有关界面工作的成果提取到可以重用的.skin文件和.css文件中。这篇文章的示例门户应用程序就是使用换肤和主题功能来定制Web部件的 外观的,你可以到MSDN杂志的网站上下载。

显示模式和页面范围

  WebPartManager 控件的一个单一实例运行在每个Web部件页面中,它负责管理Web部件实例以及Web部件如何与WebPartZone控件交互。WebPartManager控件还提供了一个可编程接口,可以用来切换Web部件页面的显示模式,例如:你可以在以下 三种模式之间切换:浏览模式、设计模式和编辑模式。例如:要通过程序将当前页面切换到设计模式,你只需要添加一个链接控件,在这个控件的事件处理程序中将DisplayMode属性设置为DesignDisplayMode即可。代码如下:

WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode;      

  理解Web部件页面每种显示模式之间的区别是很重要的。默认情况下,Web部件页面处于浏览模式,此模式不允许用户修改任何的Web部件。当切换到设计模式的时候,用户就能够在WebPartZone内部或者不同的 区之间移动Web部件了。图5列出了所有的显示模式。
  ASP.NET 2.0 Web部件控件会在后台生成所有必需的DHTML和Javascript代码,让用户在浏览器里可以进行拖放操作。在不支持必 需的DHTML和Javascript特性的浏览器里,除了拖放操作之外的所有其它编辑功能都可以使用。这意味着你不必担心要强制所有的客户使用Microsoft IE5.0或以上的浏览器。ASP.NET 2.0 Web部件控件还支持管理个人化数据的存储和获取,能够记住用户上一次将Web部件放在了什么地方。
  除了支持使用代码切换显示模式之外,WebPartManager控件还提供了允许Web部件页面在用户范围和共享范围之间切换的方法。页面范围是指对Web部件的修改是自定义操作还是个人化操作。所有的用户都能看到自定义操作的结果,而只有用户自己才能看到个人化操作的结果。改变Web部件页面的范围可以通过调用WebPartManager 控件的 ToggleScope 方法来实现,代码如下:

WebPartManager1.Personalization.ToggleScope();

  默认范围即用户范围,也就是说对Web部件的修改被记录为个人化操作,只能被当前用户看到。成功地调用ToggleScope方法会把Web部件页面改变到共享范围,这样的话,对Web部件的修改就会被记录为自定义操作。共享范围的作用是为了允许管理员或者站点设计人员统一修改Web部件页面中的Web控件,让所有用户都能看到这种改变。如果有冲突,个人化修改总是优先于全局自定义操作。
  当前用户并不总是能够成功地进入共享范围。默认情况下,任何用户都没有这样的权限。只有在Web.config文件中用户被赋予了这样的权限才行。下面是一个例子,它允许所有拥有admin或者site_designer角色的用户可以进入共享范围和修改全局自定义数据:

<webParts>
            <personalization>
            <authorization>
            <allow roles="admin, site_designer" verbs="enterSharedScope" />
            </authorization>
            </personalization>
            </webParts>      
属性和个人化

  每一个Web部件对象都有一套标准的属性能够被自定义或者个人化。例如,每个Web部件都有一个Title属性,在被加入WebPartZone之后,还能够被自定义。EditorZone控件和一些Editor部件允许用户对Web部件属性做修改。
  当用户将Web部件页面切换为编辑模式时,Web部件的菜单提供了一个编辑命令。在一个Web部件上调用编辑命令会显示出一个EditorZone控件,此控件是放在相关的 区控件中的。ASP.NET 2.0提供了一些内置的Editor部件,用来修改标准的Web部件外观、行为和布局。
  通过添加一些能够被个人化的自定义属性,Web部件个人化配置数据能够被轻松地扩展。你只需要在Web部件类的定义里面添加这些属性,并且打上诸如:Personalizable、WebBrowsable、WebDisplayName之类的特性标签就可以了。当你完成这些之后,Web部件控件会帮助你存储和获取这些被个人化或者自定义的属性值。
  当你创建某个个人化属性时,通常会同时定义一个私有字段,代码如下:

private bool _HR = true; 
[Personalizable(PersonalizationScope.User), WebBrowsable, WebDisplayName("
            Show HR News"), WebDescription("
            Use this property to show/hide HR news")] 
public bool HR { get { return _HR; } set { _HR = value; } }

  如果你想允许你的用户像这样个人化某个属性,你只需要在当前页面的EditorZone控件中添加一个PropertyGridEditorPart部件就可以了。图6显示了当你这样做的时候,用户会看到什么:


图6 Editor 部件允许用户个人化 Web 部件

  如果你定义的某个Web部件属性是字符串或者数字类型的,PropertyGridEditorPart 部件会提供一个文本框让用户修改属性值。如果这个属性是布尔类型的,PropertyGridEditorPart部件会提供一个 复选框,如图6所示。
  图7给出了从 Windows SharePoint 服务Web组件开发中学来的一个极好的编程技巧:你可以定义一个基于枚举类型的个人化属性。
  创建 WebBrowsable 以及基于枚举类型的个人化的属性,其真正价值在于 PropertyGridEditorPart 部件会生成一个包括所有可选属性值的下拉列表,正如图7中显示的 Timeframe 属性那样。对用户来说,这很方便,而且有助于保证用户选择一个有效的属性值。
  关于 Timeframe 属性,还有一件事值得留意。它被加上了一个 Personalizable 特性,特性的值为PersonalizationScople.Shared。当属性像这样被定义为共享属性的时候,它只能被自定义,而不可以个人化。既然共享属性不能被个人化,当当前页面在用户范围的时候,PropertyGridEditorPart 部件不会显示这个属性,而只会在共享范围显示这个属性。

Web部件目录


  我们已经见过了如何在 WebPartZones 控件中事先放入Web部件。你还可以用另外一种方法完成这个功能,那就是允许用户在运行时添加新的Web部件。通过使用 CatalogZone 控件和 CatalogParts 类型的部件,比如 :PageCatalogPart 和 DeclarativeCatalogPart 来达到这个目的。(参见图8
  当你用这个方法添加某个 CatalogZone 和 CatalogPart 之后,用户就可以在运行时动态添加Web部件了,界面就像如图9中所示的那样。


图9:CatalogZones允许用户动态添加Web部件

  首先,你需要理解 PageCatalogPart 的用途。在设计显示模式或者编辑显示模式下,用户能调用Web部件的“关闭”命令。当用户关闭 某个Web部件时,Web部件和相对应的个人化或者自定义配置被保留下来,以便用户可以在以后再次添加该Web部件。因此,PageCatalogPart 控件显示所有已经被关闭的Web部件的列表,用户可以再次加到页面上去。
  “关闭”命令与“删除”命令是不同的。“删除”命令在出现在编辑显示模式。当用户删除一个Web部件时,有关这个部件的所有相关信息,包括自定义和个人化数据,全部会被删除掉。
  DeclarativeCatalogPart 部件能够以声明的方式添加Web部件。图8中的代码说明了怎样定义这个目录,其中使用了一个自定义名字的部件 WeatherWebPart。采用这种方法,你就能够给用户提供各种各样的Web部件了。
  作为本文的补充,同时为了给你提供更多的有关建立Web部件页面的详细信息,我们建议大家去阅读 Stephen Walther 所写的“Introducing the ASP.NET 2.0 Web Parts Framework”一文 。文中提供了更详细的信息,以及使用 EditorZones 和 CatalogZones 建立Web控件页面的完整例子。Stephen 还讲了一些更高级的话题,包括 Verbs、Connections 和 Web 部件的导入导出等内容。

ASP.NET 2.0 门户应用开发

  
除了Web部件自身体系结构之外,在ASP.NET 2.0里面还有一些新的特性使得内部门户网站的开发更加有吸引力。正如文章前面所说,主题和换肤的引入使得风格属性可以方便直接地独立于门户页面,不用修改每个页面,就可以做 统一的风格改变。当然,更令人兴奋的是母版页(Master Pages)的引入。使用母版页,就可以将总体的 WebPartManager 控件和所有 WebPartZones 控件放在一个单独的模版页面,其它页面都可以继承这些基础外观和功能。在示例门户应用中,我们用到的一个有趣的技术就是在 母版页每个 WebPartZone 控件的ZoneTemplate 模版中都添加一个 ContentPlaceholder 控件。这样的话,使用这个 母版页的内容页面可以使用这个内容控件来添加其自己的Web部件,并且映射到相应的 ContentPlaceholder 控件中。
  为一个门户网站设计母版页时,你必须考虑的一件事是如果给用户提供自定义特性。正如你已经看到的,根据页面中包含的不同类型的区控件,有几种不同的修改页面的自定义模式 供让用户选择。
  其中一种给用户显示自定义选项的方法是将 WebPartManager 控件和一系列按钮(典型的是LinkButton)封装为一个用户控件,然后将这个控件放在 母版页中,就可以为网站中所有页面提供自定义选项。如果你的网站有多于一个的主控页面,用来给不同的页面提供不同的布局,封装为一个用户控件(我们可以叫它WebPartManagerPanel)也是很有用的。之前图1中给出的那个门户应用的菜单条就给出了一个示例的用户控件,可以显示出当前 WebPartManager 的显示模式和范围,还提供了一些 LinkButton 将页面改变 为 WebPartManager 控件支持的其中一种编辑模式。(这就是我们示例门户应用使用的用户控件)。
  在你的 WebPartManagerPanel 控件中可以提供的另外一个有用的特性是可以根据当前用户和当前页面显示或者隐藏相应的显示模式菜单项。通过查看 WebPartManager 的 SupportedDisplayModes 这个collection属性中包含哪些支持的显示模式,就可以显示或隐藏相应的显示模式菜单项。例如,要找出当前页面是否支持CatalogDisplayMode,你应该写如下的代码:

if (WebPartManager1.SupportedDisplayModes.Contains(
            WebPartManager.CatalogDisplayMode)) {
            //enable catalog display mode LinkButton here...
            }      

  还应该注意,如果当前用户没有相应的权限,调用 ToggleScope 将会失败。所以通过代码判断一下是否显示或隐藏让用户进入共享范围的界面元素是个好主意,查询 WebPartManager 控件的 Personalization 属性的 CanEnterSharedScope 属性可以做到这件事。代码如下:

if (WebPartManager1.Personalization.CanEnterSharedScope)  {
            // display UI element that allows user to enter shared scope
            }      

  本文附带的示例应用中的 WebPartManagerPanel 用户控件包含了一个完整的实现,它可以根据当前用户和当前页面的能力动态地调整 窗格的显示。

将用户控件作为Web部件

  
在使用 Windows SharePoint 服务创建Web部件的时候,最令人沮丧的一件事情就是你必须用代码去创建控件的整个界面,设计器一点帮不了忙。因为 许多Web部件都是一系列互相交互的服务器端控件组成的,在创建Web部件时,不能使用 Visual Studio 的设计器是一件很不幸的事情。一个显而易见的解决方法就是允许开发人员创建用户控件,并且可以作为Web部件使用。(一个叫 SmartPart 的第三方工具提供了在 Windows SharePoint服务中可以将用户控件作为Web部件使用)。
  ASP.NET 2.0 Web部件解决了这个问题,它可以允许任何控件直接作为Web部件使用,不用修改或者包装这些控件。这不仅可以将用户控件 结合到Web部件集合中,而且还可以轻易地将现有 asp.net 页面中使用的那些自定义控件集成起来。
  这种方法内部的工作原理是,如果一个标准控件(不是Web部件)被加入到 WebPartZone 控件中,系统会隐含地调用 WebPartManager.CreateWebPart 方法,这个方法会创建一个 GenericWebPart 类的实例,并且用 添加的那个控件去初始化这个实例。GenericWebPart 从基类 WebPart 中继承,提供了核心Web部件属性实现。当构建 GenericWebPart 控件的时候,它会将初始化的那个控件作为子控件加入。在页面呈现过程中,就像大多数复合控件那样,GenericWebPart自身不会在 响应缓存中输出任何内容,只是作为输出子控件内容的一个代理。最终结果是你可以在页面中的 WebPartZone 控件里面加入任何控件,不用担心它不会运行。例如,下面的页面定义了一个 WebPartZone 控件,里面包括一个用户控件和一个标准日历控件,在创建的时候,这两个控件都会被隐含地包装成为一个 GenericWebPart 类的控件。代码如下:

<%@ Register Src="webparts/CustomerList.ascx"
            TagName="CustomerList" TagPrefix="Wingtip" %>
            <asp:WebPartManager ID=" WebPartManager1" runat="server" />
            <asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1">
            <ZoneTemplate>
            <Wingtip:CustomerList runat="server" id="CustomerList" />
            <asp:Calendar runat="server" id="CustomerCalendar" />
            </ZoneTemplate>
            </asp:WebPartZone>      

  和标准的Web部件一样,动态创建被 GenericWebPart 包装的控件也是可以的。如果是用户控件,首先,你必须调用 Page.LoadControl 来动态地载入和创建用户控件实例。其次,还必须显式地给这个控件设置一个唯一的ID。再者,你还必须调用 WebPartManager 对象的 CreateWebPart 方法去创建一个 GenericWebPart 类的实例来作为用户控件实例的包装。最后,将获得的 GenericWebPart 实例的引用作为参数传给 AddWebPart 方法,并且指定要加入的WebPartZone。代码如下:

// create Web Part instance from User Control file
            Control uc = this.LoadControl(@"webparts\CompanyNews.ascx");
            uc.ID = "wp2";
            GenericWebPart wp2 = WebPartManager1.CreateWebPart(uc);
            WebPartManager1.AddWebPart(wp2, WebPartZone1, 1);      

  这种技术的唯一缺点就是你无法控制Web部件的一些专用特性,因为你的控件不是从 WebPart 类继承的,而只有 GenericWebPart 是从 WebPart 类继承的。一旦你运行拥有由 GenericWebPart 包装的控件的页面,你马上就会很明显地发现一个现象,不想大多数Web部件,这些Web部件默认是无标题的,而且也没有相关的图标和描述信息。图10给出了一个 由 GenericeWebPart 控件包装的带有默认标题(无标题)和图标的示例用户控件。


图10 GenericWebPart

  其中一种解决方法是,在你的用户控件中,增加一个Init事件处理例程。如果你的控件由 GenericWebPart包装(通过查询 Parent 属性的类型可以判断),你就应该在程序中设置 GenericWebPart 类的一些属性,代码如下:

void Page_Init(object src, EventArgs e) {
            GenericWebPart gwp = Parent as GenericWebPart;
            if (gwp != null)  {
            gwp.Title = "My custom user control";
            gwp.TitleIconImageUrl = @"~\img\ALLUSR.GIF";
            gwp.CatalogIconImageUrl = @"~\img\ALLUSR.GIF";
            }
            }      

  当你再次运行此页面时,一旦用户控件被 GenericWebPart 包装,对 GenericeWebPart 父控件的属性的修改会反映在包含你的控件的Web部件上。图11给出了新的设置过属性的用户控件,请注意标题和图标。


图11 标题和图标

  另外一个更有吸引力的解决方案是直接在你的用户控件类里实现 IWebPart 接口。既然用户控件从来不直接查询Web部件的属性,因为那些信息是由 GenericWebPart 类处理的,这样做初看起来好像没什么帮助。幸运的是,GenericWebPart 类的设计者意识到这种需求,如果控件实现了 IWebPart 接口, 那么在 GenericeWebPart 类中实现属性就会自动委托所包装的控件。
  所以定制某个用户控件的Web部件特性仅仅是实现 IWebPart 接口,并填充接口中定义的七个属性就可以了。图12中的代码给出了用户控件的 后台代码类的一个例子,实现了和之前我们动态修改 GenericWebPart 属性一样的结果。
  你可能还会考虑给你的用户控件建立一个另外的基类,这个基类从 UserControl 继承,并且实现了IWebPart接口,然后就可以被你的门户应用中所有的用户控件所继承。我们在这篇文章的示例应用中就是这么做的。采用这种方法,你的用户控件就能在 它们的构造函数中初始化其所需的属性,其它的就由基类去控制了。图13给出了一个实现 IWebPart 接口的用户控件基类以及一个与之相对应的后台类的代码, 该用户控件使用这个基类设置标题和图标属性。
  现在你拥有了创建用户控件的这么多的灵活性,你可能会问:当你拥有设计器支持的用户控件,同时还可以定制Web部件特性,那为什么还要创建自己自定义 的控件呢?实际上,有几个原因需要你这样做。其中一个原因是你不能给用户控件添加定义的动作(verbs)。如果需要那样做,你必须直接从 WebPart 继承,然后重写Verbs属性。当然,你也可以考虑在你的控件中实现 IWebEditable 接口。
  另外一个原因是用户控件局限于应用程序的目录,除非你将.ascx文件从一个项目复制到另外一个项目的目录下,否则你不可能在多个Web应用程序中共享用户控件。另一方面,自定义Web部件类继承自 WebPart 类,能够被编译到一个可重用的dll里面, 并且部署到全局程序集缓存(GAC)。还有一点,通过自定义Web部件类,你还可以给你的控件写一个自定义的设计器,以改变在 Visual Studio 中默认的外观,而且你还可以在这个Web部件类被放在工具箱的时候,创建一个图标。图14提供了一个特性对比表,让你决定是选择自定义Web部件还是用户控件。

Web部件和个人化特性提供者程序

  
提供者程序是ASP.NET 2.0的一个新特性,这也是你能在这个版本中看到如此之多内置的功能完整的控件只需要很少的甚至不需要任何代码就能运行 的一个主要原因。提供者程序背后的基本思路是为某个特定的特性定义一套公共的与数据相关的任务,将那些任务聚集到一个抽象类声明中,该抽象类从公共的 ProviderBase 类继承。在本文探讨的个人化特性中,必须明确提供的数据相关任务包括:

  • 为某个特定页面和用户保存Web部件的属性和布局;
  • 为某个特定页面和用户装载Web部件的属性和布局;
  • 保存常规Web部件属性和特定的页面布局( 用于常规定制);
  • 加载常规Web部件属性和特定的页面布局(用于常规定制);
  • 将某个特定页面和用户的Web部件属性和布局重置为其默认值 ;
  • 将某个特定页面的Web部件属性和布局重置为其默认值(用于常规定制);

  还有其它的一些属于个人化体系结构的附属特性也需要持久化存储的能力,但是基本上可以归结为以上六种需求。如果我们假设有一个类可以完成这六个动作,而且能够成功 地保存和恢复数据,那么当站点运行的时候,每个页面上的 WebPartManager 控件就能够使用那个类保存和恢复所有的个人化和自定义数据。定义这些方法的抽象类的名字叫 PersonalizationProvider 类, 默认情况下使用的一个具体的派生类是 SqlPersonalizationProvider 类。图15显示了代表我们定义的 六个功能的那三个方法。请注意,不论输入的 userName 参数是否为空,每个方法都能够完成用户个人化或者共享自定义数据的功能。
  所有的个人化数据都保存为普通的二进制数据(byte[]),默认的 SqlPersonalizationProvider 类会将这些数据写入数据库中的一个image类型的字段。既然 ASP.NET 2.0 知道有一个类可以提供这些方法,它就能够在基础的控件集里面建立比以前更加多的逻辑。在我们的 案例中,每个使用Web部件的页面上的 WebPartManager类负责正确地调用当前的 PersonalizationProvider 类来序列化和恢复每个页面的个人化设置。图16展示了 EditorZone 控件与默认的 SqlPersonalizationProvider 类是如何交互的。


图16 交互

  你使用 ASP.NET 2.0 越多,对该提供者架构的例子了解就会越多。比如其中有成员提供者、角色管理提供者、站点地图提供者、站点监控提供者等等很多的提供者,所有的提供者程序都定义了一个相似的核心方法集与控件交互。

修改个人化数据存储

  
和大多数ASP.NET 2.0中的提供者程序一样,默认的个人化提供者程序是面向 SQL Server 后台存储而实现的。如果不修改配置文件,默认的 SqlPersonalizationProvider 采用 SQL Server 2005 Express Edition 连接字符串,支持基于本地文件的数据库。这个连接字符串就像下面这样:

data source=.\SQLEXPRESS; Integrated Security=SSPI;
            AttachDBFilename=|DataDirectory|aspnetdb.mdf; User Instance=true
            

 

  使用 SQL Server 2005 Express Edition 基于文件的数据库的一个优势就是它可以被动态创建,不需要用户任何附加的设置。这意味着你可以建立一个全新的站点,不用设置数据库就能启用个人化特性,也能够运行!当你最初与网站交互时,系统会在站点的 App_Data 目录中生成一个新的 aspnetdb.mdf 文件,并且用支持所有默认提供者程序所需的表和存储过程来初始化该数据库。
  对于不需要扩展规模或者支持很多并发用户的小站点来说,这简直太好了。但是对于企业系统来说,需要将数据存储到某个被全面管理的、专用的数据库服务器上。幸运的是,修改 SqlPersonalizationProvider 使用的数据库是非常简单直接的。SqlPersonalizationProvider 的配置将连接字符串初始化为 LocalSqlServer,这意味着它会在配置文件的<connectionStrings>节中寻找名字为 LocalSqlServer 的配置项,使用相关的连接字符串去打开到数据库的连接。默认情况下,这个字符串就是你在前文所看到的,意味着它会写入一个本地的 SQL Server 2005 Express Edition .mdf 文件。要修改它,你必须首先清除掉 LocalSqlServer 连接字符串集合,在你的 Web.config 文件中重新设置一个新的连接字符串值。(或者你也可以修改机器范围的 Machine.config文件,去影响这台机器上的所有站点)以下是一个 Web.config 文件的例子,它将提供者数据库的值修改为指向一个本地的 SQL Server 2000 的实例:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/
            V.2.0">
            <connectionStrings>
            <clear />
            <add name="LocalSqlServer" connectionString=
            "server=.;integrated security=sspi;database=aspnetdb"/>
            </connectionStrings>
            ...
            </configuration>
            

 

  在修改生效之前,本地 SQL 服务器上必须有一个名为 aspnetdb 的数据库,里面有SqlPersonalizationProvider 所需的表和存储过程。随 ASP.NET 2.0 发行了一个名为 aspnet_regsql.exe 的工具,用它可以建立这个数据库。当以默认设置运行该程序时,它将创建一个名为 aspnetdb 的本地数据库,其中有所有提供者程序所必需的表和存储过程,或者你可以选择将这些表和存储过程安装到一个已有的数据库中。所有的表和存储过程的名字都会以“aspnet”开头,所以不太可能会与任何现有的表重复。
  和所有 ASP.NET 2.0 的提供者程序一样,这种间接的模式提供了一种非常灵活的架构,使得不用修改任何页面或者Web部件,就可以将后端的数据存储完全替换掉。

创建自己的个人化提供者程序

  为个人化提供者程序修改连接字符串的能力赋予了你一定程度的灵活性,但是在 SqlPersonalizationProvider 内部还是使用命名空间 System.Data.Sql.Client 的功能来存取数据。这意味着你必须使用 SqlServer 数据库。如果你需要将个人化数据保存到另外一种数据库中,或者可能是另外一种完全不同的数据存储中,你将不得不更进一步,创建你自己定制的个人化数据提供者。幸运的是,大多数困难的工作已经为你做好了,而且也易于使用。作为个人化数据存储到另外一种数据存储的例子,本文的示例门户网站有一个自定义提供者程序的完整实现,名为 FileBasedPersonalizationProvider 类,它将所有的个人化和自定义数据保存到应用程序 App_Data 目录下的一个本地二进制文件中。这个二进制文件的名称为每个用户和路径唯一生成,每个路径下还有一个唯一的通用的用户配置文件。
  创建一个自定义的个人化数据提供者,你必须首先建立一个从 PersonalizationProvider 基类继承的新的类,然后重写所有从基类继承的抽象方法。图17中给出的类定义演示了如何做到这一点。
  为了使你的提供者程序能够运作,其实只有两个重要的方法必须要实现:LoadPersonalizationBlobs 和 SavePersonalizationBlob。这两个方法完成了个人化数据的二进制序列化功能,当加载页面时,个人化架构会调用它们。当Web部件页面处于编辑、目录或者设计模式时,如果数据被修改了,个人化架构还会调用它们将数据写回。(典型情况下是基于一个特定的用户)。
  在下载的示例代码中,SavePersonalizationBlob 的实现代码将 dataBlob 参数写入一个基于传入的用户名称和路径唯一命名的文件。相似地,LoadPersonalizationBlobs 的实现代码会查找这个文件(使用相同的命名方法),并返回一个用户个人化的或者共享的blob数据。如果传入的userName参数为空,这两个方法默认都会保存或者装载共享数据,如果不为空,就会保存或者装载用户个人化数据。图18给出了示例 FileBasedPersonalizationProvider 中这两个方法的实现代码,以及一对用来根据用户名和路径信息生成唯一文件名的 helper 方法。
  一旦提供者程序完全实现了,通过个人化配置节中的提供者节,你就可以将这个程序注册为一个提供者程序。要想实际使用它,你必须在 Web.config 文件中定义它为默认的个人化提供者。下面是一个将我们自定义的基于文件的提供者程序作为默认提供者的例子:

<webParts>
            <personalization defaultProvider="FileBasedPersonalizationProvider">
            <providers>
            <add name="FileBasedPersonalizationProvider"
            type="Wingtip.Providers.FileBasedPersonalizationProvider" />
            </providers>
            </personalization>
            </webParts>
            

 

  如果我们重新运行我们的网站,所有的个人化数据现在都会被存储到一个本地的二进制文件。显然这不是最好的解决方法,但是示例代码提供了一些思路,让你了解如何实现你自己的个人化提供者,不论你想基于什么样的后端存储都可以。图19给出了我们新的提供者是如何插入到整个的Web部件体系结构中去的。


图19
使用 FileBasedPersonalizationProvider

更进一步

  
到现在为止,你已经看到了如果使用 ASP.NET 2.0 和其新的Web部件控件来创建具有丰富特性的支持自定义和个人化的门户应用程序,而 ASP.NET 2.0 使得这一工作变得相当简单。也许这个架构的最重要的特性是可插件的能力。提供者架构使得将符合你的站点特性的个人化数据写入后端数据存储变得相对简单,而且也不会被绑定到一个特定的序列化实现和数据存储上。所以,现在就前进,大胆地使用ASP.NET 2.0 Web部件去创建可以自定义的站点吧!
  如果你喜欢这篇文章,在ASP.NET 2.0门户应用中创建Web部件还有大量的东西需要学习。记得从MSDN杂志网站下载本文的示例代码。在 ASP.NET 2.0 QuickStart tutorials 中也有一些例子可以拿来研究。我们还推荐你去看看 Fredrik Normén的网志,里面有一些有关 ASP.NET 2.0 Web 部件的有趣的例子。
 

作者简介
  Ted Pattison
是一名作家和培训师,通过 Pluralsight 提供一些易于上手的培训课程,还通过 Ted Pattison Group 提供咨询服务,还是几本书的作者。
  Fritz Onion
是 Pluralsight 的合伙人。Pluralsight 是一家从事教育和内容创建的公司,他在该公司里专注于 ASP.NET Web 开发,Fritz 还是 Essential ASP.NET (Addison Wesley 2003) 的作者。在www.pluralsight.com/fritz
上面可以看到他的网志。


本文由 VCKBASE MTT 翻译

转载:http://www.vckbase.com/document/viewdoc/?id=1550

posted @ 2007-08-01 11:24 WebServices 阅读(457) 评论(0) 编辑

利用 ASP.NET 2.0 创建自定义 Web 控件

利用 ASP.NET 2.0 创建自定义 Web 控件

发布日期: 09/03/2004 | 更新日期: 09/03/2004

Jayesh Patel、Bryan Acker、Robert McGovern
Infusion Development

适用于:
Microsoft ASP.NET 2.0
Microsoft Visual Studio 2005

摘要:ASP.NET 2.0 中新的自适应呈现模型为控件编写人员提供了很多新的选项。本文展示了这些选项如何使创建 ASP.NET 的自定义控件变得比以前更加容易。

*
本页内容
简介 简介
自适应呈现模型 自适应呈现模型
创建自定义服务器控件 创建自定义服务器控件
TagKey TagKey
使用自定义控件 使用自定义控件
创建复合服务器控件 创建复合服务器控件
创建复合控件 创建复合控件
添加控件行为 添加控件行为
回调示例 回调示例
使用设计器 使用设计器
小结 小结

简介

从使用基本的文本编辑器到创作标记页面,Web 开发已经经历了一个漫长的过程。目前,集成开发环境 (IDE) 为开发过程中的几乎每个方面都提供了图形化表示形式。此外,还实现各种说明性编程技术以提高效率并降低出现错误的几率。Visual Studio 2005 和 ASP.NET 2.0 中的控件体系结构遵循了这些编程趋势,并且提供了可靠的、可扩展的环境,该环境设计为使开发人员可以创建能够以说明方式配置的控件。

此外,ASP.NET 中新的自适应呈现模型减少了编写可专门识别其目标浏览器的控件的需要。换句话说,控件开发人员可以专注于设计控件,而让 ASP.NET 框架负责转换控件并针对不同类型的浏览器和设备呈现它。

尽管 ASP.NET 2.0 在控件设计过程中提供了增量改进功能,但实际控件呈现模型已经完全进行了更改。作为自定义控件开发人员,您将会看到利用 ASP.NET 的几个新选项。最重要的是,您将会发现只需编写较少的代码便可完成相同的任务。

在 ASP.NET 2.0 中,创建自定义服务器控件有很多方法,每种方法都有其优点和局限性。本文将讨论与自定义控件的创建和配置相关的详细信息。代码示例和体系结构概念要求您对 C# 编程语言具有中等水平的理解。

自适应呈现模型

在 ASP.NET 1.x 中,自定义控件开发人员必须设计每个服务器控件,以便它可以识别不同的浏览器类型并发出正确的输出。ASP.NET 1.x 控件框架提供了几项功能以使该任务变得更简单,但开发人员仍然必须根据浏览器的类型编写切换程序、开发适当的 HTML,然后针对不同类型的浏览器测试控件。此外,如果开发人员希望控件在移动设备上显示,他必须创建一个与普通 Web 浏览器上使用的控件不同的全新控件。

ASP.NET 2.0 通过新的自适应呈现模型简化了浏览器检测和呈现过程。在 ASP.NET 2.0 中引入的自适应呈现模型旨在用于支持那些众多能够使用标记格式(包括 HTML、WML、XHTML 或 CHMTL)的不同设备。

自适应呈现模型体系结构

每个控件都可以链接到一个适配器,它会针对特定的目标设备修改控件的行为和标记。例如,HTML 适配器将 ASP.NET 控件生成为标准的 HTML 和 DHTML,以便普通 Web 浏览器使用。另一方面,WML 适配器将相同的控件转换成无线标记语言,以便蜂窝电话或其他移动设备使用。

customwebcontrolsaspnet2_fig01

1. 控件-适配器寿命周期

上图说明了控件方法与适配器方法之间一对一的映射。如果有适配器(如果控件的 Adapter 属性不为空),执行就会在控件和适配器方法之间传输,如上图所示。在生成阶段,控件对象或适配器对象都可以生成输出(通常情况下两者不同时生成输出)。通常情况下,如果有适配器,那么适配器的实现将覆盖控件的实现。在 ASP.NET 2.0 中,自适应呈现模型适用于所有 ASP.NET 控件(不仅仅是移动控件),并且允许 ASP.NET 2.0 支持统一的控件体系结构。

实际意义

自适应呈现模型的实际意义有两个主要方面。第一,作为开发人员,您可以一次设计控件并期望它可以在具有适配器的任何类型的设备或浏览器上使用。第二,您可以对常用适配器利用广泛的 Microsoft 测试,减少您自己浏览器的特定测试。

自适应呈现模型还为 ASP.NET 2.0 提供了将其他服务添加到控件生成过程中的机会。由于具有适配器模型,您可以:

根据目标的类型,使用筛选器 来更改控件的外观。

根据目标的类型,使用模板来更改整个页面布局。

根据浏览器控制在浏览器上的呈现,而不必依赖于 ASP.NET 1.x 的 uplevel/downlevel 确定。

在本文中,我们将重点放在创建自定义控件的应用方面。但是,请牢记自适应呈现模型是新的基础框架。

创建自定义服务器控件

Visual Studio 2005 提供了很多用于开发自定义服务器控件的有用工具。为了说明某些功能,我们将创建一个 MailLink 控件,它公开了两个属性:EmailText。该控件将生成必需的 HTML 来将所提供的 Text 包装到 mailto: 链接标记中。

创建项目

在 Visual Studio 2005 中,我们通过在新建项目向导中选择适当的图标来创建一个新的“Web Control Library”项目:

customwebcontrolsaspnet2_fig02thumb

2. Visual Studio 2005 中的新建项目向导

该项目是利用默认的自定义控件类实现创建的。对于我们的示例,我们将该默认文件重命名为 MailLink.cs。

:在解决方案资源管理器中重命名该文件时,Visual Studio 2005 将会自动更新类名。

MailLink 的源代码在由项目向导生成的默认模板上构建。MailLink 类从 WebControl 基类自动派生。

public class MailLink : WebControl  {

WebControl 类提供默认实现方法,可以很简单地覆盖这些方法来为我们的控件提供详细说明。

添加属性

MailLink 示例中,我们需要添加 EmailText 属性。为了正确配置这些属性,我们不仅必须编写代码,还要分配几个特性。

[Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("The e-mail address.")]
public virtual string Email {
get {
string s = (string)ViewState["Email"];
return (s == null) ? String.Empty : s;
}
set {
ViewState["Email"] = value;
}
}

特性(以粗体表示)定义了新控件将如何与设计器 (Visual Studio) 进行交互。Email 属性的特性告诉 Visual Studio 如何在设计过程中处理属性:

BindableEmail 属性可绑定 到数据源。您可以将 Email 字段链接到数据库、XML 文件或任何其他 DataSet。该特性强制 Visual Studio 在控件的可绑定属性列表中显示 Email 属性。

AppearanceEmail 属性将显示在 Appearance 类别下的属性视图中。您可以选择想要的任何类别,包括默认类别:AppearanceAccessibilityBehaviorDataLayoutMisc。只要用户选择了属性的类别组织方法,Email 属性将会显示在 Appearance 下。

DefaultValueEmail 属性具有一个空的默认值。尽管空值对于 Email 字段来说有意义,但对于您添加到控件中的其他属性可能并不合适。当用户将您的控件放到他们的 Web 页上时,选择适当的默认值可为用户免去不计其数的单击操作。

Description — 属性说明显示在控件列表下,并且也可能作为工具提示出现。Email 属性将具有 The e-mail address 说明。

Localizable — 它会用发送信号的方式通知 ASP.NET 2.0 Framework 该控件包括可以针对不同语言或位置进行配置的文本属性。

您可以使用 System.ComponentModel 命名空间中的各种特性来进一步改进任何特殊属性的外观和行为。我们将在本文的使用设计器部分中更详细地介绍修改属性或控件的行为的方法。

接下来,我们需要添加 Text 属性。Text 属性与 Email 属性稍有不同,因为我们希望将 Text 显示为由 MailLink 控件发出的 HTML 的一部分。为此,我们需要从 System.Web.UI 命名空间中添加一个新的特性。

[Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("The text to display on the link."),
Localizable(true),
PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public virtual string Text {
get {
string s = (string)ViewState["Text"];
return (s == null) ? String.Empty : s;
}
Set {
ViewState["Text"] = value;
}
}

Text 属性的 PersistenceMode(PersistenceMode.InnerDefaultProperty) 特性(粗体代码)指定设计器应该将该属性作为控件标记内的内部内容序列化。该特性还声明 Text 是控件的默认属性。当用户在 Visual Studio 中使用这个控件时,Text 属性将会作为该控件的内部文本自动显示在图形设计器上,并且如果用户单击该控件并尝试更改显示的文本,Text 属性将会自动更改。

另一方面,应用到属性的特性会影响设计期间用户与控件的交互方式。在运行过程中,这些特性被 ASP.NET 运行时忽略。

有关 ViewState 的注释

请注意,用于两个属性的 GetSet 方法都利用 ViewState 对象。ViewState 对象是一个内置到 WebControl 类中的帮助器对象。从开发角度讲,ViewState 可被视为一个集合类,用于存储在回发过程中我们想要保留的任意属性。实际上,ViewState 封装了确定如何执行持久性(使用 Cookie、会话等等)所需的所有代码和逻辑。

生成控件

在定义了控件属性之后,接下来的步骤就是要设计将由控件发出的实际响应。在 MailLink 示例中,我们希望设计控件来生成基本的 HTML 标记。

TagKey

WebControl 的默认实现会生成一个 标记。我们的 MailLink 控件通过为 TagKey 属性提供它自己的实现来覆盖该默认实现。TagKey 属性定义将要封装控件内容的最外面的标记。

幸运的是,我们可以使用 HtmlTextWriterTag 枚举来指示链接 标记,而不必实际编写 HTML 文本。该枚举方法用于最常用的 HTML 标记。

protected override HtmlTextWriterTag TagKey {
get {
return HtmlTextWriterTag.A;
}
}

如果您需要生成一个不属于 HtmlTextWriterTag 枚举的一部分的标记,您必须覆盖 WebControl.TagName 属性,而非 TagKey 属性。TagName 属性会返回由控件生成的实际 HTML 标记字符串。TagName 的默认 WebControl 实现只调用 TagKey,并以完美的提取方式提取正确的 HTML。

AttributesToRender

在定义了基本标记之后,接下来的步骤就是分配我们要添加到该标记中的各种特性。我们的 MailLink 控件将覆盖 AddAttibutesToRender 方法以便为“mailto”标记添加适当的标记。

protected override void AddAttributesToRender(
HtmlTextWriter writer){
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Href,
"mailto:" + Email);
}

对基类的 addAtributeToRender() 调用会被调用,以确保可以正确生成其他样式和特性。如果我们忽略该基本调用,我们可能会失去内置到所有 Web 控件中的母版页设计、筛选器或其他功能。

RenderContents

最后,由于所需的 WebControl 类的方法和属性都已被覆盖,因此可以使用 RenderContents 方法来编写文本。出于安全原因,MailLink 使用 HtmlTextWriter.WriteEncodedText 方法编写 HTML 编码输出。HTML 编码安全地将潜在的危险字符转换为更安全的表示形式。

protected override void RenderContents(
HtmlTextWriter writer) {
if (Text == String.Empty) {
Text = Email;
}
writer.WriteEncodedText(Text);
}

请注意,我们只生成 Text 属性。如果 Text 属性为空,我们将利用 Email 属性填充它。请记住,Text 属性旨在用作控件标记的内部文本。这种类型的控件至少需要某一可显示的文本(以便用户进行单击)。如果我们试图生成一个空字符串,我们将失去链接标记的预期功能。

如何生成的?

Render() 方法基本上控制着 WebControl 的整个输出。默认情况下,Render() 方法实际上会依次调用 RenderBeginTag()RenderContents() 以及 RenderEndTag()。尽管在 ASP.NET 1.x 中调用结构并未变化,但由于该呈现模型,修改这些调用的影响却发生了变化。

您可以覆盖 Render() 方法来发出您想要的任何内容。换句话说,您可能已经跳过了覆盖 TagKey 属性、AttributestoRender 属性和 RenderContents() 方法,并且仅使 Render() 编写“text”。但是,这种做法可能会严重影响自适应呈现。如果重写 Render() 来直接发出最终输出,您会绕过内置到 WebControl 类中的大多数自适应呈现特性。

自适应呈现模型和各种适配器的作用是:截获对各种标记方法的调用并转换特定设备的输出。在 MailLink 的特定示例中,几乎所有的标记语言都支持用于 链接的相同语法。但是,其他标记通常在不同的标记语言中会有截然不同的转换。如果我们为这样的标记使用了 Render(),我们的控件将只能在某些浏览器上使用,而适配器无法更改该行为。通过设计控件以使用自适应元素而不是使用 Render(),您可以让 ASP.NET 框架有机会根据浏览器提供在浏览器上的呈现服务。

使用自定义控件

自定义控件能够以很多方法包括在 Web 应用程序中。标准方法是将自定义控件编译到一个程序集中,然后在使用该控件的所有 Web 应用程序中添加一个对该程序集的引用。

使用 EmailLink

为了使用 EmailLink 控件,您需要:

1.

MyControls 项目编译到一个程序集中。

customwebcontrolsaspnet2_fig03

3. 编译包含 EmailLink 控件的 MyControls 命名空间

2.

在新的 Web 项目中添加一个对已编译程序集的引用。

customwebcontrolsaspnet2_fig04

4. 编译应用程序并添加一个引用

在正确添加引用之后,自定义控件应该出现在工具箱中的“MyControls Components”下。

customwebcontrolsaspnet2_fig05

5. 工具箱中的 EmailLink

MyControls 程序集中的所有组件都使用默认的齿轮图标,因为我们未曾在每个控件上设置特定的图标。设置图标如同在该控件类上调整图标属性那样简单。

页面上的控件

在添加了对包含控件的程序集的引用之后,您可以将 MailLink 控件拖动到设计器表面并像使用任何其他 ASP.NET 服务器控件那样使用它。

customwebcontrolsaspnet2_fig06

6. MailLink 自定义控件

6 展示了 MailLink 控件的设计器视图。请注意,Properties 窗口公开了预期的 EmailText 元素,它们可以用于配置控件。通过将自定义控件编译到可重复使用的程序集中,MailLink 控件可以被很多 Web 应用程序重复使用。

创建复合服务器控件

诸如 LoginGridView 这些可靠的控件是由很多基本控件组成的。在 ASP.NET 1.x 中,您必须通过艰苦的工作将嵌套标记和元素添加到自定义控件中来开发复合控件。在 ASP.NET 2.0 中,您可以通过扩展 System.Web.UI.WebControls.CompositeControl 类来构建复杂的复合控件。CompositeControl 类提供了将多个控件的输出合并到单个统一的控件中所必需的框架。

管理复合控件比管理基本自定义控件稍微困难一些,因为复合控件需要一些自定义布局的信息。复合控件将它们的呈现和事件处理任务委托给构成控件。子组件的所有关联的适配器类也会被自动应用。这样,如果您具有适当的适配器,复合控件将会在任何目标浏览器类型或设备上正确地呈现。

创建复合控件

创建复合控件的初始过程与创建自定义服务器控件的初始过程相似。但是,该过程还涉及了更多的步骤。在以下示例中,我们将创建一个由 LabelTextBox 组成的简单的复合 AgeCollector 控件,它旨在收集生日的信息。

复合控件类应该通过从 CompositeControl 继承开始。

public class AgeCollector : CompositeControl
{
}

定义属性

对于我们的简单控件,我们必须为标签 (Prompt) 和文本框 (DateOfBirth) 创建属性。

  [Bindable(true), Category("Appearance"),
DefaultValue("Please enter your date of birth:"),
Description("Text to prompt user with.")
Localizable(true)]
public virtual String Prompt {
get
{
string s = (string)ViewState["Prompt"];
return (s == null) ? String.Empty : s;
}
set {
ViewState["Prompt"] = value;
}
}

再一次,我们使用特性为属性提供说明和默认值。我们选择了使提示可以进行本地化,以便该控件无论何时都可以用于要求进行国际化的应用程序中。实际的提示可以绑定到包含语言特定文本的资源文件。

还必须定义 DateOfBirth 属性。但是,我们不是使用 String,而是使用 DateTime 数据类型来正确地存储日期。

  [Bindable(true), Category("Appearance"),
DefaultValue(""),
Description("Date of Birth Input area")]
public virtual DateTime DateOfBirth {
get
{
bject o = ViewState["DateOfBirth"];
return (o == null) ? DateTime.Now : (DateTime)o;
}
set {
ViewState["DateOfBirth"] = value;
}
}

CreateChildControls 方法

我们的复合控件由一个标签和一个文本框组成。我们无法使用简单控件的技术来显示这两个标记,除非使用强制方式和 Render() 方法。因为我们希望利用自适应呈现并显示我们的两个控件,所以我们需要覆盖内置到 CompositeControl 类中的 CreateChildControls() 方法。这种方法使我们可以定义控件,并将我们的复合控件的属性传递到要显示的单个控件中。

  protected override void CreateChildControls() {
//Create and load the label
Label lab1 = new Label();
lab1.Text = Prompt;
lab1.ForeColor = this.ForeColor;
this.Controls.Add(lab1);
//Add a line break between the label and text box
Literal lit = new Literal();
lit.Text = "";
this.Controls.Add(lit);
//Add the Textbox
TextBox tb = new TextBox();
tb.ID = "tb1";
tb.Text = DateOfBirth.ToString();
this.Controls.Add(tb);
//call the parent method
base.CreateChildControls();
}

请注意,我们必须初始化每个控件、分配所有属性,然后将控件添加到内置到 CompositeControl 类中的 Controls 集合。我们还使用了 Literal 对象将换行符
置于标签和控件之间。Literal 对象是非常简单的控件,您可以使用它在功能元素之间插入原始 HTML。

请注意,我们还对基本方法进行了调用,以便确保我们的复合控件具有内置到 CompositeControl 基类中的任何其他功能。尤其是,基本方法会强制 ASP.NET 将 Controls 集合的所有元素添加到控件树中。如果我们忽略这个调用,或者将其置于我们方法的顶部,那么复合控件将不会正确地生成。

完整的 AgeCollector

当我们的 AgeCollector 控件生成时,ASP.NET 将在每个子控件上实际调用适当的方法,并将结果合并到复合控件的输出中。换句话说,如果我们已正确地设计了简单控件,那么该复合控件就只是一个容器。自适应呈现模型将会自动应用到每个子控件中。但是,实际的 CompositeControl 将不会被修改,因为它不包含需要更改的任何控件。

以下是另一个实例,其中使用的适当方法 (CreateChildControls()) 利用了自适应呈现模型,而不是简单地在 WebControl 上重载 Render() 方法。由于自适应呈现模型和 CompositeControl 的特性,ASP.NET 2.0 节省了我们的开发时间、减少了代码行数并减少了很多的测试烦恼。只要我们知道元素控件可通过特定适配器正确地生成,CompositeControl 将会通过该适配器正确地生成。

如果我们将控件拖动到 ASP.NET 页面上并查看属性,我们将会看到具有 PromptDateOfBirth 属性的单个控件。

customwebcontrolsaspnet2_fig07

7. AgeCollector 使用

请注意,如果我们将复合控件的 ForeColor 更改为红色,我们实际上更改了 LabelForeColor。但是,我们尚未链接某些其他属性。例如,我们无法更改 DateOfBirth 字段的 ForeColor。换句话说,当您构建一个复合控件时,您始终需要考虑应该公开哪些子控件属性。

添加控件行为

到目前为止,我们设计的两个控件都是简单、静态的控件。也就是说,这些控件不会完成利用普通的内置控件或简单用户控件 (.ascx) 无法完成的任何操作。构建自定义服务器控件的主要原因之一就是要提供使用现有控件集无法执行的新功能。

事件模型

在 Web 窗体页面中,与服务器控件关联的事件由客户端引发并由 Web 服务器处理。对于在客户机上由服务器控件引发的事件,ASP.NET 2.0 事件模型收集有关请求的信息,并使用 HTTP Post 将详细信息传递到服务器。服务器上的 Page Framework 对该公告作出解释以确定发生的事件,然后调用适当的处理程序方法。

customwebcontrolsaspnet2_fig08thumb

8. 典型的服务器控件事件

ASP.NET 2.0 可处理几乎所有捕获、传输和解释事件的方法。详细信息对于开发人员来说是隐藏的,开发人员只需要关心服务器上的处理程序方法的实现。

大多数服务器事件要求一个到服务器的往返以便进行处理,因此支持有限数量的单击类型事件。出于性能原因,不支持鼠标悬停和其他内部事件。

回发事件

ASP.NET 2.0 中的很多服务器控件都生成回发事件。回发事件将页面传递到服务器以便进行处理。这是一个非常昂贵的操作,因为它要求页面通过网络进行传递。

回发模型自从 ASP.NET 1.x 就没有进行过显著更改。为了创建一个可处理回发的控件,您的控件必须实现 IPostBackDataHandler 接口,它定义了两个方法:

LoadPostData — 该方法处理您控件的回发数据。

RaisePostDataChangedEvent — 该事件通知应用程序由于处理回发数据,该控件的状态已经更改。

PostDataChangedEvent 调用引发的事件必须在该控件内部定义。然后,用户可以在开发过程中编写实际的事件方法。

非回发事件

某些服务器控件支持非回发事件。此类事件会更改控件的状态,但并不要求立即进行处理。这些事件由控件缓存,而不是立即传递到服务器以进行处理。例如,ListBox 控件可能包含很多元素。如果用户选择一个不同的元素,那么控件将在不通知服务器的情况下显示适当的更改并记住其新状态。在张贴包含 ListBox 的窗体之后,ListBox 控件将提交事件(选定的项)。

非回发事件的默认行为可以通过设置 AutoPostBack 属性进行更改。如果 AutoPostBack 设置为 true,那么通常由客户端缓存的事件发送信号通知服务器立即进行处理。启用 AutoPostBack 的控件要求客户机允许运行脚本。

ASP.NET 2.0 并未以任何明显的方式更改该模型。

回调和带外请求

标准的 Web 协议设计用于同步通讯。每个请求接收响应的速度与服务器生成数据的速度同样快。但是,很多任务都需要带外 请求,例如同一时间访问第三方资源。这些请求未处于浏览器和 Web 服务器之间的标准通讯带区内,因此被认为是带外请求。

ASP.NET 1.x 中的带外

进行带外数据请求的要求提示众多开发人员可以创造性地使用可用资源来获得所需的功能。例如,通过使用 ActiveX 组件和 JavaScript,开发人员能够进行外部 HTTP 调用而无需完全回发到服务器。下面的 JavaScript 示例说明了可以与 ASP.NET 1.x 一起使用的带外 HTTP 请求。

function RetrieveGoogleFrontPage() {
var XmlHttp = new ActiveXObject("Msxml2.XMLHTTP.4.0");
XmlHttp.Open("GET", "http://www.fakedomain.com", false);
XmlHttp.Send();
return XmlHttp.responseText;
}

这种机制的一个缺点就是 XmlHttp.responseText 包含该请求的完整结果。开发人员将必须编写只返回商业数据的特殊页面,否则响应会由于不必要的标记而非常庞大。

ASP.NET 2.0 中的带外

ASP.NET 2.0 概括了 XmlHttp 对象的使用并提供了内置的回调功能。新系统的核心有两个关键项:System.Web.UI.ICallbackEventHandlerPage.GetCallbackEventReference 方法。

Page.GetCallbackEventReference 方法及其重载用于指定将参与回调事件的 JavaScript 方法。

public string GetCallbackEventReference(
Control control,
string argument,
string clientCallback,
string context
);

上述代码显示了 GetCallBackEventReference 所需的最小参数集,这些参数将在下面进行详细说明。

Controlcontrol 参数确定实现 RaiseCallbackEvent 方法的 ICallbackEventHandler

Argumentargument 字符串包含客户端脚本。评估该脚本的结果将作为 eventArgument 参数传递到 RaiseCallbackEvent

ClientCallbackclientCallback 参数包含客户端事件处理程序的名称,该处理程序将接收成功服务器事件的结果。

Contextcontext 参数包含一个客户端脚本。评估该脚本的结果将传递到客户端事件处理程序,该处理程序在 clientCallback 参数中指定为 context 参数。

CallbackEventHandlerGetCallbackEventReference 方法相结合在客户端和服务器之间产生异步通讯。

回调示例

以下 Web 页使用回调机制查询服务器以获得其当前时间。该页面弹出一个 JavaScript 警告,在无需完整页面回发的情况下显示当前时间。

<%@ Page Language="C#" CompileWith="Default3.aspx.cs"
ClassName="Default3_aspx" %>
<%@ Register TagPrefix="cc1"
Namespace="MyControls"
Assembly="WebControlLibrary3" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<script language="javascript">
function GetServerTime() {
var message = '';
var context = '';
<%=CallBack%>
}
function ShowServerTime(timeMessage, context) {
alert('The time on the server is:\n' + timeMessage);
}
function OnError(message, context) {
alert('An unhandled exception has occurred:\n' + message);
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:timesnap id="TimeSnap1" runat="server"> </cc1:timesnap>
<input type="button" value="GetTime"
onclick="GetServerTime();" />&nbsp;
</div>
</form>
</body>
</html>

上述页面源代码包含三个关键 JavaScript 函数:GetServerTime()ShowServerTime()OnError()。这些 JavaScript 函数与页面的 GetCallbackEventReference 带外请求相关联。

public partial class Default3_aspx {
public string CallBack;
void Page_Load(object sender, EventArgs e) {
CallBack = this.GetCallbackEventReference(TimeSnap1,
"message","ShowServerTime","context","OnError");
}
}

GetCallbackEventReference 方法需要为其第一个参数实现 ICallbackEventHandler 接口的对象。通过实现 RaiseCallbackEvent() 方法,TimeSnap 自定义服务器控件符合接口要求。

    public class TimeSnap : WebControl, ICallbackEventHandler
{
...
public string RaiseCallbackEvent(string eventArgument) {
// Uncomment next line to test error handler
// throw new ApplicationException(
// "Some unhandled exception");
return DateTime.Now.ToLocalTime().ToShortTimeString();
}
}
}

TimeSnap.RaiseCallbackEvent() 方法仅返回 string 格式的当前时间。

customwebcontrolsaspnet2_fig09

9. 回调请求输出

9 说明了按下 GetTime 按钮的结果。向服务器发出带外请求,从而产生显示服务器上当前时间的“Alert”窗口。发出这个请求不需要回发,因此控件的最初生成时间不会改变。

使用设计器

在前面的示例中,我们已经使用了几个标准的特性来规定自定义控件的属性将与设计器 (Visual Studio) 进行交互的方式。我们为各种控件属性分配了特性以定义属性将在其中出现的类别、定义属性是否应该具有一个默认值、定义属性的说明应该是什么样子以及属性是否应该为 bindable。在 ASP.NET 1.x 中,附加的设计器类 使您可以创建用于编辑属性的新对话框、自动将属性值从 String 转换为其他数据类型(反之亦然),并显示只在运行时生成的控件的占位符数据。

设计器类有助于将控件开发分成两个阶段。第一,您必须开发自定义控件。第二,您必须决定开发人员将如何与设计环境内的控件进行交互。设计器类通过在每个自定义控件的顶部充当装饰师来完成第二个任务。换句话说,如果您要开发很多自定义控件,您可以创建一个标准的可重复使用的设计器集,并通过特性简单地将设计器应用到每个自定义控件中。

ASP.NET 2.0 为设计器模型提供了几项增强功能:

新的复合控件设计器 — CompositeControlDesiger 类完全识别复合控件,并且提供支持父子控件关系的功能。

新的数据绑定控件设计器 — DataBoundControlDesignerDatabound 控件提供了很多新功能。您可以使用该设计器来提供模拟数据,或者在设计期间自动连接到活 datasource

增强的备用设计时区域支持 — 新的 DesignerRegion 类及其子类提供了一种非常灵活的机制以便显示控件。您可以使用 DesignerRegion 来设置控件的选项卡式视图。您还可以使用 EditableDesignerRegion 控件为控件创建新的模板。

增强的模板支持 — 现在,设计器类提供了更简洁的机制以便将新的模板添加到控件中。模板化控件是一种将控件逻辑和控件显示分开的控件。显示通过模板进行定义,而逻辑在实际控件中进行编码。

增强的任务支持 — 现在,设计器可以合并设计时的任务。最常见的任务将可在视图之间切换。但是,其他任务可以包括控件的自动配置或资源文件的自动创建。任务可以在设计时控件上显示为菜单(与允许您配置 GridView 控件的菜单相似)。

增强的事件支持 — 设计器中的事件模型已经进行了改进。现在,您可以创建事件来响应在不同区域中的单击或对各种任务的单击。使用设计器时,只要用户在特定区域上单击就可以使控件切换视图、自动生成代码或更改配置。

ASP.NET 2.0 具有一个经过显著改进的设计器模型,它可以使专业控件开发人员的工作更加简单。如果您只是为自己使用而构建一个单个的控件,该设计器就大材小用了。但是,如果您要为分发而构建一个控件,您可以使用新的设计器来全面地自定义 Visual Studio 2005 中控件的行为。

小结

尽管 ASP.NET 2.0 包含了一个内容丰富的扩展控件集,但开发人员通常有很多理由来创建自定义控件。由于 ASP.NET 2.0 中的增强功能,创建自定义控件的过程要比在 ASP.NET 1.x 中更快、更容易。新的 CompositeControl 基类完全利用自适应呈现模型,并为创建复杂的控件提供了一个简单、易于使用的容器。如果您的控件需要回发或回调功能,ASP.NET 2.0 简化了处理客户端脚本文件和开发带外请求的过程。最后,在您开发控件后,您可以使用各种设计器类来完全配置控件在可视化设计器(例如 Visual Studio 2005)内的行为。

相关书籍

A First Look at ASP.NET V. 2.0

ASP.NET 2.0 Revealed

Developing ASP.NET Server Controls and Components

关于作者

Jayesh Patel — Jay Patel 是 .NET 和 Java 技术的开发人员。Jay 的研究重点在于基于编程的模式和灵活方法。

Bryan Acker — Bryan Acker 是 Infusion Development 的技术编写人员。Bryan 在 ASP、ASP.NET Web 开发和 Web 宿主方面有非常深厚的背景。

Robert McGovern — Rob McGovern 是一名资深作家、开发人员并且是 Infusion Development 的项目经理。Rob 参加过数个不同的 ASP.NET 项目,包括“CodeNotes for ASP.NET”和“The JSP to ASP.NET migration guide”。

Infusion Development Corporation 是 Microsoft 认证的解决方案提供商,为跻身财富杂志 1000 强的公司提供自定义软件开发、培训和咨询服务,主要对金融服务行业提供上述服务。基于在纽约和多伦多的办事处,Infusion Development 已经建立了一个国际化的客户基地,其中包括一些世界最大的金融服务、证券经纪和软件开发行业的公司。Infusion Development 的员工同时还是 CodeNotes 系列书籍的作者和创始者。


http://www.microsoft.com/china/msdn/library/webservices/asp.net/USdnvs05custwebcon.mspx?mfr=true

posted @ 2007-08-01 10:34 WebServices 阅读(237) 评论(0) 编辑

How to Register User Controls and Custom Controls in Web.config

Tip/Trick: How to Register User Controls and Custom Controls in Web.config

I've been including this technique in my ASP.NET Tips/Tricks talks the last year, but given how many people are always surprised by its existence I thought it was worth a dedicated tip/trick post to raise the visibility of it (click here to read other posts in my ASP.NET Tips/Tricks series).

Problem:

In previous versions of ASP.NET developers imported and used both custom server controls and user controls on a page by adding <%@ Register %> directives to the top of pages like so:

<%@ Register TagPrefix="scott" TagName="header" Src="Controls/Header.ascx" %>
<%@ Register TagPrefix="scott" TagName="footer" Src="Controls/Footer.ascx" %>
<%@ Register TagPrefix="ControlVendor" Assembly="ControlVendor" %>

<html>
<body>
    
<form id="form1" runat="server">
        
<scott:header ID="MyHeader" runat="server" />
    </
form>
</body>
</html>

 

Note that the first two register directives above are for user-controls (implemented in .ascx files), while the last is for a custom control compiled into an assembly .dll file.  Once registered developers could then declare these controls anywhere on the page using the tagprefix and tagnames configured.

This works fine, but can be a pain to manage when you want to have controls used across lots of pages within your site (especially if you ever move your .ascx files and need to update all of the registration declarations.

Solution:

ASP.NET 2.0 makes control declarations much cleaner and easier to manage. Instead of duplicating them on all your pages, just declare them once within the new pages->controls section with the web.config file of your application:

<?xml version="1.0"?>

<configuration>

  
<system.web>
    
    
<pages>
      
<controls>
        
<add tagPrefix="scottgu" src="~/Controls/Header.ascx" tagName="header"/>
        <
add tagPrefix="scottgu" src="~/Controls/Footer.ascx" tagName="footer"/>
        <
add tagPrefix="ControlVendor" assembly="ControlVendorAssembly"/>
      </
controls>
    
</pages>

  
</system.web>

</configuration>

 

You can declare both user controls and compiled custom controls this way.  Both are fully supported by Visual Studio when you use this technique -- and both VS 2005 Web Site Projects and VS 2005 Web Application Projects support them (and show the controls in WYSIWYG mode in the designer as well as for field declarations in code-behind files).

One thing to note above is the use of the "~" syntax with the user-controls.  For those of you not familiar with this notation, the "~" keyword in ASP.NET means "resolve from the application root path", and provides a good way to avoid adding "..\" syntax all over your code.  You will always want/need to use it when declaring user controls within web.config files since pages might be using the controls in different sub-directories - and so you always need to resolve paths from the application root to find the controls consistently.

Once you register the controls within the web.config file, you can then just use the controls on any page, master-page or user control on your site like so (no registration directives required):

 

<html>
<body>
    
<form id="form1" runat="server">
        
<scott:header ID="MyHeader" runat="server" />
    </
form>
</body>
</html>

Hope this helps,

Scott

P.S. Special thanks to Phil Haack who blogged about this technique as well earlier this month (for those of you who don't know Phil, he helps build the very popular SubText blog engine and has a great blog).

Published Sunday, November 26, 2006 12:57 PM by ScottGu

Comments

# ScottGu's Blog : ASP.NET 2.0 Tips, Tricks, Recipes and Gotchas

PingBack from http://weblogs.asp.net/scottgu/pages/ASP.NET-2.0-Tips_2C00_-Tricks_2C00_-Recipes-and-Gotchas.aspx

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Sunday, November 26, 2006 5:03 PM by John West

Question: I've been using this sparingly... I'm worried that it might impace performance.  Do you think so?  In other words, if I register 30 user controls and 5 custom server controls in this way, will my pages slow down?  Will compilation slow down?

Thanks!

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Sunday, November 26, 2006 5:15 PM by Haacked

Thanks for the shot out Scott!  Much appreciated, especially from such a great blog and blogger. Yep, I'm a ScottGu fanboy. ;)

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Sunday, November 26, 2006 6:08 PM by ScottGu

Hi John,

The good news is that there isn't any performance difference between registering them in a web.config file vs. at the top of a page.  

The runtime performance is exactly the same (they get compiled down to the same instructions in both scenarios).

Compilation should also compile at the same performance.

Hope this helps,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Sunday, November 26, 2006 6:28 PM by Chris adf471587879rzq

Scott,

There's a mistake in your code.  In the "Solution" in which the registrations are placed in the web.config file, this line:

<scott:header ID="MyHeader" runat="server" />

should be ....

<scottgu:header ID="MyHeader" runat="server" />

because you used a different tag prefix ("scottgu") in the revised version.

Thanks for the info!

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Sunday, November 26, 2006 6:30 PM by ScottGu

Hi Chris,

Good catch! ;-)

Hopefully you got the point anyway! :-)

Thanks,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Sunday, November 26, 2006 10:18 PM by Adam

There seems to be an issue with this method and the designer generated code.

http://forums.asp.net/thread/1453826.aspx

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Sunday, November 26, 2006 11:26 PM by ScottGu

Hi Adam,

There are cases when the designer can't infer the base class of a user-control with VS 2005 Web Application Projects - in which case it just generates a field declaration in the code-behind of the page as type "UserControl".

To get a strongly-typed declaration of the user-control, you can manually add the declaration to your code-behind class (not the .designer class) of the type you want (which is typically the UserControl code-behind class name).

Once you do this the designer will pick this up and use it as the field declaration and you'll get full intellisense.

Hope this helps,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Sunday, November 26, 2006 11:44 PM by vikram

good trick

I have been using this trick but for only those controls which are used through out the Site. I always thought if a user control is used only ate one or 2 place, It should be placed in the Page itself, but if used at many pages in the site then should be declared in the web.config file

Thanks

Vikram

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Monday, November 27, 2006 7:03 AM by Ryan S

With regards to the user control being declared with the incorrect data type in the designer.cs file - does your solution (manually declare the variable in the code behind file) "force" the invalid declaration to be removed from the designer.cs file? Or will there be a duplicate named variable with a different data type?

I am using Web Application Projects and understand this to be a limitation of that project type currently.

Is this something that is going to be addressed in a service pack or hotfix to the web application project?

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Monday, November 27, 2006 10:08 AM by ScottGu

Hi Ryan,

If you declare the control type/name in your code-behind file in a VS 2005 Web Application Project, then the designer automatically avoids adding it to the .designer.cs/.vb file.  

This avoids any duplicate name collisions (note: you'll need to flip once to source view or design view and make a change in order to have the designer remove it from the .designer file - from them on out it will be omitted).

The reason you sometimes need to-do this are in cases where the control is implemented inside the same assembly as the pages.  You can sometimes get into an un-buildable state where the designer can't reflect on the project to infer the type (this isn't a problem with controls implemented in other projects or separate assemblies - since they are already successfully compiled).  This situation is less of a "bug" per-se - and more of a design outcome of building everything within the same assembly.  We are going to continue to look for ways to make this case less common - but there will always be some times when you can't reflect and get the type details.  In cases like this you can just declare the field in your code-behind file and everything will work.

Hope this helps,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Monday, November 27, 2006 2:50 PM by Arash

quite nice! i didn know it exits.

does this add any overhead to the pages that don't implement the controls?

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Monday, November 27, 2006 3:48 PM by ScottGu

Hi Arash,

The good news is that there is no performance penality to using this trick.  It is just as efficient as without.

Thanks,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Monday, November 27, 2006 3:49 PM by ScottGu

Hi Ryan,

I actually just chatted with someone on the VS 2005 Web Application Project feature team, and he actually mentioned that they've added some better detection support for this in SP1.  So you should actually see the cases where you need to declare a field in the code-behind to get a type-specific value go down.

Hope this helps,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Wednesday, November 29, 2006 10:17 AM by Adrian

Hi Scott

I've used another of the <add> variants - to add an assembly reference - in the past and ran into a problem.

Our site is arranged as a set of individual webs - including one at the root of the site.  Like this:

  /

  /admin

  /access

Each of those three would be a separate ASP.NET web application.

Now, the contents of web.config is inherited in these circumstances, I think, so - in the area we're concerned with - the page controls set for /admin starts from the page controls set for the web.

Now, I have custom controls (or assemblies for that matter) that only exist in the root web.  I get run-time errors when I try to access the sub-webs because the assemblies or whatever don't exist.

I could do a <clear/> I think ... but from memory the last time I did that, I cleared the whole set, including everything from machine.config - which wasn't quite what I wanted!

Am I missing something, or is this really a problem?

BTW - I read somewhere that it was a bad idea locating a web at the root (or, I suppose) having sub-webs).  Unfortunately, it's meets the use requirements ...

Thanks

Adrian

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Wednesday, November 29, 2006 3:42 PM by ScottGu

Hi Adrian,

Correct - the collection settings within a web.config file will inherit down to sub-applications by default.

What you can do in your sub-applications is to use <remove/> instead or <clear/> - and just specify the setting you wish to remove.

That way you won't clear everything.

Hope this helps,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Wednesday, November 29, 2006 9:29 PM by Dave Frank

Hey Scott,

I have a usercontrol that exists in my master page, and I access its properties with:

CType(Master, botwmaster).myusercontrol.Visible = False

(for example)

Doing this requires that I add a reference to the control from the page: <%@ Reference Control="~/controls/usercontrol.ascx" %>

My question is, how would I add that reference in the web.config so I don't have to do that in every page?  I'd love to eventually get all control declarations and references to exist in a centralized place (for those that exist on multiple pages)

More food for thought - it would be great if things declared in a master page would bubble down to your aspx page - the control is registered in the master page with @register already.  

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Friday, December 01, 2006 2:26 PM by BT

Thanks for all the information, Scott.

This approach seems cleaner to me than @Register'ing the same controls all over the place. So, I embarked on a 5-minute journey through my application to take the registrations out of the .ASPX pages and all into web.config.

I ran into a problem though on pages where I would instantiate one of my user controls in codebehind and add it to a table cell. I've got a UC story.ascx with a public storyId that renders the content for a story - simple enough. I use the following in codebehind on various pages to stick stories in table cells:

 story myStory;

 myStory = (story)LoadControl("userControls/story.ascx");

 myStory.storyId = 1234;

 cell.Controls.Add(myStory);

However when I moved all the UC registrations to web.config, I ran into "The type or namespace name 'story' could not be found" - can you tell me why or how else I can accomplish this? Thanks!

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Saturday, December 02, 2006 6:46 PM by ScottGu

Hi BT,

If you are dynamically loading and then type-casting the control with a VS 2005 Web Site Project, you can add a <%@ Reference %> element to the top of your page.  This will add an assembly reference for you automatically and allow you to reference it like above.

Hope this helps,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Saturday, December 02, 2006 7:39 PM by ScottGu

Hi Dave,

One option to avoid the <%@ Reference %> directive would be to use the VS 2005 Web Application Project.  This compiles the entire project into a single assembly, and so avoids hte need to explictly reference page/controls.

Hope this helps,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Tuesday, December 05, 2006 7:43 AM by Anupam

Hi Scott,

how do i force a page to load my usercontrol 's dll file form another path, instead of the /bin folder.

reasons for my wanting to do this.

1) i want to keep my control and its dll separate.

2)i don't want the website restarting each time the admin uploads a new usercontrol.

thnx.

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Wednesday, December 06, 2006 4:53 AM by JP

Hi Scott,

This is very useful, but it brings some trouble along in my project: if I use a user control in another user control, and both controls are in the same directory in the project (in your example, ~/Controls/ ), this compile error occurs:

The page '/Controls/Outer.ascx' cannot use the user control '/Controls/Inner.ascx', because it is registered in web.config and lives in the same directory as the page.

I'd rather not move nested controls to a different directory. Is there a solution for this codewise, or will there be a fix in SP1?

Cheers

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Wednesday, December 06, 2006 12:26 PM by Joe

Nice tip.

I changed my pages to use it.

One issue:

I have a user control which uses another user control. They both reside in a folder named \Controls.

I got this error:

The page '/MyApp/Controls/CtlA.ascx' cannot use the user control '/MyApp/Controls/CtlB.ascx, because it is registered in web.config and lives in the same directory as the page.

I just left it the old way.

BTW - that was a very good error message - it alowed me to pinpoint the problem right away.

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Thursday, December 07, 2006 9:47 AM by gbrock_isi

Scot - In a blog from Aug 28 2005 you discussed building a library of user controls and the ability to then consume them in other web applications. Twice in the blog it was asked how to reference the user control in the a page of the consuming application. For example, one entry said

Hi Scott. I am having a similar problem to a previous comment. This all works fine if I include the separate .ascx file. However, as soon as I set the build flag to not allow updating (which as a consequence does not create a separate .ascx file), I cannot get it to work. Your previous response was that ASP.NET can resolve the .ascx references at runtime. How do you register the control? without registering it, compilation fails. Thanks in advance Curtis.

I had been trying to do this for better part of two days and when I say the blog entry I thought, "Finally, I'll get the secret sauce". But your response was completely disappointing..

Hi Curtis,

What I'd recommend is for you to have the user-control project be marked as updatable, and copy the .ascx file into the consuming project.

You could then compile the consuming project as non-updatable.  This will then remove the HTML for both the pages on the site, as well as the user-control you are using.

Hope this helps,

Scott

How can one develop a distributable library of user controls and protect the source, as you claim is possible with the plethora of build options in VS2005, if you can't do this. I just want to know if it's possible or not and what one has to do?

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Friday, December 08, 2006 12:23 AM by ScottGu

Hi Greg,

Here is a blog post that discusses how to convert a user control into a compiled control: http://blogs.msdn.com/davidebb/archive/2005/10/30/487160.aspx

Thanks,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Wednesday, December 13, 2006 10:05 AM by Brian Lowry

To those of you receiving this error: (JP)

The page '/Controls/Outer.ascx' cannot use the user control '/Controls/Inner.ascx', because it is registered in web.config and lives in the same directory as the page.

One solution I have read is to move the control out of the directory its currentyly sharing with outer.ascx, but this isn't feasible in my project.

I found that you can simply re-register the control inside of the outer.ascx:

<%@ Register TagPrefix="Brian" TagName="Inner" Src="Controls/Inner.ascx" %>

and the problem goes away.

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Wednesday, December 20, 2006 7:43 AM by paul landers

this "trick" didn't work for me, when i put the register stuff in the web.config:

<add assembly="App_Web_mytest2uc.ascx.cdcab7d2"

               namespace="XY"

               tagPrefix="xy" />

error:

'The type or namespace name 'XY' could not be found'

i've packed my ascx in an assembly as described here (http://blogs.msdn.com/davidebb/archive/2005/10/30/487160.aspx), but i can only instantiate it within my aspx-code behind, when i register the assembly declarative in the aspx-webpage:

<%@ Register TagPrefix="xy" Namespace="XY" Assembly="App_Web_mytest2uc.ascx.cdcab7d2" %>

any ideas?

# How about WebServices?

Monday, January 15, 2007 5:43 AM by Siderite

 How about Web Services? I have this Web User Control with a static method that uses LoadControl and executes some methods that use the controls inside the WUC. I am using it to render HTML which I then embed into an email. I want to do this inside a Web Service and nothing seems to be working. The error, of course, is that UcUserControl type is not valid in current context, but I can't add a register directive to the asmx, I can't use an assembly directive (I get ASP.NET runtime error: The file 'ucusercontrol.ascx.cs' cannot be processed because the code directory has not yet been built.) and declaring the user control in the web.config as in this article doesn't work.

Any idea how I can use inside a web service the type declared in the code behind of a web user control ?

Thanks.

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Friday, January 19, 2007 10:47 PM by ScottGu

Hi Siderite,

Have you seen this article of mine: http://weblogs.asp.net/scottgu/archive/2006/10/22/Tip_2F00_Trick_3A00_-Cool-UI-Templating-Technique-to-use-with-ASP.NET-AJAX-for-non_2D00_UpdatePanel-scenarios.aspx

It enables you to use .ascx templates within web-services to dynamically generate HTML.

Thanks,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Friday, February 02, 2007 1:19 AM by Siderite

 Thanks, Scott! It's a nice implementation, but it uses reflection. I was looking for an ASP.NET 1.1 like use of the web user control object in other objects, but I guess that's out of the question now.

 It was particularily interesting to see that your code to do the same thing as mine used a completely different method. You used LoadControl, I used Controls.Add (and had a lot of problems with it, of course) and then ServerExecute instead of RenderControl... Very useful stuff. Thanks!

# Problem in registering a user control in wb.config

Friday, February 23, 2007 5:59 AM by nishant

Hi

as u said that instead of registering a user control once per web page that v r going to use it within,its better to register it in web.config file

I did that and gave tagprefix,tagname and src attributes proper values.but the problem is

DO I NEED TO DRAG THE USER CONTROL ON MY WEB PAGE(S)

OR WUD I HAV TO MANUALLY WRITE THE TAGPREFIX AND TAGNAME ETC IN XYZ.ASPX PAGE TO SET THE VALUE OF ID ETC

IF I DO LATER WAY THEN THE VALUES OF TAGPREFIX AND TAGNAME I SPECIFIED ARE NOT PICKED UNDER XYZ.ASPX PAGE.(THEY ARE NOT SHOWN IN POP UP LIST)

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Monday, February 26, 2007 8:39 PM by Richard

This works great but when I view the page(in Design mode) that contains the controls the controls show up in red. An error shows for each control that states: This control cannot be displayed because its TagPrefix is not registered in this Web Form. Also, the same error shows up in the Error List. The odd thing is that I can build the solution, run the project and view the page/controls perfectly. Is there anything that can be done to get rid of this annoying error message?

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Tuesday, February 27, 2007 2:13 AM by ScottGu

Hi Richard,

Can you try closing the project and re-open it to see if it makes any difference?

Are you opening things the same way in VS as they get invoked via IIS?  Meaning, is the web.config file in the root of the project?

Thanks,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Thursday, March 01, 2007 10:32 AM by Stephen Kinsey

Superb tip, works a treat and really helps to make the aspx pages look much neater.

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Friday, March 02, 2007 1:39 AM by Shekhar

Hi Scott !!!

thanks for sharing this useful information with us....

I also want to know how I can refers to User Control which resides in different directory than my project root directory

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Saturday, March 03, 2007 9:24 PM by ScottGu

Hi Shekhar,

You can do this by using the "~" syntax when referencing the controls from the root:

~/subdir/mycontrol.ascx

Hope this helps,

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Monday, March 05, 2007 1:22 AM by Shekhar

Hi Scott !!!

Thanks a lot for ur reply.....

The solution u have given works perfectly when controls are present in inner directory of root directory.

Wat about if the user controls are present in outside of root directory?

is it possible to refer that controls ?

Thanks again for your kind help

Shekhar

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Tuesday, March 06, 2007 10:25 PM by ScottGu

Hi Shekhar,

Unfortunately I don't think it is possible to register user controls outside of the application root directory.  

Sorry!

Scott

# re: Tip/Trick: How to Register User Controls and Custom Controls in Web.config

Wednesday, March 07, 2007 10:46 AM by Justin Wignall

Hi Scott,

I had previously used your tips above and added all of my usercontrols into the web.config file. Great! It's a really useful feature and it worked perfectly.

Now it doesn't. For some reason all of my code has stopped working and when trying to debug the build fails (along with intellisense etc.)

I was adding controls dynamically e.g.

Dim News As Controls_News = LoadControl("~/Controls/News.ascx")

Where Controls_News is the partial class name.

Adding a register directive to the page solves this issue, I am just wondering if you or any of your readers here would know why it worked for a while and then stopped?

I created a new project, cleaned ASP.NET temp files directory and so on and the problem persists.

http://weblogs.asp.net/scottgu/archive/2006/11/26/tip-trick-how-to-register-user-controls-and-custom-controls-in-web-config.aspx

posted @ 2007-08-01 10:30 WebServices 阅读(1147) 评论(0) 编辑

技巧和诀窍:如何在Web.config中注册用户控件和自定义控件

 【原文地址】 Tip/Trick: How to Register User Controls and Custom Controls in Web.config
【原文发表日期】 Sunday, November 26, 2006 12:57 PM

去年我一直把这个技巧包含在我的ASP.NET 技巧/诀窍讲座里,但倘若有这么多人总是为它的存在而感到惊讶的话,我想值得用一个专属的技巧/诀窍帖子来提高它的可见度 (点击这里阅读我的ASP.NET 技巧/诀窍系列里的其他帖子)。

问题:

在ASP.NET 的早先版本里,开发人员通过在页面的顶部添加 <%@ Register %> 指令来引入和使用自定义服务器控件和用户控件时,象这样:

<%@ Register TagPrefix="scott" TagName="header" Src="Controls/Header.ascx" %>
<%@ Register TagPrefix="scott" TagName="footer" Src="Controls/Footer.ascx" %>
<%@ Register TagPrefix="ControlVendor" Assembly="ControlVendor" %>

<html>
<body>
    
<form id="form1" runat="server">
        
<scott:header ID="MyHeader" runat="server" />
    </
form>
</body>
</html>

 

注意到上面的前两个注册指令是用来注册用户控件的(是在.ascx文件里实现的),最后这个是用来注册编译进一个程序集 .dll 文件里的自定义控件的。注册完后,开发人员可以在页面的任何地方用设定好的 tagprefix (标识前缀)和标识符号名( tagname)来声明这些控件。

这行之有效,但管理起来会很痛苦,当你要在你的网站的许多页面上使用控件的话,尤其是,假如你移动了.ascx 文件,需要更新所有的注册声明的话。

解决方案:

ASP.NET 2.0 使得控件声明极其干净而且管理起来极其容易。不用在你的页面上重复这些声明,只要在你的应用的web.config 文件的新的 pages->controls 部分声明一次即可:

<?xml version="1.0"?>

<configuration>

  
<system.web>
    
    
<pages>
      
<controls>
        
<add tagPrefix="scottgu" src="~/Controls/Header.ascx" tagName="header"/>
        <
add tagPrefix="scottgu" src="~/Controls/Footer.ascx" tagName="footer"/>
        <
add tagPrefix="ControlVendor" assembly="ControlVendorAssembly"/>
      </
controls>
    
</pages>

  
</system.web>

</configuration>

 

你可以用这种方式同时声明用户控件和编译好的自定义控件。当你使用这个技巧时,Visual Studio是完全支持这两者的,而且 VS 2005 Web Site 项目 和 VS 2005 Web Application 项目也都支持这两者。Visual Studio会在设计器里以所见即所得(WYSIWYG)模式显示这些控件,也会在后台编码文件里提示控件字段的声明。

需要注意的是,上面用户控件中“~”句法的使用。对那些不熟悉这个符号的人,ASP.NET中“~”符号意思是“从应用的根路径来定位”,它提供了一个很好的方法来避免在你的编码里到处使用“..\”。在web.config文件里声明用户控件时,你总是应该使用它,因为页面也许会使用在不同子目录里的控件,所以你应该总是始终如一地从应用的根路径开始定位这些控件。

一旦你在web.config 文件中声明好这些控件后,你就可以在你网站上的任何一个页面,母板页或者用户控件中使用它们了,象这样(不再需要注册指令):

 

<html>
<body>
    
<form id="form1" runat="server">
        
<scottgu:header ID="MyHeader" runat="server" />
    </
form>
</body>
</html>

希望本文对你有所帮助,

Scott

附注:特别感谢 Phil Haack ,他在这个月的早先时候也曾在博客里讨论过这个技巧。对你们中间那些不认识 Phil 的人,他帮忙建造了非常受欢迎的 SubText博客引擎,而且拥有一个非常精彩的博客。

本文转自http://blog.joycode.com/scottgu/archive/2006/11/27/88083.aspx

posted @ 2007-08-01 10:26 WebServices 阅读(228) 评论(1) 编辑

2007年7月18日

ArcGIS的网络分析

  ArcGIS的网络分析ArcGIS的网络分析分为两类:传输网络(Network Analyst)和效用网络(Utility Network Analyst)

一、从应用上来考虑:

1.
传输网络常用于道路、地铁等交通网络分析。

特点:在传输网络中,汽车和火车都是可以自由移动的物体,具有主观选择方向的能力。

传输网络解决的问题有:

A.
计算点与点之间的最佳距离,时间最短或者距离最短,最佳路径能够绕开事先设置的障碍物

B.
可以进行多点的物流派送,能够按照规定时间规划送货路径,也能够自由调整各点的顺序,也会绕开障碍物

C.
寻找最近的一个或者多个设施点

D.
确定一个或者多个设施点的服务区,绘制服务区范围的条件可以是多个,例如,同时列出3分钟、6分钟、9分钟的服务区

E.
绘制起点-终点距离矩阵

2.
效用网络常用于水、电、气等管网的连通性分析。

特点:在效用网络中,水、电、气通过管道和线路输送给消费者,水、电、气被动地由高压向低压输送,不能主观选择方向。

效用网络解决的问题有:

A.
寻找 连通的/不连通的 管线

B.
/下游追踪

C.
寻找环路

D.
寻找通路

E.
爆管分析

posted @ 2007-07-18 15:24 WebServices 阅读(210) 评论(0) 编辑

欢迎光临我的博客

欢迎光临我的博客

posted @ 2007-07-18 15:23 WebServices 阅读(25) 评论(0) 编辑