[教程]ASP.Net MVC的Html.ActionLink解析2

mikel阅读(812)

在《ASP.NET MVC框架配置与分析》这篇文章中,已经介绍了MVC框架的配置,并且简单讲述了它的运行机制。本文将重点描述,MVC框架中默认的地址重写。
1、注册地址重写

MVC的地址重写必须在Global.asax.cs中初始化,从而保证所有的请求都能被Controller控制。简单的意思就是必须在运行期,应用程 序启动的时候被初始化。为什么要到运行期,而不发生在编译器,为什么不能用静态构造函数来完成呢?那是因为编译期连地址都没确定,怎么能够保证地址是正确 的呢?
在Global.asax.cs中默认有两个重写方法被注册

 1         protected void Application_Start(object sender, EventArgs e)
 2         {
 3             // Note: Change Url= to Url="[controller].mvc/[action]/[id]" to enable 
 4             //       automatic support on IIS6 
 5 
 6             RouteTable.Routes.Add(new Route
 7             {
 8                 Url = "[controller]/[action]/[id]",
 9                 Defaults = new { action = "Index", id = (string)null }   ,
10                 RouteHandler = typeof(MvcRouteHandler)
11             });
12 
13 
14             RouteTable.Routes.Add(new Route
15             {
16                 Url = "Default.aspx",
17                 Defaults = new { controller = "Home", action = "Index", id = (string)null },
18                 RouteHandler = typeof(MvcRouteHandler)
19             });
20 
21 
22         }

第一个注册方法是描述了一组规则,而第二组方法描述了根目录下的Default.aspx如何交由controller 来处理。
2、controller 处理地址

比如,我们现在的URL地址是www.yurow.cn,那么,当请求www.yurow.cn的时候,实际上是请求的Default.aspx,这个时候,程序找到规则Defaults = new { controller = "Home", action = "Index", id = (string)null },(这个是默认规则,所有的默认规则都可以通过Defaults = object的方式来调用,当然也有例外,下面会讲到。)。默认规则告诉程序,这个页面请求要交给一个类名为HomeController的控制器来处理(controller = "Home", 就是用控制器的类名减去Controller,这点和Attribute非常相似)。而action = "Index"告诉程序,请求的是控制器中的Index方法,最后的 id = (string)null 则是默认参数。在MVC中,默认参数只有一个,就是id。而这里id值是null。在默认HomeController类中也有两个方法

 1         [ControllerAction]
 2         public void Index()
 3         {
 4             RenderView("Index");
 5         }
 6 
 7         [ControllerAction]
 8         public void About()
 9         {
10                RenderView("About");
11         }


请求Default.aspx调用Index方法后,将输出定位到Index.aspx(RenderView("Index");)。这时候显示的就是Index.aspx。当然,这时候浏览器显示的地址是http://www.yurow.cn/,实际上还可以有另外一种表示方式http://www.yurow.cn/Home/Index
而“域名/controller/action/id” 才是MVC真正的地址现实方式。映射的地址则是“根路径 /Views/controller/action(.aspx)”。id参数不是交由aspx文件处理的,而是交由controller处理,这点我认 为就是和WebForm模型最大的不同。
再来看看注册的第一组规则: Url = "[controller]/[action]/[id]",定义了URL的路径就是上面所说的“域名/controller/action/id”,在这里就是http://www.yurow.cn/Home/About 。
3、controller 到View的数据传递

这两组定义中似乎都没有用到id参数,id到底体现在哪里呢?
尝试在HomeController和About.aspx.cs中接收id参数Request["id"],然后把地址变成http://www.yurow.cn/Home/About/1,结果Request["id"]是null并不是我们想象中的'1'。而事实上是这样接收这个id参数的:

        [ControllerAction]
        
public void About(string id)
        {
            
if (string.IsNullOrEmpty(id))
                id 
= "";
                RenderView(
"About", (object)id);
            
        }

在About.aspx.cs中要这样使用

1     public partial class About : ViewPage< string>
2     {
3 
4         protected void Page_Load(object sender, EventArgs e)
5         {
6             Response.Write(ViewData);
7         }
8     }

ViewPage< string>定义了About.aspx.cs接收Controller里传递过来的 (object)id,并且申明这是一个string类型。而ViewData就是这个值。当然也可以定义成任何类型,因为这里接收的类型是object。
4、重写后的地址构造
MVC提供了自动构造重写地址的方法——Html.ActionLink。
Html.ActionLink一共有3个重载。
1)先说简单的public string ActionLink(string linkText, string actionName);
参数一是链接显示的文字,而参数二是action的名称。比如Html.ActionLink("链接文字","About");
链接地址实际是< a href="/Home/About">链接文字< /a>。地址中的Home是怎么来的呢?就是Defaults里定义的。注意,如果你不去改写,那么赋值了一次,无论是controller 还是action或者id的值都是一直不变的。而这个Home在注册的时候就已经被赋值了。
2)public string ActionLink(string linkText, string actionName, string controllerName);
参数一和参数二和1)相同,参数三则是controller的名称。比如你可以赋值
Html.ActionLink("链接文字","MyPage","Test");
链接地址实际是< a href="/Test/MyPage">链接文字< /a>。明明没有Test控制器,也没有MyPage页面,怎么能够定义成功?这就是因为,虽然参数名是这样定义的,实际上这仅仅是一个字符串,代表要请求的目录(controllerName),和aspx文件(actionName)。
3)public string ActionLink(string linkText, object values);
这个是自定义式的赋值。可以这样写:
< %= Html.ActionLink("About Us", new { id="1",controller="Home", action="About" })%>
实际地址就是< a href="/Home/About/1">About Us"< /a>。

[教程]ASP.Net MVC的Html.ActionLink解析

mikel阅读(866)

Html.ActionLink 方法用于指向Controller的Action
ActionLink方法定义的代码

//页面显示linkText指向Global.asax.cs中默认的Controller对象的actionName方法
public string ActionLink(string linkText, string actionName) {
if (String.IsNullOrEmpty(linkText)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
}
if (String.IsNullOrEmpty(actionName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}
return GenerateLink(linkText, null /* routeName */, actionName, null /* controllerName */, new RouteValueDictionary());
}
//页面显示linkText指向默认Global.asax.cs中默认的Controller对象的actionName方法并且带的参数值values
public string ActionLink(string linkText, string actionName, object values) {
if (String.IsNullOrEmpty(linkText)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
}
if (String.IsNullOrEmpty(actionName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}
return GenerateLink(linkText, null /* routeName */, actionName, null /* controllerName */, new RouteValueDictionary(values));
}
//页面显示linkText指向定义的RouteValueDictionary中的Controller对象的actionName方法
public string ActionLink(string linkText, string actionName, RouteValueDictionary valuesDictionary) {
if (String.IsNullOrEmpty(linkText)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
}
if (String.IsNullOrEmpty(actionName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}
if (valuesDictionary == null) {
throw new ArgumentNullException("valuesDictionary");
}
return GenerateLink(linkText, null /* routeName */, actionName, null /* controllerName */, new RouteValueDictionary(valuesDictionary));
}
//页面显示linkText指向ControllerName对应的Controller的actionName方法
public string ActionLink(string linkText, string actionName, string controllerName) {
if (String.IsNullOrEmpty(linkText)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
}
if (String.IsNullOrEmpty(actionName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
return GenerateLink(linkText, null /* routeName */, actionName, controllerName, new RouteValueDictionary());
}
//
public string ActionLink(string linkText, string actionName, string controllerName, object values) {
if (String.IsNullOrEmpty(linkText)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
}
if (String.IsNullOrEmpty(actionName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
return GenerateLink(linkText, null /* routeName */, actionName, controllerName, new RouteValueDictionary(values));
}
public string ActionLink(string linkText, string actionName, string controllerName, RouteValueDictionary valuesDictionary) {
if (String.IsNullOrEmpty(linkText)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
}
if (String.IsNullOrEmpty(actionName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
if (valuesDictionary == null) {
throw new ArgumentNullException("valuesDictionary");
}
return GenerateLink(linkText, null /* routeName */, actionName, controllerName, new RouteValueDictionary(valuesDictionary));
}

使用实例代码

<li>
<%= Html.ActionLink("Home","Index")%>
</li>
<li>
<%= Html.ActionLink("About","About")%>
</li>
<li>
<%=Html.ActionLink("Products","Detail","Products" )%>
</li>
<li>
<%=Html.ActionLink("Products","Detail","Products" ,2)%>
</li>

[教程]ASP.Net MVC定义URL Routing

mikel阅读(740)

文/QLeelulu  出处/博客园+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
定义URL Routing +2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
在一个route中,通过在大括号中放一个占位符来定义( { and } )。当解析URL的时候,符号"/"和"."被作为一个定义符来解析,而定义符之间的值则匹配到占位符中。route定义中不在大括号中的信息则作为常量值。+2½_U‚JåU˜www.pin5i.comùsP©6è—
下面是一些示例URL: +2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—

+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
通 常,我们在Global.asax文件中的Application_Start事件中添加routes,这确保routes在程序启动的时候就可用,而且 也允许在你进行单元测试的时候直接调用该方法。如果你想在单元测试的时候直接调用它,注册该routes的方法必需是静态的同时有一个 RouteCollection参数。+2½_U‚JåU˜www.pin5i.comùsP©6è—
下面的示例是Global.asax中的代码,演示了添加一个包含两个URL参数action 和 categoryName的Route对象:+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—

引用:
protected void Application_Start(object sender, EventArgs e)+2½_U‚JåU˜www.pin5i.comùsP©6è—
{+2½_U‚JåU˜www.pin5i.comùsP©6è—
    RegisterRoutes(RouteTable.Routes);+2½_U‚JåU˜www.pin5i.comùsP©6è—
}+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
public static void RegisterRoutes(RouteCollection routes)+2½_U‚JåU˜www.pin5i.comùsP©6è—
{+2½_U‚JåU˜www.pin5i.comùsP©6è—
    routes.Add(new Route+2½_U‚JåU˜www.pin5i.comùsP©6è—
    (+2½_U‚JåU˜www.pin5i.comùsP©6è—
        "Category/{action}/{categoryName}"+2½_U‚JåU˜www.pin5i.comùsP©6è—
        , new CategoryRouteHandler()+2½_U‚JåU˜www.pin5i.comùsP©6è—
    ));+2½_U‚JåU˜www.pin5i.comùsP©6è—
}

+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
设置Route参数的默认值 +2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
当你定义个一route的时候,你可以分配一个默认值给route的参数。默认值是当URL中没有包含参数的值的时候使用的。你可以在Route类中通过dictionary来设置Default属性来设置route的默认值: +2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—

引用:
void Application_Start(object sender, EventArgs e) +2½_U‚JåU˜www.pin5i.comùsP©6è—
{+2½_U‚JåU˜www.pin5i.comùsP©6è—
    RegisterRoutes(RouteTable.Routes);+2½_U‚JåU˜www.pin5i.comùsP©6è—
} +2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
public static void RegisterRoutes(RouteCollection routes)+2½_U‚JåU˜www.pin5i.comùsP©6è—
{+2½_U‚JåU˜www.pin5i.comùsP©6è—
  routes.Add(new Route+2½_U‚JåU˜www.pin5i.comùsP©6è—
  (+2½_U‚JåU˜www.pin5i.comùsP©6è—
    "Category/{action}/{categoryName}"+2½_U‚JåU˜www.pin5i.comùsP©6è—
          new CategoryRouteHandler()+2½_U‚JåU˜www.pin5i.comùsP©6è—
  )+2½_U‚JåU˜www.pin5i.comùsP©6è—
    {+2½_U‚JåU˜www.pin5i.comùsP©6è—
      Defaults = new RouteValueDictionary +2½_U‚JåU˜www.pin5i.comùsP©6è—
          {{"categoryName", "food"}, {"action", "show"}}+2½_U‚JåU˜www.pin5i.comùsP©6è—
    }+2½_U‚JåU˜www.pin5i.comùsP©6è—
  );+2½_U‚JåU˜www.pin5i.comùsP©6è—
}

+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
当URL Routing处理URL Request的时候,上面route定义产生的结果如下表所示:  +2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—
+2½_U‚JåU˜www.pin5i.comùsP©6è—

+2½_U‚JåU˜www.pin5i.comùsP©6è—拼吾爱程序人生

[教程]使用.Net的CodeDOM技术实现语言无关的Code Wizard

mikel阅读(783)

本文曾发表于It168:http://tech.it168.com/m/2007-09-11/200709111500188.shtml

摘要:CodeDOM是.net framework的一项重要的源代码生成技术。本文详细讨论了CodeDOM的原理以及如何利用CodeDOM技术实现一个与语言无关的Code Wizard。并给出了一个用C#语言实现的例子。

关键字:Code Wizards、CodeDOM、.net framework数据表、模板文件

一、什么是CodeDom?

现在的程序规模越来越 大,虽然在计算发展的几十年间,产生了许多快捷、高效的编程语言和开发工具,如C#、Visual Studio、java等。也产生了许多用以辅助软件设计、开发的思想和方法,如UML、OOP、Agile等。尽管利用这些技术和方法可以大大提高程序 编写的效率,但是仍可能有重复的编码工作。因此,现在出现了许多可以自动产生源代码或者目标文件的软件,即Code Wizards。
   
一般这些Code Wizards在生成源代码时都是通过设置模板文件,然后根据这些模板文件生成源代码。有很多Code Wizards只能生成固定的语言(如java、C#等)。虽然有一些Code Wizards可 以生成多种语言,但也只是固定的几种。而且生成源代码部分都是显示地固定在程序中。这样非常不易扩展。如CodeSmith系统,这是一个非常不错的 Code Wizard。它使用一个扩展名为cst的文件来设置模板。这个模板文件的格式类似于ASP.NET。如果想生成C#源代码,必须要在其中显示地标明,并 且模板的固定部分要使用C#语言编写。如果这样的话,同样功能要生成不同语言的代码,如C#和VB.net。就要编写两个模板文件。这是非常不方便的。
   
从以上的描述来看, Code Wizard所 面临的一个重要问题就是如何使用一个模版文件来生成不同语言的源代码。幸好Microsoft提供了一种解决方案,这就是CodeDOM技术。 CodeDOM的全称是代码文档对象模型(Code Document Object Model)。整个CodeDOM就是一张对象图(object graph)。它用这张图中的所有对象描述了面向对象语言中的几乎所有的语法现象,如类、接口、方法、属性等。
CodeDOM通过对象模型对语言进行了抽象,然后利用具体语言所提供的生成源代码的机制来生成源代码,并可调用相应的编译器将源码生成*.dll或*.exe。从而可以达到与语言无关的目的。图1描述了使用CodeDOM生成和编译源代码的过程。

图1 CodeDom生成和编译源代码的过程

从上图可以看出,CodeWizard只使用CodeDOM对语言进行抽象,然后通过CodeDomProvider生成源代码。最后通过编译器生成中间语言。下面将详细讨论如何利用CodeDOM来实现CodeWizard。

二、实现CodeWizard

下面要实现的这个 CodeWizard非常简单。其功能主要是将一个数据表映射成一个类。这个类提供了Add和Save方法以及和数据表的每个字段相对应的属性。使用这个 类可以向数据表添加记录。为了便于描述,将这个数据表保存成xml文件格式。每条记录为一个item结点,每一个字段为这个结点的一个属性。表名为这个 xml文件的根结点名称。这个xml文件的格式如下所示:

<MyTable>    
    
<item id = "01 " name = "Bill "/>
    
<item id = "02" name = "Mike" />
</MyTable>

这个CodeWizard通过一个模板文件来定义数据表的结构。模板文件的格式如下:

<MyTable>
    
<id type = "System.Int32"/>
    
<name type = "System.String"/>
</MyTable>

其中type为字段的类 型,它的值是在.net framework中的System中定义的简单类型,如System.Int32、System.String、System.Char等。下面就详细 讨论如何利用这个模板文件和CodeDOM技术来生成C#和VB.net的源代码。

三、CodeDOM的结构

CodeDOM由两部分组成:

1. 用于描述抽象代码结构的一组类。其中CodeCompileUnit类是这些类的根。代表一个源码文件(如C#的*.cs和VB.net的*.vb)。在使用CodeDOM时,必须先建立一个CodeCompileUnit类的对象,然后在这个对象中加入必要的namespace、class等面向对象元素。

用于生成和编译源代码的类。这个类必须从CodeDomProvider类继承。每种.net framework

所支持的语言都有自己的CodeDomProvider类。如在C#中的CodeDomProvider类叫CSharpCodeProvider,而在VB.net中叫VBCodeProvider

四、数据表类的定义

要用CodeDOM定义一个类需要三步:

1. 建立一个CodeCompileUnit对象。这个类相当于一个源码文件。

2. 建立一个CodeNamespace对象。理论上在.net framework上运行的程序语言,如C#、VB.net

等,可以没有namespace。但在CodeDOM中必须使用这个类,如果不想要namespace,可以将namespace的名字设为 null或空串。

建立一个CodeTypeDeclaration对象。这个类可以建立Class和Interface两种Type。在

这个例子中只建立Class。如果想建立Interface,只需将IsInterface属性设为true即可。

主要的实现代码如下:

    private CodeCompileUnit m_CodeCompileUnit;
    private CodeNamespace m_CodeNameSpace;
    private CodeTypeDeclaration m_Class;
    
private void InitCodeDom()
    {
        m_CodeCompileUnit 
= new CodeCompileUnit();
        m_CodeNameSpace 
= new CodeNamespace("xml.tables"); 
        m_CodeCompileUnit.Namespaces.Add(m_CodeNameSpace);
        m_Class 
= new CodeTypeDeclaration(m_ClassName);
        m_CodeNameSpace.Types.Add(m_Class);
}

其 中namespace的名子是“xml.tables”。在建立完namespace后,将其加入到m_CodeCompileUnit的 Namespaces集合中。m_ClassName是一个String变量,它的值就是数据表的表名。最后将所建立的类加入到namespace的 Types集合中。在产生完类后。需要在这个类中加入四部分内容,它们分别是:全局变量、属性、构造函数和方法(Add和Save方法)。下面就分别讨论 它们的实现过程。

五、全局变量的生成

这个数据表类中有四种全局变量:用于操作xml文件的类型为XmlDocument的变量、用于保存数据表文件名的变量、用于确定是否为加入状态的Boolean型变量、以及用于保存每个字段值的变量组。具体实现代码如下:

private void GenerateFields()
{
    
// 产生 "private XmlDocument m_xml = new XmlDocument();"  
    CodeMemberField xml = new CodeMemberField("System.Xml.XmlDocument""m_xml");
    CodeObjectCreateExpression createxml 
= new CodeObjectCreateExpression("System.Xml.XmlDocument");
    xml.InitExpression 
= createxml;
    m_Class.Members.Add(xml);
    
// 产生 "private String m_XmlFile;"  
    CodeMemberField xmlfile = new CodeMemberField("System.String""m_XmlFile");
    m_Class.Members.Add(xmlfile);
// 根据模板文件产生保存字段值的变量
   
String fieldname = "", fieldtype = "";
    
foreach (XmlNode xn in m_Xml.DocumentElement.ChildNodes)
    {
         fieldname 
= "m_" + xn.Name;
         fieldtype 
= xn.Attributes["type"].Value;
         CodeMemberField field 
= new CodeMemberField(fieldtype, fieldname);
         m_Class.Members.Add(field);
    }
    
// 产生 "private bool m_AddFlag;" 
    CodeMemberField addflag = new CodeMemberField("System.Boolean""m_AddFlag");
    m_Class.Members.Add(addflag);
}

在以上代码中每段程序上方的注释是它们所生成的C#源代码。在输入这段代码之前,需要引入两个namespace。

using System.CodeDom;
using System.CodeDom.Compiler;

五、属性的生成

    在数据表类中每个属性代表数据表的一个字段,名子就是字段名。这些属性和保存字段的全局变量一一对应。下面是具体的实现代码:

private void GenerateProperties()
{
    String fieldname 
= "", fieldtype = "";
    
foreach (XmlNode xn in m_Xml.DocumentElement.ChildNodes)
    {
        fieldname 
= xn.Name;
        fieldtype 
= xn.Attributes["type"].Value;
        CodeMemberProperty property 
= new CodeMemberProperty();
        property.Attributes 
= MemberAttributes.Public | MemberAttributes.Final;
        property.Name 
= fieldname;
        property.Type 
= new CodeTypeReference(fieldtype);
        property.HasGet 
= true;
        property.HasSet 
= true;
        CodeVariableReferenceExpression field 
= new CodeVariableReferenceExpression("m_" + fieldname);
        
// 产生 return m_property
        CodeMethodReturnStatement propertyReturn = new CodeMethodReturnStatement(field);
        property.GetStatements.Add(propertyReturn);
        
// 产生 m_property = value;
        CodeAssignStatement propertyAssignment = new CodeAssignStatement(field,
        new
 CodePropertySetValueReferenceExpression());
        property.SetStatements.Add(propertyAssignment);
        m_Class.Members.Add(property);
    }
}

这些生成的属性是可读写的。这就需要将HasGet和HasSet两个属性设为true,然后分别将get和set方法中的语句分别加到GetStatements和SetStatements中。

六、构造函数的生成

    构造函数的主要工作是打开数据表。如果数据表不存在,就创建这个数据表文件。在编写代码之前,需要先定义三个全局变量。因为这三个全局变量在程序中会多次用到。它们的类型都是CodeVariableReferenceExpression这个类型变量其实在生成源码中的作用就是对某一个变量的引用。具体的实现代码如下:

private CodeVariableReferenceExpression m_XmlFileExpression;
private CodeVariableReferenceExpression m_XmlExpression;
private CodeVariableReferenceExpression m_AddFlagExpression;        
private void InitVariants()
{
m_XmlFileExpression 
= new CodeVariableReferenceExpression("m_XmlFile");
    m_XmlExpression 
= new CodeVariableReferenceExpression("m_xml");
    m_AddFlagExpression 
= new CodeVariableReferenceExpression("m_AddFlag");
}

下面是生成构造函数的源代码:

private void GenerateConstructor()
{
    
// 定义构造函数
    CodeConstructor constructor = new CodeConstructor();
    constructor.Parameters.Add(
new CodeParameterDeclarationExpression("System.String""xmlFile"));
    constructor.Attributes 
= MemberAttributes.Public;
    
// 产生 "m_XmlFile = xmlFile;" 
    CodeAssignStatement assignXmlFile = new CodeAssignStatement(m_XmlFileExpression,
    new
 CodeVariableReferenceExpression("xmlFile"));
    
// 产生 "m_xml.LoadXml("…");"
    CodeMethodInvokeExpression invokeLoadXml = new CodeMethodInvokeExpression(m_XmlExpression, "LoadXml",
   new CodePrimitiveExpression("<?xml version=\"1.0\" encoding=\"gb2312\" ?><" + m_Xml.DocumentElement.Name
   +
 "></" + m_Xml.DocumentElement.Name + ">"));
    
// 产生 "m_xml.Save(m_XmlFile);"
    CodeMethodInvokeExpression invokeSave = new CodeMethodInvokeExpression(m_XmlExpression, "Save",
    m_XmlFileExpression);
    CodeStatementCollection statements 
= new CodeStatementCollection();
    statements.Add(invokeLoadXml);
    statements.Add(invokeSave);
    
// 产生if语句:  "if (System.IO.File.Exists(m_XmlFile))  else  "
    CodeConditionStatement ifStatement = new CodeConditionStatement(new CodeMethodInvokeExpression(
   new
 CodeVariableReferenceExpression("System.IO.File"), "Exists", m_XmlFileExpression), new CodeStatement[] { } ,
   new CodeStatement[] { statements[0], statements[1] });
    
// 产生 "m_xml.Load(m_XmlFile);"
    CodeMethodInvokeExpression invokeLoad = new CodeMethodInvokeExpression(m_XmlExpression, "Load",
     m_XmlFileExpression);
    
// 产生 "m_AddFlag = false;"
    CodeAssignStatement assignAddFalse = new CodeAssignStatement(m_AddFlagExpression,
    new CodePrimitiveExpression(false));
    constructor.Statements.Add(assignXmlFile);
    constructor.Statements.Add(ifStatement);
    constructor.Statements.Add(invokeLoad);
    constructor.Statements.Add(assignAddFalse);
     m_Class.Members.Add(constructor);
}


七、Add和Save方法生成

Add方法只有一条语句,功能是将m_AddFlag设为true,以使数据表类处于加入状态。Save方法比较复杂。它的功能是当m_AddFlag为true时在数据表文件的最后加入一条记录,并保存。具体实现代码如下:

private void GenerateMethods()
{
CodeTypeReference voidReference 
= new CodeTypeReference("System.void");
    
//产生Add方法
    CodeMemberMethod add = new CodeMemberMethod();
    add.ReturnType 
= voidReference;
    add.Name 
= "add";
    add.Attributes 
= MemberAttributes.Public | MemberAttributes.Final;
    CodeAssignStatement assignAddTrue 
= new CodeAssignStatement(m_AddFlagExpression,
   
new CodePrimitiveExpression(true));
    add.Statements.Add(assignAddTrue);
    m_Class.Members.Add(add);
    
//产生Save方法
    CodeMemberMethod save = new CodeMemberMethod();
   save.ReturnType 
= voidReference;
   save.Name 
= "save";
   save.Attributes 
= MemberAttributes.Public | MemberAttributes.Final;
   System.Collections.Generic.List
<CodeStatement> ifStatements =
  
new System.Collections.Generic.List<CodeStatement>();
   
//产生 "XmlNode xn = m_xml.CreateNode(XmlNodeType.Element, "item", "");"
  CodeVariableDeclarationStatement xmlNode = new CodeVariableDeclarationStatement("System.Xml.XmlNode""xn");
   CodeMethodInvokeExpression createNode 
= new CodeMethodInvokeExpression(m_XmlExpression, "CreateNode"
  new CodeExpression[] {new CodeVariableReferenceExpression("System.Xml.XmlNodeType.Element"),
 
new CodePrimitiveExpression("item"),
  new
 CodePrimitiveExpression("") });
   xmlNode.InitExpression 
= createNode;
   ifStatements.Add(xmlNode);
   
//产生 "XmlAttribute xa = null; "
   CodeVariableDeclarationStatement xmlAttr = new CodeVariableDeclarationStatement("System.Xml.XmlAttribute""xa");
   xmlAttr.InitExpression 
= new CodePrimitiveExpression(null);
   ifStatements.Add(xmlAttr);
//产生字段属性
   CodeStatementCollection statements = new CodeStatementCollection();
   
foreach (XmlNode xn in m_Xml.DocumentElement.ChildNodes)
   {
   CodeMethodInvokeExpression createAttribute 
= new CodeMethodInvokeExpression(m_XmlExpression,
           
"CreateAttribute"new CodePrimitiveExpression(xn.Name));
       CodeAssignStatement assignxa 
= new CodeAssignStatement(
        new
 CodeVariableReferenceExpression("xa"), createAttribute);
       CodeMethodInvokeExpression invokeToString 
= new CodeMethodInvokeExpression(
        new
 CodeVariableReferenceExpression("m_" + xn.Name), "ToString");
       CodeAssignStatement assignValue 
= new CodeAssignStatement(
     
new CodeVariableReferenceExpression("xa.Value"), invokeToString);
       CodeMethodInvokeExpression invokeAppend 
= new CodeMethodInvokeExpression(
      new
 CodeVariableReferenceExpression("xn.Attributes"),
       "Append"new CodeVariableReferenceExpression("xa"));                
       statements.Add(invokeAppend);
       ifStatements.Add(assignxa);
       ifStatements.Add(assignValue);
       ifStatements.Add(statements[
0]);
   }
   
// 产生 "m_xml.DocumentElement.AppendChild(xn);"
   CodeMethodInvokeExpression invokeAppendChild = new CodeMethodInvokeExpression(new
  CodeVariableReferenceExpression(
"m_xml.DocumentElement"), "AppendChild",
   new
 CodeVariableReferenceExpression("xn"));
   statements.Clear();
   statements.Add(invokeAppendChild);
    ifStatements.Add(statements[
0]);
    
// 产生 "m_xml.Save(m_XmlFile);"
    CodeMethodInvokeExpression invokeSave = new CodeMethodInvokeExpression(m_XmlExpression,
  
"Save", m_XmlFileExpression);
    statements.Clear();
    statements.Add(invokeSave);
    ifStatements.Add(statements[
0]);
    
// 产生 "m_AddFlag = false;" 
    CodeAssignStatement assignAddFalse = new CodeAssignStatement(m_AddFlagExpression,
   
new CodePrimitiveExpression(false));
    ifStatements.Add(assignAddFalse);
// 产生if语句: "if (m_AddFlag)"
    CodeConditionStatement ifStatement = new CodeConditionStatement(m_AddFlagExpression,
    ifStatements.ToArray());
    save.Statements.Add(ifStatement);
    m_Class.Members.Add(save);
}

八、生成源代码

生成具体语言的源代码需要一个从CodeDomProvider继承的类。对于C#而言是CSharpCodeProvider类。实现代码如下:

using Microsoft.CSharp;
public void SaveCSharp(String filename)
{
IndentedTextWriter tw 
= new IndentedTextWriter(new StreamWriter(filename, false), "    ");
    CodeDomProvider provide 
= new CSharpCodeProvider(); 
    provide.GenerateCodeFromCompileUnit(m_CodeCompileUnit, tw, 
new CodeGeneratorOptions());
    tw.Close();
}

在使用CSharpCodeProvider类时需要用到m_CodeCompileUnit这个全局变量。这样可产生一个*.cs文件。以上代码中的IndentedTextWriter类是建立一个文件的Writer,用于向这个文件中输出源代码。但和其它的Writer不同的是它的输出是缩进的(以四个空格进行缩进)。如是想生成VB.net的代码,只需将CSharpCodeProvider改为VBCodeProvider即可。

九、编译源代码

到 现在为止,这个数据表类的源代码已经全部生成了。你可以将这个源文件直接加入到自己的工程中。或者直接将其编译成*.dll文件,然后在程序中调用。如果 想编译,可以直接调用指定语言的编译器(如C#中的csc.exe)。但这样不是太方便。在CodeDOM中提供了一种机制,可以在程序中通过 CodeDomProvider直接调用指定语言的编译器。下面是编译C#源程序的一个例子。

public void CompileCSharp(String sourcefile, String targetFile)
{
CompilerParameters cp 
= new CompilerParameters(new String[] { "System.Xml.dll" },  targetFile, false);
   CodeDomProvider provider 
= new CSharpCodeProvider();
   cp.GenerateExecutable 
= false;
   
// 调用编译器
   CompilerResults cr = provider.CompileAssemblyFromFile(cp, sourcefile);
   
if (cr.Errors.Count > 0)
   {
       
// 显示编译错误
       foreach (CompilerError ce in cr.Errors)
           System.Windows.Forms.MessageBox.Show(ce.ToString());
   }
}

对于以上代码有两点说明:

  1. 使用CodeDomProvider调用编译器时也需要传递相应的参数,如在本例中将System.Xml.dll

作为一个参数,表示目标文件需要调用这个dll中的资源。

  1. 在调用编译器后,如果出现错误,可使用cr.Errors获得错误信息。

十、结束语

我花了一个晚上的时间实现了这个简单的例子,并用C#2.0调试通过,只是为了抛砖引玉。自动生成源代码有很多的方法,但使用CodeDom生成源代码会有更大的灵活性,主要表现在以下三个方面:

1.     语言无关。即只要是.net framework所支持的语言,并且这种语言提供了CodeDomProvider。

就可以生成这种语言的源代码。

2.     如果所生成的语言是测试版或要将这种语言升级到下一个版本,也可以考虑使用CodeDOM。

因为当这种语言的语法有所变化时,CodeDomProvider也会随之升级。因此,使用CodeDOM的Code Wizards也会随着CodeDOM而升级,这样就不必修改Code Wizards的源代码了。

3.     如果所生成的一种语言是你所不熟悉的,如果不使用CodeDOM,必须要熟悉这种语言的语

法,才能生成它的源代码。而使用CodeDOM却可以避免这一点。因为CodeDOM是使用抽象的object graph来描述语言的。而语言的具体语法是由CodeDomProvider所决定的。

其实CodeDOM不仅可以用在Code Wizards上,也可以用在许多其它地方,如可以生成Web

Services的客户端代理(Client Proxies),或根据UML图生成类的构架代码。总之,使用CodeDom可以大大降低和语言的偶合度,并且很容易维护和升级系统。

[资源]国外几款开源的CMS

mikel阅读(764)

1、Ludico
Ludico是C#编写的居于ASP.NET 2.0的Portal/CMS系统。它的模块化设计是你可以按照你希望的使用或开发网站功能。它里面有高级的用户管理,一个所见即所的(WYSIWYG)的编辑器等。
下载地址:http://sourceforge.net/projects/ludico/
2、umbraco 
Umbraco是一款在.net平台下C#开发的开源内容管理系统,该系统效率,灵活,用户界面都不错。
下载地址:http://umbraco.org/
3、mojoPortal
mojoPortal是一款C#开发的面相对象网站框架,它可以运行于Windows的ASP.NET 和GNU/Linux 或Mac OS X的Mono的平台上。
下载地址:http://www.mojoportal.com/
4、Kodai CMS
Kodai CMS是.net平台下的一款功能齐全的内容管理系统。
下载地址:http://www.gotdotnet.com/workspaces/workspace.aspx?id=070f30c3-6089-4a75-b84c-fac654a7ec08
5、nkCMS
NkCMS是使用ASP.NETSQL server 2000开发的内容管理系统。
下载地址:http://nkcms.sourceforge.net/
6、Go.Kryo
Go.Kryo是一个用ASP.net(C#).NET 实现的简单的内容管理系统,后台数据库使用Microsoft SQL Server 。
下载地址:http://sourceforge.net/projects/gokryo/
7、Amplefile
Amplefile是一款内容管理系统,是.net环境下的windows应用程序,使用了.Net remoting.
下载地址:http://sourceforge.net/projects/amplefile/
8、ndCMS
ndCMS是 ASP.net (C#)下的一个内容管理系统。它提供了用户管理,文件管理,一个WYSIWYG编辑器,模板管理,拼写检查和内置的http压缩。ndCMS的目标是提供一个简单而快速的方式部署.Net站点
下载地址:http://ndcms-net.sourceforge.net/
9、Cuyahoga
Cuyahoga是C#开发的灵活的CMS / Portal 解决方案。它可以运行于Microsoft .net 和Mono 平台,支持SQL Server, PostgreSQL或MySQL作为底层数据库。
下载地址:http://www.cuyahoga-project.org/
10、Rainbow
Rainbow项目是一款使用Microsoft's ASP.net和C#技术开发的有丰富功能的开源内容管理系统。
下载地址:http://www.rainbowportal.net/Rainbow/Default.aspx

[资源].net的项目汇总

mikel阅读(734)

经过微软.net Framework 2.0的革新,.net平台的开发越来越精彩。笔者收集基于.net开发的n个项目,一方面为新手提供有价值学习资源,培养起软件开发的乐趣,另一方面共勉已从事.net领域工作多年并有明确发展目标的同仁,提高自己,更上一层楼。
SharpDevelop
强大的插件系统,通过Addin构建成一个功能齐全的.net开发IDE。核心是AddInTree。跟随这个项目开发许多有用的组件,比如功能文本编辑器(ICSharpCode.TextEditor),SharpZipLib等。
链接:http://www.icsharpcode.net/
DotNetNuke
这个就是著名DNN,使用VB.NET进行开发。通过其基本架构可进行堆积木式快速建站。而且支持子网站系统。其由ASP.NET Portal start kit进化而来。
链接:http://www.dnnchina.net/ ,http://www.dotnetnuke.com
Community Server
这个也是一个很著名的ASP.NET项目,记得好像最早系统原形为ASP.NET Forums,后来加入了.Text Blog 和nGallery成为一个完整的通用系统。对应的中文版本为宝玉修改的CCS。
链接:http://communityserver.org/
Rainbow
另外一个类似于DNN的系统,使用C#进行开发。
链接:http://www.rainbowportal.net/
RssBandit
一个客户端的RSS查看器,使用C# 进行开发,最大的特点是数据存储采用xml文件。
链接:http://www.rssbandit.org/
FreeTextbox
Web上的一个Html超文本编辑器,早些版本是开源的,现在还是免费使用,不过源代码需要购买。
链接:http://www.freetextbox.com
World Wind
使用.NET开发的一个Windows窗体系统,以地球外观看得角度提供全球定位功能,类似于Google Earth。
链接:http://worldwind.arc.nasa.gov
log4net
对应Java中的log4j。一个强大的日志管理模块。
链接:http://logging.apache.org/log4net/
Monodevelop
非Windows 系统下的.net 平台开发工具。
链接:http://www.monodevelop.com
Paint.NET
使用.net开发的画图软件,功能不错。
链接:http://www.getpaint.net/index.html
Nunit
对应Java中的Junit,非常著名的单元测试工具。
链接:http://www.nunit.org/
FCKeditor
Web上的又一个Html超文本编辑器。
链接:http://www.fckeditor.net/
Nlog
一个日志管理库,类似于Log4Net。
链接:http://www.nlog-project.org/
ManagedSpy
.net 平台下的Spy ++,支持通过.net 2.0开的Windows Forms应用程序。
链接:http://msdn.microsoft.com/msdnmag/issues/06/04/ManagedSpy/
Guidance Explore
类似于一个.net平台编程问题简答的FAQ。不过提供的都是英文。
链接:http://www.codeplex.com/Wiki/View.aspx?ProjectName=guidanceExplorer
Terrarium
一个.net开发的多人游戏。玩这个游戏可以提高编程能力^_^。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
TaskVision
任务管理系统,一个经典的Smart Client智能客户端程序。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
IssueVision
事务管理系统,Smart Client智能客户端程序。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
FotoVision
又一个值得学习的.net开发的Windows应用程序。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
Infragistics Tracker Application
由Infragistics开发的Smart Client智能客户端程序。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
Windows Forms RSS Portal
一个.net 开发的聚合器。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
Enterprise Library
微软的企业库,对原早些时候开发的一些Block 模块进行整合提供企业统一的接口,新版本使用了.net 2.0的的许多功能。
链接:http://msdn.microsoft.com/practices/
PetShop
基于N-tier设计的电子商务网站,没什么好说的了。
链接:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdasamppet4.asp
XmlNotepad
一个用.net 开发的xml文件编辑器。
链接:http://www.microsoft.com/china/msdn/library/data/xml/xmlnotepad.mspx
DockManager Control
一个很不错的实现类似于VS 2005的界面某些功能的组件。
链接:http://www.codeproject.com/vb/net/DockPanelSkin.asp
Starter Kits for Visual Web Developer
该套件为学习研究应用asp.net2.0的开发人员提供许多宝贵资源,很不错。
链接:http://www.asp.net/downloads/starterkits/default.aspx?tabid=62
以上是笔者的一些资源积累,还有需多经典项目案例没有包括,比如一些早期微软提供的项目案例以及现在热门的ORM经典框架。

[教程]ASP.net学习路线

mikel阅读(837)

如果你已经有较多的面向对象开发经验,跳过以下这两步:
  第一步 掌握一门.NET面向对象语言,C#或VB.NET 我强烈反对在没系统学过一门面向对象(OO)语言的前提下去学ASP.NETASP.NET是一个全面向对象的技术,不懂OO,那绝对学不下去!
  第二步 对.NET Framework类库有一定的了解 可以通过开发Windows Form应用程序来学习.NET Framework。ASP.NET是建构在.NET Framework之上的技术,你对.NET Framework了解得越深,学习ASP.NET就越快。 举个例子:下面简单列出几个对掌握ASP.NET非常重要的概念:对象的内存模型,委托,事件,多线程,程序集和应用程序域,安全模型 .NET Framework当然还有许多其它的东西,但不理解与把握清楚上述这些东西,是很难真正把握ASP.NET的。出于急迫的心情与现实的考虑,不少人没有扎实的面向对象基础就想直接学习ASP.NET,其结果只能是欲速则不达。在具备了OO基础之后,以下是具体的学习ASP.NET技术步骤。
  第一步:学习HTML与CSS
  这并不需要去学一大堆的诸如Dreamweaver,Firework之类的各种网页设计工具,关键是理解HTML网页嵌套的block结构与CSS的box模型。许多 ASP.NET控件最后都必须转化为HTML。而且,div+CSS是当前主流的网页布局模型。学习这部分时,关键在于理解概念,而不需要将精力花在美化页面的许多技巧上,那是网站美工的工作,不是程序员的工作。
  第二步:学习JavaScript
  JavaScript不是Java,它主要运行于浏览器端。可以完成许多工作,功能也很强大:比如它将客户端网页中的HTML元素看成一棵树,可以编写代码访问并修改树节点,动态生成新的HTML代码,从而达到动态修改网页显示特性的目的。
  JavaScript在目前的网站开发中用得很多,非常重要。
  另外,它也是目前非常流行的AJAX技术的基础。
  第三步:学习计算机网络原理
  找一本大学<计算机网络>教材,重点看看它的有关互联网的部分,了解一些域名解析和HTTP协议等知识。这是进行互联网开发的理论基础。
  第四步:学习ASP.NET表示层技术,会设计Web页面
  需要结合先前打好的面向对象技术的基础,至少掌握以下内容:
  (1) 各种Web控件的使用方法,
  (2) 理解信息在网页中的传送方式,比如Cookie,ViewState,Session等的使用。
  (3) ASP.NET应用程序与网页的生命周期,以及相关对象(比如httpcontext,response,request)的用途。
  (4) ASP.NET实现事件驱动的内幕
  (5) 自定义用户控件
  再次强调一下,没有OO基础,很难掌握上述技术,就只能被这些东东牵着鼻子走了,会很被动。
  第五步 掌握数据库技术
  具体地说,要学习以下内容:
  (1) 学会使用SQL Server 2005:不要求精通它的各种工具与管理配置技术,但至少知道如何连接,如何建表,如何创建存储过程
  (2) 学习ADO.NET,掌握使用代码人工访问数据库(别用VS2005的向导)的方法
  (3) 学习数据绑定控件的使用
  第六步 理解多层架构
  这时,先前在OO学习阶段涉及到的程序集与应用程序域等就派上用场了,现在,网站架构大多采用多层架构:表示层、业务逻辑层、数据存取层以及数据库本身。
  可以先上网找一此多层架构的资料看,再找一个现成的比较复杂的开源ASP.NET项目分析一下其架构就差不多了。基本上都是一个套路,到处应用。
  有的朋友问:学习架构是不是必须学习设计模式。
  我的看法是:不必!当然,你如果学习过设计模式,那当然更好。但在实际开发中,如果只想着机械地套用某种模式,反而起不到好的结果。我的观点:在学习设计模式时要多思多悟,其思想就会渐渐地融入你的大脑,在真实的设计实践中,忘掉所有的写在书上的模式,一切从实际出发,相信你的直觉,只要达到设计要求的方案就是可行的方案,事实上,你这样做了之后,回过头来再看,会发现你的设计往往暗合设计模式的理论。
  第七步 学习XML与Web Service
  先了解XML的基础知识,找本讲XML的书看一下就差不多了,然后,再学习Web Service。Web Service其实可类比为远程方法调用(以XML格式表达的调用信息)。
  学 到了这里,如果你还有兴趣,不妨再去看看SOA,不过SOA的资料都是理论与概念,看起来比较郁闷,而且离实际开发可能比较远。所以,这是可选项。
  第八步 学习AJAX
  学习AJAX的主要目的是创建具有更丰富特性的Web表示层,而经过前面七步的学习,到此再学习AJAX已水到渠成,所有的基础都已具备,不会有太大的问题了。
  在基础未具备的前提下直接学AJAX,就象“浮沙之上筑高台”,所以,将AJAX的学习次序安排在这里。
  第九步 学习RIA技术
  RIA:Rich internet application,可看成是将C/S与B/S优点合二为一的尝试。就具体技术来说,主要指微软的Silverlight(WPF_E),毕竟前头一路行来都是微软路线。
  不过目前学习它主要是为了技术储备。估计此技术的普及需要2到3年的时间,因为预装Vista的机器今年才开始大卖。
本贴来自ZDNetChina中文社区 http://bbs.zdnet.com.cn ,本贴地址:http://bbs.zdnet.com.cn/viewthread.php?tid=108315

[教程]解读ASP.NET TimeTracker Starter Kit(2)——重构篇

mikel阅读(719)

看了ASP.NET Time Tracker Starter Kit的代码以后,觉得这个程序是学习面向对象编程开发的一个好案例。整个程序从功能上来讲就是记录人员参加项目工作的工时记录。分析得出主要有人员、项目、工时记录这几个类。整个程序正好是以这几个类为基础展开的。在业务逻辑层里TTUser(用户类)、Project(项目类)和TimeEntry(工时记录类)分别就包括了对相应表的增、删、改等操作和整个程序中所用到的对这几个主要对象的各种操作。整个程序在类的划分和功能上做的很好,但是我觉得在三层架构的划分上不是很理想。

业务逻辑层包含的许多数据访问层的东西,如:数据库连接信息、用到的存储过程等。而这些信息我觉得放在数据访问层应该会更好一些。作为数据访问层,应该为数据逻辑层提供访问数据库的方法,而不是只提供一个简化数据访问的组件(DAAB)。假设要使用Oracle数据,那么改动的代码就需要改动很多,同时也不利于将来的改动。

我的想法是:彻底的将与数据相关的操作从数据逻辑层分离出去,对数据访问层只提供访问方法。在数据访问层的设计上,还是沿用“为数据访问层编写一个基类”的方法,通过基类访问DAAB。这样一来如果要变换为Oracle数据库,只要修改DAAB中的内容即可。大家帮忙看看我的改进代码,看看思路有无问题。

数据访问层基类:

using System;
using System.Data;
//数据访问组件(用的微软提供的)
using MyStarterKit.DDAB;

namespace MyStarterKit.TimeTracker.DAL
{
    
/// <summary>
    
/// DALBase 的摘要说明。
    
/// 数据访问层积累
    
/// </summary>

    public class DALBase
    
{
        
//数据库连接字符串
        private string CONNSTR = System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"];

        
public DALBase()
        
{
        }


        
/// <summary>
        
/// 返回一个单值
        
/// </summary>
        
/// <param name="commandText"></param>
        
/// <param name="parameterValues"></param>
        
/// <returns></returns>

        protected object ExecuteScalar(string commandText, params object[] parameterValues)
        
{
            
return SQLHelper.ExecuteScalar(CONNSTR, commandText, parameterValues);
        }


        
/// <summary>
        
/// 执行无返回值操作
        
/// </summary>
        
/// <param name="commandText"></param>
        
/// <param name="parameterValues"></param>

        protected void ExecuteNonQuery(string commandText, params object[] parameterValues)
        
{
            SQLHelper.ExecuteNonQuery(CONNSTR, commandText, parameterValues);
        }


        
/// <summary>
        
/// 返回DataSet
        
/// </summary>
        
/// <param name="commandText"></param>
        
/// <param name="parameterValues"></param>
        
/// <returns></returns>

        protected DataSet ExecuteDataset(string commandText, params object[] parameterValues)
        
{
            
return SqlHelper.ExecuteDataset(CONNSTR, commandText, parameterValues);
        }

    }

}

 

数据访问层代码:(以项目类数据访问层代码为例)

using System;
using System.Data;

namespace MyStarterKit.TimeTracker.DAL
{
    
/// <summary>
    
/// Project 的摘要说明。
    
/// 项目信息类(数据访问层代码)
    
/// </summary>

    public class Project : DALBase
    
{
        
public Project()
        
{
        }


        
/// <summary>
        
/// 获取全部的项目列表
        
/// </summary>
        
/// <returns></returns>

        public DataSet GetAllProjects()
        
{
            
return base.ExecuteDataset("TT_ListAllProjects");
        }


        
/// <summary>
        
/// 获取项目列表
        
/// </summary>
        
/// <param name="parameterValues"></param>
        
/// <returns></returns>

        public DataSet GetProjects(params object[] parameterValues)
        
{
            
return base.ExecuteDataset("TT_ListProjects",parameterValues);
        }
 

        
/// <summary>
        
/// 删除项目
        
/// </summary>
        
/// <param name="parameterValues"></param>

        public void Remove(params object[] parameterValues)
        
{
            
base.ExecuteNonQuery("TT_DeleteProject",parameterValues);
        }


        
/// <summary>
        
/// 更新项目
        
/// </summary>
        
/// <param name="parameterValues"></param>

        public void Update(params object[] parameterValues)
        
{
            
base.ExecuteNonQuery("TT_UpdateProject",parameterValues);
        }


        
/// <summary>
        
/// 新增项目
        
/// </summary>
        
/// <param name="parameterValues"></param>
        
/// <returns>新项目的Id</returns>

        public int Insert(params object[] parameterValues)
        
{
            
return Convert.ToInt32(base.ExecuteScalar("TT_AddProject",parameterValues));
        }


    }

}

业务逻辑层代码:(以项目类业务逻辑层代码为例)

 

/// <summary>
/// 根据用户和用户角色,获取指定用户能够查看的项目列表
/// </summary>
/// <param name="userID"></param>
/// <param name="role"></param>
/// <returns></returns>

public static ProjectsCollection GetProjects(int userID, string role)
{
    
string firstName = string.Empty;
    
string lastName = string.Empty;
    
// 创建数据访问层类
    DAL.Project project = new DAL.Project();
    
// 调用数据访问层方法
    DataSet ds = project.GetProjects(userID, Convert.ToInt32(role));
    ProjectsCollection projects 
= new ProjectsCollection();
    
foreach(DataRow r in ds.Tables[0].Rows)
    
{
        Project prj 
= new Project();
        prj.ProjectID 
= Convert.ToInt32(r["ProjectID"]);
        prj.Name 
= r["ProjectName"].ToString();
        prj.Description 
= r["Description"].ToString();
        prj.ManagerUserID 
= Convert.ToInt32(r["ManagerUserID"]);
        prj.ManagerUserName 
= TTUser.GetDisplayName(Convert.ToString(r["UserName"]), ref firstName, ref lastName);
        prj.EstCompletionDate 
= Convert.ToDateTime(r["EstCompletionDate"]);
        prj.EstDuration 
= Convert.ToDecimal(r["EstDuration"]);
        projects.Add(prj);
    }

    
return projects;
}

更多相关内容:点击这里>> 

[教程]解读ASP.NET Portal Starter Kit(1)——数据库篇

mikel阅读(663)

  ASP.NET Portal Starter Kit数 据库结构总体上来讲是由网站引擎的核心表(用户表、角色表和角色关系表)和各个用户模块相关的表组成。核心表存储整个网站的用户权限的配置信息(详细的用 法及说明将放到《角色身份认证篇》中讲)。各用户模块存储各个功能模块的信息。各个用户功能模块表都是独立的。这样有利于新增模块扩展功能。同时也可将各 个功能模块的表分别部署到不同的数据库中提高程序的高伸缩性和可扩展性。数据的访问方式是全部通过存储过程进行的。这样做的好处有:1、提高了数据库的性能;2、杜绝了SQL注入式攻击(在我的理解上应该是);3、可将可将连接到SQL Server数据库用户的权限只配置成只能对指定存储过程进行执行操作,这样就进一步提高了数据库的安全性。

数据库中的表:

Portal_Announcements公告信息表(在公告信息模块中用)

字段名

类型

含义

备注

ItemID

Int

链接Id

主键(自动编号)

ModuleID

Int

所属模块ID

决定该公告在那个模块中显示(模块ID:用户配置文件PortalCfg.xml中的模块表ID,以下皆同)

CreatedByUser

Nvarchar(100)

创建者

记录创建和修改该公告的用户

CreatedDate

Datetime

创建时间

记录创建和修改该公告的时间

Title

Nvarchar(100)

名称

 

MoreLink

Nvarchar(150)

更多细节的链接

 

MobileMoreLink

Nvarchar(150)

移动细节链接地址

在“移动设备浏览器”上的更多细节链接地址

ExpireDate

Datetime

有效日期

 

Description

Nvarchar(2000)

描述

 

 

Portal_Contacts联系方式信息表(在联系方式模块中用)

字段名

类型

含义

备注

ItemID

Int

链接Id

主键(自动编号)

ModuleID

Int

所属模块ID

决定该联系方式在那个模块中显示

CreatedByUser

Nvarchar(100)

创建者

记录创建和修改该联系方式的用户

CreatedDate

Datetime

创建时间

记录创建和修改该联系方式的时间

Name

Nvarchar(50)

联系人姓名

 

Role

Nvarchar(100)

联系人角色

与系统框架中的角色不同,理解成职位更合适

Email

Nvarchar(100)

联系人Email

 

Contact1

Nvarchar(250)

联系方式1

 

Contact2

Nvarchar(250)

联系方式2

 

 

Portal_Discussion用户讨论留言信息表(用户讨论模块用)

字段名

类型

含义

备注

ItemID

Int

链接Id

主键(自动编号)

ModuleID

Int

所属模块ID

决定该讨论在那个模块中显示

Title

Nvarchar(50)

讨论标题

 

CreatedDate

Datetime

创建时间

记录创建该讨论的时间

Body

Nvarchar(100)

留言内容

 

CreatedByUser

Nvarchar(100)

创建者

记录创建和修改该讨论的用户

DisplayOrder

Nvarchar(100)

显示顺序

记录回复的讨论的时间和本身提交的时间 (可用作讨论留言的排序和显示回复关系)

 

Portal_Documents用户上传文档信息表(在显示文档信息模块中用)

字段名

类型

含义

备注

ItemID

Int

链接Id

主键(自动编号)

ModuleID

Int

所属模块ID

决定该联系方式在那个模块中显示

CreatedByUser

Nvarchar(100)

创建者

记录创建和修改该联系方式的用户

CreatedDate

Datetime

创建时间

记录创建和修改该联系方式的时间

FileNameUrl

Nvarchar(250)

 

上传至服务器中的文件路径

FileFriendlyName

Nvarchar(150)

文档名称

友好的文件名称显示 

Category

Nvarchar(50)

类别

 

Content

Image

内容(二进制)

上传到数据库中的内容

ContentType

Nvarchar(50)

类型

上传文件的类型

ContentSize

Int

大小

上传文件的大小

 

Portal_Events事件信息表(在显示事件信息的模块中使用)

字段名

类型

含义

备注

ItemID

Int

链接Id

主键(自动编号)

ModuleID

Int

所属模块ID

决定该事件在那个模块中显示

CreatedByUser

Nvarchar(100)

创建者

记录创建和修改该事件的用户

CreatedDate

Datetime

创建时间

记录创建和修改该事件的时间

Title

Nvarchar(100)

事件名称

 

WhereWhen

Nvarchar(150)

发生地点

 

Description

Nvarchar(2000)

描述

 

ExpireDate

Datetime

有效日期

 

 

Portal_HtmlText静态HTML信息表(在显示静态静态HTML的模块中用,可用于显示新闻等文本)

字段名

类型

含义

备注

ModuleID

Int

所属模块ID

决定该静态HTML在那个模块中显示(主键,限定一个模块只对应一个静态HTML文件)

DesktopHtml

Ntext

桌面HTML内容

 

MobileSummary

Ntext

移动摘要

在“移动设备浏览器”上显示的摘要

MobileDetails

Ntext

移动细节

在“移动设备浏览器”上显示的细节

 

Portal_Links链接信息表(快速链接模块和连接模块用)

字段名

类型

含义

备注

ItemID

Int

链接Id

主键(自动编号)

ModuleID

Int

所属模块ID

决定该链接在那个模块中显示

CreatedByUser

Nvarchar(100)

创建者

记录创建和修改该链接的用户

CreatedDate

Datetime

创建时间

记录创建和修改该链接的时间

Title

Nvarchar(100)

名称

 

Url

Nvarchar(250)

链接地址

 

MobileUrl

Nvarchar(250)

移动链接地址

 

ViewOrder

Int

排序号

 

Description

Nvarchar(2000)

描述

 

 

Portal_Roles角色信息表(门户网站引擎核心表)

字段名

类型

含义

备注

RoleID

Int

角色Id

主键(自动编号)

PortalID

Int

门户网址ID

可架设多个门户站点而共用一个数据库,通过PortalID区分

RoleName

Nvarchar(50)

角色名称

 

Portal_UserRoles用户角色关系表(门户网站引擎核心表)

字段名

类型

含义

备注

UserID

Int

用户Id

关联用户信息表(Portal_Roles

RoleID

Int

角色Id

管理角色信息表(Portal_Users

 

Portal_Users用户信息表(门户网站引擎核心表)

字段名

类型

含义

备注

UserID

Int

用户Id

主键(自动编号)

Name

Nvarchar(50)

用户姓名

 

Password

Nvarchar(50)

密码

采用MD5的加密方式存储

Email

Nvarchar(100)

用户Email

用于登录,并设置成唯一性索引(可防止注册相同的Email,这样设置后当有相同的Email插入时程序就会抛出异常,捕获这个异常就可判断Email是否重复,这样就可以省掉判断Email是否重复的代码

 

数据库中的存储过程:

存储过程名称

说明

Portal_AddAnnouncement

添加新公告

Portal_AddContact

添加新联系方式

Portal_AddEvent

添加新事件

Portal_AddLink

添加新链接

Portal_AddMessage

新建一条新的讨论留言,其中@ParentID的参数为被回复留言的Id,通过该ID找到该留言的DisplayOrder,加上新增留言的时间就是新留言的DisplayOrder

Portal_AddRole

添加角色信息

Portal_AddUser

添加一个新用户,返回用户的Id

Portal_AddUserRole

添加用户角色关系

Portal_DeleteAnnouncement

删除公告信息(注:以下删除部分若无特殊说明均为删除指定ItemID的信息)

Portal_DeleteContact

删除联系方式

Portal_DeleteDocument

删除用户上传文档

Portal_DeleteEvent

删除事件信息

Portal_DeleteLink

删除链接信息

Portal_DeleteModule

当删除一个模块时,联动的删除该模块相关的全部信息

Portal_DeleteRole

删除角色信息

Portal_DeleteUser

删除用户

Portal_DeleteUserRole

删除角色用户关系

Portal_GetAnnouncements

根据ModuleID(模块ID)返回有效期内的公告信息

Portal_GetAuthRoles

像是没有用到该存储过程,而且该存储过程涉及的表数据库中没有(在用户配置文件中有类似的表),需要在深入研究

Portal_GetContacts

根据ModuleID(模块ID)返回联系方式

Portal_GetDocumentContent

根据文档的(ItemID)获取存储在数据库中的文档信息

Portal_GetDocuments

根据ModuleID(模块ID)返回用户上传文档信息

Portal_GetEvents

根据ModuleID(模块ID)返回有效期内的事件信息

Portal_GetHtmlText

根据ModuleID(模块ID)返回静态HTML文本信息

Portal_GetLinks

根据ModuleID(模块ID)返回连接信息,并按(ViewOrder)排序号排序

Portal_GetNextMessageID

获取讨论的下一条留言

Portal_GetPortalRoles

获取指定门户站点(指定PortalID)的全部角色信息

Portal_GetPrevMessageID

获取讨论的上一条留言

Portal_GetRoleMembership

根据角色ID获取该角色对应的用户成员信息

Portal_GetRolesByUser

根据用户Email获取用户角色信息

Portal_GetSingleAnnouncement

根据公告的(ItemID),获取单个公告的信息

Portal_GetSingleContact

根据联系方式的(ItemID),获取单个联系方式的信息

Portal_GetSingleDocument

根据文档的(ItemID),获取单个文档的信息

Portal_GetSingleEvent

根据事件的(ItemID),获取单个事件的信息

Portal_GetSingleLink

根据链接的(ItemID),获取单个链接的信息

Portal_GetSingleMessage

根据留言的(ItemID),获取单个留言的信息

Portal_GetSingleRole

根据角色ID,获取单个角色的信息

Portal_GetSingleUser

根据用户Email,获取单个用户的信息

Portal_GetThreadMessages

根据父留言信息的DisplayOrder,返回按时间顺序和回复关系返回子留言信息

Portal_GetTopLevelMessages

根据ModuleID(模块ID)返回顶层留言信息

Portal_GetUsers

获取全部的用户信息,并按Email排序

Portal_UpdateAnnouncement

更新公告信息

Portal_UpdateContact

更新联系方式信息

Portal_UpdateDocument

更新文档信息,当未找到指定文档ID时添加新的文档信息

Portal_UpdateEvent

更新事件信息

Portal_UpdateHtmlText

更新静态HTML文本信息

Portal_UpdateLink

更新指定的连接信息

Portal_UpdateRole

更新角色信息

Portal_UpdateUser

更新用户信息

Portal_UserLogin

根据emailpassword返回登录的用户姓名(用于判断用户登录是否通过)

  
更多相关内容:点击这里>>

[教程]Portal应用点滴(一) —— Portal简介

mikel阅读(996)

  [引言] 早在2003年底,Portal这个名词第一次出现在了我的技术辞典中。当时对Portal的理解仅仅限于“页面上的几个可以随意摆放的小窗口”,觉得没 有什么特别的,也没有把这项技术放在心上了。2004年底,我参与了一个基于Portal的项目开发。在过去将近一年的时间里,一直都在和IBM WebShpere Portal打交道,这给了我认识并学习Portal的好机会。Daily Portal这一系列的文章算是我这一年来对Portal认识的小小一个总结吧。
 
        Portal是一个内涵很丰富的名词,其中文意思是“大门,正门,入口或者进入的手段”。而这项技术也确实人如其名,它为用户提供了一种
简单而且统一的访问Web Application的入口。在这个定义中,统一是最为重要的方面,因为在一个企业中,其IT Support可能是由很多很多相对独立的系统组成,而它们都有各自的访问入口。在没有使用Portal技术之前,不管是企业内部员工,企业的合作伙伴以及企业用户都不得不在几个系统中来回跳转以获得对自己有用的信息,对效率有很大的影响。更糟糕的是,跨系统对信息进行采集和总括的时候,散布在各个系统的相关信息将会以不同的呈现方式出现在用户面前,要迅速从中获得所需要的信息集将是一项十分艰巨的任务。而Portal的统一意味着集成(integrate),它所提供的不仅仅是将几个系统的信息采集过来在一张页面上显示出来,还要提供安全,权限,统一的登陆服务,个性化设置等等。利用Portal来进行Legacy系统的整合是一个十分重要而且有意义的课题,可惜这一年来所做的项目仅仅是对一个系统升级,还没有机会亲身体验Portal在系统整合方面的强大优势,希望有经验的朋友可以不吝赐教。
        说完Portal的定义,我们来看一下Portal的组成结构:

portal.gif

从图中我们可以看出,Horizontal Portals Infrastructure部分为建立不同的Portal系统提供了最基础的服务,通常它由以下几个方面组成:
        1、Presentation:提供了在不同的client端显示的服务;
        2、Personalization:基于User Profile的个性化设置服务;
        3、Collaboration:提供与其他系统,如果Mail,IM等的协作服务;
        4、Portlets:这个名词是不是很熟悉呢?Portlet提供了访问Web Application的服务;
        5、Search and Workflow:提供与Legacy系统集成的服务;
        6、Publish and subscribe:提供内容发布与订阅服务;
        7、Administration and Security: 管理及安全服务;
        8、Integration:提供与数据库连接以及元数据共享服务。

而在这八个方面当中,Portlets是最为核心的,通常我们对Portal的第一步感性认识也是从Portlet开始。简单的说,从页面上看到的一 个个小窗口就是portlet。我们可以把Portlet看成是Web世界中的可重用的Component,它为用户提供了访问Web Application,Web Service或者Web Content的服务。一个Portal Page通常会包含几个Portlet,而这些Portlet之间是相互独立且互不影响的,也就是当一个Portlet无法正常工作的时候,另外的 Portlet并不会因此而罢工,仍会正常地进行处理。
        最后大致说一下,使用Portal技术会给企业带来怎样的益处。首先,以 可重用的Portlet作为核心的开发方式使得企业可以更快速的响应商业上的需求;Portal提供了强大的系统集成功能,降低了系统维护以及使用的成 本,同时也降低了员工培训的成本;Portal提供了统一的访问入口使得用户访问各个系统的时候有更好的用户体验以及更好的安全性。
 

在昨天的Post中,偶已经对Portal进行了简单的介绍,不知道各位对Portal是不是有一点点认识了呢?如果还是云里雾里的话也没有关系,毕竟Portal所涵盖的内容是很广博的,掌握它需要时间,了解也是需要时间的。在初步介绍了Portal的概念之后,今天我们来探讨一下Portal的核心——Portlet。
        首先,Portlet在表现形式上就是我们所看到的页面上一个个小窗口,但是它绝不仅限于将Web Content以模块化的方式显示出来。实际
上,每一个Portlet就是一个完整的Web Application,拥有完整的MVC。而Portlet的开发,部署,管理和显示都是完全独立的,不受其他Portlet的影响,从而其functionality(功能性)也是独立的,也就是说一个Portlet就是一个处理特定义务逻辑的Component。正是Portlet这种独立性与功能性,使得Portal的Administrator或者是End User能够很方便地配置符合自己要求的页面,充分提高信息采集与概括的效率。
        做完了表面功夫之后,我们将注意力集中到Portlet的底层实现中来。从Portlet的类继承关系上,我们可以看到Portlet的
Superclass仍然是HttpServlet。事实上,虽然Portlet相对Servlet而言进行了很大程度上的扩展并有很大的优越性,但是其本质与Servlet并无二致,都是一个处理HttpRequest并返回HttpResponse的过程,只是这个过程已经不是Servlet中的doGet、doPost或者service那么简单了,而是分成了两个方面:Event Phase和Content Rendering Phase。这就是Portlet的Two-phase Portlet Request Processing了。也许你会对这样的划分产生了兴趣,为什么需要这样去划分呢?其实,这样做是为了保持Portlet之间独立性。你可以设想一下,在一个Portal Page中包含了两个Portlet,而你在其中的一个Portlet输入了数据并提交,那么该Portlet将会处理你的请求并返回相应的结果,而另外一个Portlet则需要保持原来的样子。在这样的一个过程,对于处理了你的请求的Porlet,其Processing包含了两个phase,而另外一个Portlet则仅是进行了Content Rendering Phase的处理并将cache的内容返回。写到这里,我不禁想到了Ajax。以上的case,可以看作是页面的部分更新,如果使用了Ajax,是不是可以简化Portlet Request的处理过程呢?我相信这是一个有趣并值得深入探讨的问题。[1] 
        说完了比较底层的东西,我们最后再来看看Portlet比较特殊的一个地方:Portlet Mode。一个Portlet通常会包含几种display
mode,最常见的莫过于Minimize和Maxmize的display mode,除此之还会有Help Mode和Edit Mode。Help Mode为用户提供关于该Portlet的帮助信息,而Edit Mode则为用户提供了设置Portlet属性的界面。用户可以通过点击Portlet的title bar上的图标(图中黑色椭圆框住的部分)来切换不同的display mode,如下图所示:

PortalBar.bmp

        以上对Portlet作了简单的介绍,希望能够给各位以初步的印象。讲了那么多理论上的东西,大家是不是有点厌烦了呢?不要急,在接下来的Post中就要动手实践了,敬请关注哦! 🙂

 

在上一篇Post中,我提到要开始实践了,但是酝酿一篇与实践密切相关的Post还真的很费劲。毕竟建立一个portlet的方法有很多种,而且还会因使用的工具不同而方法迥异。平常在工作当中都是使用IBM提供的Portal Toolkit进行Portlet开发,打开Create New Portlet Wizard,跟着提示点几下按钮,一个漂亮的Portlet就在弹指之间完成了。如果我把这样的过程step by step地写下来,似乎意义不大,毕竟这些内容在IBM的红皮书中都写得清清楚楚了,况且由Portal Tookit搭建起来的开发环境也不是唾手可得的,看来还得从Open Source的东西入手了。
        在Apache的Portals项目中有一个名叫Pluto的子项目,它致力于构建一个简单的Portlet Container,让程序员可以Deploy并运行所开
发的Portlet。Pluto相对于BEA、IBM等大公司提供的Portal Server而言,尽管显得功能简陋,但是作为Portlet API的一个参考实现还是挺有权威的,其简单性也正好让我们更加关注Portlet的本质。更重要的是,它是开源的,大家都很容易从网上找到它。于是,Pluto就成为我们进行实践的不二之选了。OK,想好了就开始动手咯!
        首先,我们从Pluto下载页面
中把最新版本1.0.1-rc4的Pluto给download下来。由于Portlet Container并不是一个可以独立运行的container,它是离不开Web Server和Servlet Container的,所下载的Pluto中就包含了Tomcat5.5.9。由于Tomcat5.5.9所要求的JRE是5.0或以上,如果你的机子上装有5.0以上版本的JRE,那么你只需要将Pluto解压到某一个目录下(<PLUTO-HOME>),然后在console中进入<PLUTO-HOME>/bin目录,运行startup即可。这样Tomcat连同Pluto都启动了,接着我们访问http://localhost:8080/pluto/portal,就会出现如下界面,准备工作就大功告成了。

Pluto.bmp

如果你的机子上只有JRE1.4的话,要让Tomcat跑起来我们就还得从Tomcat5.5.9的下载页面下载一个名为compat的zip file。然后把这个zipfile解开,里面包含着bin和common两个目录,bin目录下包含着jmx.jar文件,common目录下还包含着一个endorsed目录,该目录下包含着xml-apis.jar和xercesImpl.jar文件。我们只需要把jmx.jar复制到<PLUTO-HOME>/bin目录下,改掉<PLUTO-HOME>/common/endored目录下的两个jar文件的后缀,并将xml-apis.jar和xercesImpl.jar复制到该目录下即可。
        准备工作完成以后,我们就开始咱们的第一个Portlet了——HelloWorld Portlet,具体的步骤请参考OnJava上的文章:What Is a Portlet, 在这里我就不罗嗦了。与这篇文章所介绍的不同,由于我们使用的是1.0.1-rc4,发布Portlet就不再像文章所说的那样复杂了,你也不需要有 Maven的基础,只需要通过Pluto的Admin页面将war文件上传并根据页面提示进行少许配置,就轻而易举地完成Portlet的发布了。发布完 成以后,需要重新启动Pluto,这是一个非常不方便的地方,但据说在一个版本的Pluto将支持Hot Deploy,发布Portlet就不需要重新启动了。
        最后,我们通过浏览器访问http://localhost:8080/pluto/portal,你会发现在左边的Portal Page列表中多了一项——HelloWorld。点击该项,我们就可以看到这个最简单的Portlet了。

HelloWorldPortlet.bmp