webform 调用sql存储过程_webform加sql和webform加oricle的区别 - CSDN
精华内容
参与话题
  • C#二十六 使用Ado.Net调用存储过程

    千次阅读 2020-02-09 22:12:28
    存储过程是连接式访问数据库的一种延伸,主要是通过命令对象调用数据库系统中的存储过程来完成的。...使用Ado.Net中的SqlCommand可以调用并执行Sql Server数据库的存储过程。 视频课堂https://edu.csdn.net...

    存储过程是连接式访问数据库的一种延伸,主要是通过命令对象调用数据库系统中的存储过程来完成的。存储过程可以带参数,也可以不带参数,可以返回结果页可以没有返回结果。存储过程执行速度快、允许模块化程序设计并且提高系统安全性,所以是最常用的操作数据库的技术。使用Ado.Net中的SqlCommand可以调用并执行 Sql Server数据库的存储过程。

     视频课堂https://edu.csdn.net/course/play/7621

    C#程序设计及宿舍管理系统实战 

    https://edu.csdn.net/course/detail/27107

    重点:

    Ø      如何定义与使用存储过程以及存储过程的重要性

    Ø      参数对象

    Ø      调用有返回值的存储过程

     

     

    预习功课:

    Ø      SqlCommand如何才能调用存储过程

    Ø      SqlParameter类及其使用规则

    Ø      参数化对象的使用

    Ø      如何才能调用有返回值的存储过程

     

     

     

     

     

    3.1           参数化对象

     

    参数对象(Parameter)表示命令对象(Command)中的一个参数,我们前面用过的命令对象都是不带参数的,我们也可以使用带参数的命令对象,这将给程序带来更大的灵活性。我们知道存储过程中存在输入输出参数,所以在介绍C#调用存储过程前,需要来看看系统为我们提供的参数参数对象到底有何用处以及如何使用。

     

    测试表:Person

    Create table Person

    {

       psnNo Nchar(6) PrimaryKey,

       psnName Nvarchar(5) NotNull,

       psnSex  Nchar(1) not null,

       psnAge smallint notnull,

       psnAddress Nvarchar(50)not null

    }

    go

    该表可建立于我们的测试数据库Test下:

     

    在向数据库里插入记录,我们可以使用下面Sql语句:

    Insert into Person values('001,'心酸果冻','女',25,'北京宣武区')

    Insert into Person values('002,'芬理希梦','女',24,'上海浦东区')

    很显然这两个语句非常相似,有没有一种方法可以使我们避免这种无谓的重复呢?有,就是使用参数化Sql语句。在实现上面的插入操作时,可以使用参数化Sql语句将两个Sql语句中不同的部分用参数来表示,然后在使用的时候给参数赋予一个具体的值即可,这样就不用每次都将Sql语句重新写一遍了。我们使用参数化Sql语句重写了上面的插入操作如下:

     

    //通过连接字符串建立数据库连接

    SqlConnection cn=new SqlConnection("server=.;database=Test;uid=sa;pwd=123456");

    try

    {

      cn.Open();

    //创建带参数的Sql语句

      string sql="Insert into Personvalues(@Id,@Name,@Sex,@Age,@Address) ";

      SqlCommand cmd=newSqlCommand(sql,cn);

    //创建参数并加入到Parameters集合里

      cmd.Parameters.Add("@Id",SqlDbType.NChar,6);

      cmd.Parameters.Add("@Name",SqlDbType.Nvarchar,5);

      cmd.Parameters.Add("@Sex",SqlDbType.Nchar,1);

      cmd.Parameters.Add("@Age",SqlDbType.SmallInt);

      cmd.Parameters.Add("@Address",SqlDbType.NvarChar,50);

     

    //设置参数的值,并执行插入

      cmd.Parameters["@Id"].Value="004";

      cmd.Parameters["@Name"].Value="帕瓦罗蒂";

      cmd.Parameters["@Sex"].Value="男";

      cmd.Parameters["@Age"].Value="32";

      cmd.Parameters["@Address"].Value="东部部落";

      cmd.ExecuteNonQuery();

     

     

     

    //设置带参数的值,并执行插入

    cmd.Parameters["@Id"].Value="005";

    cmd.Parameters["@Name"].Value="天使毛毛";

    cmd.Parameters["@Age"].Value="18";

    cmd.Parameters["@Address"].Value="东部部落";

    //此处设置的值可以为对应的Winform、Webform的文本框、下拉框等用户输入的值;

    //如:ccmd.Parameters["@Id"].Value=txtId.Text;

    cmd.ExecuteNonQuery();

    }

    catch(SqlExecption ex)

    {

      //数据库出错报错信息

    }

    finally

    {

      cn.Close();

    }

     

    这段代码执行后数据库里会增加两条记录。这里就是使用参数化的Sql语句的方式进行的,要注意参数化Sql语句是和命令对象配合使用的。上面的参数化Sql语句中使用的"@Id"等就是参数(参数以@开头可随便定义名称),在SqlCommand中需要为这些参数创建对应的参数对象,具体说来参数化Sql语句的使用有三步:

    1.     构造参数Sql语句,可以是任何Sql语句

    2.     为每一个Sql语句中出现的参数定义一个参数对象,并将这些参数加入到命令对象中

    3.     给参数设置值,并执行查询

     

    构造参数Sql语句我们就不说了,定义参数对象比较复杂,上面我们看到的是使用cmd.Parameters.Add方法创建参数对象,实际上我们也可以自己定义参数对象,定义完后要加入到命令对象里面:

    SqlParameter parId=newSqlParameter();

    parId.ParameterName="@Id";  //设置参数的名称

    parId.Size=6;              //设置参数数据的最大值

    cmd.Parameters.Add(parId);     //将参数对象加入到命令对象中

     

    这段代码和我们上面使用的"cmd.Parameters.Add("@Id",SqlDbType.Nchar,6)效果是相同的。像这样使用自己定义的参数可以更灵活的定制参数,因为SqlParameter类为我们提供许多有用的属性。

     

    属性

    说明

    ParameterName

    参数的名称,在与参数化Sql中出现的参数名要对应

    SqlDbType

    参数的数据类型

    IsNullable

    该值指示参数是否接受空值

    Size

    获取或设置参数数据的最大大小,设置Size仅影响输入的参数值

    SourceColumn

    获取或设置源列的名称

    SourceVersion

    确定参数值使用的是原始值还是当前值

    Value

    参数的值

    Direction

    指示参数是只可输入、只可输出,双向还是存储过程返回值

     

     

    3.2  调用无返回值的存储过程

     

    无返回值的存储过程可以执行增加记录、删除记录、修改记录等数据库操作。使用命令对象执行无返回值存储过程和无返回值的Sql语句执行方式基本相同,都是使用ExecuteQuery()。下面这个例子,是调用存储过程修改存储过程修改Person表中我们刚刚插入的“帕瓦罗蒂”的地址,我们首先要在数据库建立如下存储过程:

     

    Create Proc ch_Person

    as

        Update Person

            Set psnAddress=’乞力马扎罗’

        Where psnName=’帕瓦罗蒂’

    Go

     

    要通过C#执行该存储过程,需要创建一个SqlCommand类的命令对象,然后修改命令对象的类型属性CommandType为存储过程类型,并设置命令对象的CommandText为存储过程的名字,然后通过ExecuteNonQuery()方法执行存储过程即可:

    核心代码:

    SqlConnection cn=new SqlConnection("server=.;database=test;uid=sa;pwd=123456");

    try

    {

        cn.Open();

        SqlCommandcmd=cn.CreateCommand();

        //设置命令类型为存储过程

        cmd.CommandType=CommandType.StoreProcedure;

        //设置存储过程的名字

        cmd.CommandText="ch_Person";

        //执行存储过程

        cmd.ExecuteNonQuery();

    }

    catch(SqlExecption ex)

    {

        //数据库出错信息提示

    }

    finally
        {

        cn.Close();

    }

     

    无返回值的存储过程还可以包含传入参数,比如下面这个存储过程,可以按照姓名更新地址。姓名和地址都是存储过程传入的参数:

    Create Proc ch_Person

        @p_psnName NvarChar(5),

        @p_psnAddress NvarChar(50)

    As

       Update Person

           Set psnAddress=@p_psnAddress

       Where psnName=@p_psnName

    Go

     

    调用这个存储过程又需要用到命令对象中的参数属性Parameters,只需要在这个参数集合里加入存储过程的参数定义并设置其值就可以了,代码如下:

     

    SqlConnection cn=new SqlConnection("server=.;database=test;uid=sa;pwd=123456");

    try

    {

      cn.Open();

      SqlCommandcmd=new SqlCommand("ch_Person",cn);

      cmd.CommandType=CommandType.StoredProcedure;

    //加入参数对象,并设置其值

      cmd.Parameters.Add("@p_psnName",SqlDbType.NVarChar).Value="帕瓦罗蒂";

      cmd.Parameters.Add("@p_psnAddress",SqlDbType.NVarChar).Value="日本广岛";

    //后面的具体的值,在设置的时候可以变为具体的文本框控件等的值;

    //如:cmd.Parameters.Add("@p_psnName",SqlDbType.NVarChar).Value=txtName.Text

    //执行存储过程

      cmd.ExecuteNonQuery();

    }

    catch(SqlException ex)

    {

      //操作数据库出错信息处理

    }

    finally

    {

      cn.Close();

    }

     

    3.3  调用带返回值的存储过程

     

    上面讨论了对于没有返回值的存储过程的调用,那么对于带有返回值的存储过程我们能调用并获得返回数据么?当然可以,实际上和上面无返回值的实现方式差别不大。我们在介绍SqlParameter的时候曾经提到SqlParameter的一个Direction属性,这个属性就可以指定参数是输入还是输出,指定了Direction属性为输出类型的参数对象,就可以调用存储过程时获得存储过程的返回值。Direction属性是通过ParameterDirection枚举指定的,如:

    l       Input:表示该参数为输入参数

    l       InputOutput:表示该参数既能输入,也能输出

    l       Output:表示该参数为输出参数

    l       ReturnValue:获取存储过程的返回值

    另外,我们知道存储过程的返回值有两种方式,一种是通过设置参数为Output属性而返回,一种是直接在存储过程里使用Return关键字来返回值。下面这个存储过程可以通过姓名查找其地址,上面说的两种返回方式都使用了:

    Create Proc gt_Address

       @p_psnName NvarChar(5)  //请输入姓名

       @g_psnAddress NvarChar(5)OutPut  //返回此人的地址

    As

       Select@g_psnAddress=psnAddress From Person

       WherepsnName=@p_psnName

       If@@Error<>0

           Return-1   //如果查询语句出错返回-1

       Else

           Return 0

    Go

     

    我们调用这个存储过程的部分代码如下:

     

    SqlConnection cn=new SqlConnection("server=.;database=test;uid=sa;pwd=123456");

    try

    {

       cn.Open();

       SqlCommandcmd=new SqlCommand("gt_Address",cn);

       cmd.CommandType=CommandType.StoredProcedure;

       //加入参数,并设置参数的值和Direction属性

       cmd.Parameters.Add("@p_psnName",SqlDbType.NvarChar).Value="帕瓦罗蒂";

       SqlParametercpar=cmd.Parameters.Add("@g_psnAddress",SqlDbType.NvarChar,50);

       cpar.Direction=ParameterDirection.Output;

       SqlParametercres=cmd.Parameters.Add("@return",SqlDbType.Int);

       cres.Direction=ParameterDirection.ReturnValue;

       //执行存储过程

       cmd.ExecuteNonQuery();

       //获得参数的值

       intres=(int)cres.Value;

       stringaddress=(string)cpar.Value;

       Console.WriteLine("返回值:{0},地址:{1}",res,address);  

    }

    catch(SqlException ex)

    {

       //数据库出错信息报告

    }

    finally

    {

       cn.Close();

    }

     

    运行结果:

    返回值:0,地址:日本广岛

     

    在上面的这段代码中,我们给命令对象定制了三个参数,其中有一个输入参数:@p_psnName(参数的Direction属性如果没有指定,那么默认是输入参数),两个返回参数:@g_psnAddress和@return。如果查看存储过程你就会发现存储过程里并没有@return参数,事实上在这里,@return是一个临时参数,我们通过这个参数来获取存储过程的返回值,这个参数可以是任何名称,如@Re、@R等。存储过程的返回值是整型的,所以@return类型也应该是整型。另外,注意对于要返回的参数,如果是字符串型(NvarChar、Nchar或Char)则必须指定长度,如上面的@g_psnAddress参数我们指定了长度为50。

       

        对于带参数的存储过程,不管是输入参数还是输出参数,实际上有一种简单的方式创建参数,就是使用系统类SqlCommandBuilder的静态方法DeriveParameters自动生成参数。使用DeriveParameters方法可以从SqlCommand中指定的存储过程中检索参数信息并填充到该SqlCommand对象的Parameters集合里。我们使用DeriveParameters方法重新实现上面的存储过程调用如下:

       

    SqlConnection cn=new SqlConnection("server=.;database=test;uid=sa;pwd=123456");

    try

    {

       cn.Open();

       SqlCommandcmd=new SqlCommand("gt_Address",cn);

       cmd.CommandType=CommandType.StoredProcedure;

       //为命令对象生成参数

       SqlCommandBuilder.DeriveParameters(cmd);

       //设置输入参数的值

       cmd.Parameters.Add("@p_psnName",SqlDbType.NvarChar).Value="帕瓦罗蒂";

       //执行存储过程

       cmd.ExecuteNonQuery();

     

       intres=(int)cmd.Parameters["@Return"].Value;

       string address=(string)cmd.Parameters["@p_psnAddress"].Value;

       Console.WriteLine("返回值:{0},地址:{1}",res,address);  

    }

    catch(SqlException ex)

    {

       //数据库出错信息报告

    }

    finally

    {

       cn.Close();

    }

       

       

        这个程序的执行方式和运行结果和上面那个完全一样。不难看出,使用DeriveParameters只是将参数创建的过程交给了系统,我们省略了参数创建的过程,但给参数传值还有获得参数的值还是需要自己操作,即使如此,这样还是使程序简单了很多。

    展开全文
  • 来源: 版权所有:UML软件工程组织 作者:袁剑

     来源: 版权所有:UML软件工程组织

    作者:袁剑
    摘要

    本文介绍了在.NET框架下应用Web设计模式改进WebForm程序设计的一些基本方法及要点。

    关键字

    设计模式,ASP.NET,WebForm,MVC,Page Controller,Front Controller,Page Cache

    目录

    引言
    经典的WebForm架构
    设计模式
    MVC模式下的WebForm
    Page Controller模式下的WebForm
    Front Controller模式下的WebForm
    Page Cache模式下的WebForm

    引言

    记得微软刚刚推出ASP.NET时,给人的震撼是开发Web程序不再是编写传统的网页,而像是在构造应用程序,因而微软称之为WebForm。但是两年后的今天,有相当多的开发人员仍然延用写脚本程序的思路构建一个又一个的WebForm,而没有发挥出ASP.NET的优势,就此本文希望通过实例能够启发读者一些新的思路。
    由于篇幅有限,本文不可能通过一个复杂的Web应用来向读者展示结合设计模式的WebForm,但是如果仅仅是一个小程序的确没有使用模式的必要。为了便于理解,希望您能把它想象成是一个大型系统中的小模块(如果代码是大型系统的一部分那么使用模式就变得非常重要)。
    在本文的末尾给出了所有源程序的下载地址。
    经典的WebForm架构
    首先来看一个简单的应用,数据库设计如下图,Portal是Subject的父表,通过portalId进行一对多关联,程序需要根据portalId显示不同的Subject列表。

    按照我们编写WebForm一般的习惯,首先在页面上拖放一个DropDownList、一个DataGrid、一个Button控件:
    界面(webForm.aspx):
    〈form id="webForm" method="post" runat="server">
    〈asp:DropDownList id="dropDownList" runat="server">〈/asp:DropDownList>
    〈asp:Button id="button" runat="server" Text="Button">〈/asp:Button>
    〈asp:DataGrid id="dataGrid" runat="server">〈/asp:DataGrid>
    〈/form>

    然后利用VS.NET代码隐藏功能编写的核心代码如下:
    后置代码(webForm.aspx.cs):
    //页面初始化事件
    private void Page_Load(object sender, System.EventArgs e)
    {
    if ( ! IsPostBack )
    {
    string SQL_SELECT_PORTAL = "SELECT * FROM PORTAL";
    //使用using确保释放数据库连接
    //连接字符串存放在Web.Config文件中便于修改
    using( SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["ConnectionString"] ) )
    {
    SqlDataAdapter dataAdapter = new SqlDataAdapter( SQL_SELECT_PORTAL, conn );
    DataSet dataSet = new DataSet();
    dataAdapter.Fill( dataSet );
    //设置下拉列表的数据源与文本域、值域
    dropDownList.DataSource = dataSet;
    dropDownList.DataTextField = "portalName";
    dropDownList.DataValueField = "portalId";
    dropDownList.DataBind();
    }
    }
    }
    //Button的Click事件
    private void button_Click(object sender, System.EventArgs e)
    {
    string SQL_SELECT_SUBJECT = "SELECT * FROM SUBJECT WHERE portalId = {0}";
    using( SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["ConnectionString"] ) )
    {
    //用下拉列表选择的值替换掉SQL语句中的待定字符{0}
    SqlDataAdapter dataAdapter = new SqlDataAdapter( string.Format( SQL_SELECT_SUBJECT, dropDownList.SelectedValue ), conn );
    DataSet dataSet = new DataSet();
    dataAdapter.Fill( dataSet );
    dataGrid.DataSource = dataSet;
    dataGrid.DataBind();
    }
    }

    执行结果如图所示,程序将根据下拉列表框选择的值绑定DataGrid,非常典型的一个WebForm架构,体现出ASP.NET事件驱动的思想,实现了界面与代码的分离。但是仔细看看可以从中发现几个问题:

    对数据库操作的代码重复,重复代码是软件开发中绝对的“坏味道”,往往由于某些原因当你修改了一处代码,却忘记要更改另外一处相同的代码,从而给程序留下了Bug的隐患。

    后置代码完全依赖于界面,在WebForm下界面的变化远远大于数据存储结构和访问的变化,当界面改变时您将不得不修改代码以适应新的页面,有可能将会重写整个后置代码。

    后置代码不仅处理用户的输入而且还负责了数据的处理,如果需求发生变更,比如需要改变数据的处理方式,那么你将几乎重写整个后置代码。
    一个优秀的设计需要每一个模块,每一种方法只专注于做一件事,这样的结构才清晰,易修改,毕竟项目的需求总是在不断变更的,“唯一不变的就是变化本身”,好的程序一定要为变化作出准备,避免“牵一发而动全身”,所以一定要想办法解决上述问题,下面让我们来看看设计模式。

    设计模式

    设计模式描述了一个不断重复出现的问题以及对该问题的核心解决方案,它是成功的构架、设计及实施方案,是经验的总结。设计模式的概念最早来自于西方建筑学,但最成功的案例首推中国古代的“三十六计”。

    MVC模式下的WebForm

    MVC模式是一个用于将用户界面逻辑与业务逻辑分离开来的基础设计模式,它将数据处理、界面以及用户的行为控制分为:Model-View-Controller。
    Model:负责当前应用的数据获取与变更及相关的业务逻辑
    View:负责显示信息
    Controller:负责收集转化用户的输入

    View和Controller都依赖于Model,但是Model既不依赖于View,也不依赖于Controller,这是分离的主要优点之一,这样Model可以单独的建立和测试以便于代码复用,View和Controller只需要Model提供数据,它们不会知道、也不会关心数据是存储在SQL Server还是Oracle数据库中或者别的什么地方。
    根据MVC模式的思想,可以将上面例子的后置代码拆分为Model和Controller,用专门的一个类来处理数据,后置代码作为Controller仅仅负责转化用户的输入,修改后的代码为:
    Model(SQLHelper.cs):封装所有对数据库的操作。
    private static string SQL_SELECT_PORTAL = "SELECT * FROM PORTAL";
    private static string SQL_SELECT_SUBJECT = "SELECT * FROM SUBJECT WHERE portalId = {0}";
    private static string SQL_CONNECTION_STRING = ConfigurationSettings.AppSettings["ConnectionString"];
    public static DataSet GetPortal()
    {
    return GetDataSet( SQL_SELECT_PORTAL );
    }
    public static DataSet GetSubject( string portalId )
    {
    return GetDataSet( string.Format( SQL_SELECT_SUBJECT, portalId ) );
    }
    public static DataSet GetDataSet( string sql )
    {
    using( SqlConnection conn = new SqlConnection( SQL_CONNECTION_STRING ) )
    {
    SqlDataAdapter dataAdapter = new SqlDataAdapter( sql, conn );
    DataSet dataSet = new DataSet();
    dataAdapter.Fill( dataSet );
    return dataSet;
    }
    }

    Controller(webForm.aspx.cs):负责转化用户的输入
    private void Page_Load(object sender, System.EventArgs e)
    {
    if ( ! IsPostBack )
    {
    //调用Model的方法获得数据源
    dropDownList.DataSource = SQLHelper.GetPortal();
    dropDownList.DataTextField = "portalName";
    dropDownList.DataValueField = "portalId";
    dropDownList.DataBind();
    }
    }
    private void button_Click(object sender, System.EventArgs e)
    {
    dataGrid.DataSource = SQLHelper.GetSubject( dropDownList.SelectedValue );
    dataGrid.DataBind();
    }

    修改后的代码非常清晰,M-V-C各司其制,对任意模块的改写都不会引起其他模块的变更,类似于MFC中Doc/View结构。但是如果相同结构的程序很多,而我们又需要做一些统一的控制,如用户身份的判断,统一的界面风格等;或者您还希望Controller与Model分离的更彻底,在Controller中不涉及到Model层的代码。此时仅仅靠MVC模式就显得有点力不从心,那么就请看看下面的Page Controller模式。

    Page Controller模式下的WebForm

    MVC 模式主要关注Model与View之间的分离,而对于Controller的关注较少(在上面的MVC模式中我们仅仅只把Model和Controller分离开,并未对Controller进行更多的处理),但在基于WebForm的应用程序中,View和Controller本来就是分隔的(显示是在客户端浏览器中进行),而Controller是服务器端应用程序;同时不同用户操作可能会导致不同的Controller策略,应用程序必须根据上一页面以及用户触发的事件来执行不同的操作;还有大多数WebForm都需要统一的界面风格,如果不对此处理将可能产生重复代码,因此有必要对Controller进行更为仔细的划分。
    Page Controller模式在MVC模式的基础上使用一个公共的页基类来统一处理诸如Http请求,界面风格等,如图:

    传统的WebForm一般继承自System.Web.UI.Page类,而Page Controller的实现思想是所有的WebForm继承自定义页面基类,如图:

    利用自定义页面基类,我们可以统一的接收页面请求、提取所有相关数据、调用对Model的所有更新以及向View转发请求,轻松实现统一的页面风格,而由它所派生的Controller的逻辑将变得更简单,更具体。
    下面看一下Page Controller的具体实现:
    Page Controller(BasePage.cs):
    public class BasePage : System.Web.UI.Page
    {
    private string _title;
    public string Title//页面标题,由子类负责指定
    {
    get
    {
    return _title;
    }
    set
    {
    _title = value;
    }
    }
    public DataSet GetPortalDataSource()
    {
    return SQLHelper.GetPortal();
    }
    public DataSet GetSubjectDataSource( string portalId )
    {
    return SQLHelper.GetSubject( portalId );
    }
    protected override void Render( HtmlTextWriter writer )
    {
    writer.Write( "〈html>〈head>〈title>" + Title + "〈/title>〈/head>〈body>" );//统一的页面头
    base.Render( writer );//子页面的输出
    writer.Write( @"〈a href=""http://www.asp.net"">ASP.NET〈/a>〈/body>〈/html>" );//统一的页面尾
    }
    }

    现在它封装了Model的功能,实现了统一的页面标题和页尾,子类只须直接调用:
    修改后的Controller(webForm.aspx.cs):
    public class webForm : BasePage//继承页面基类
    {
    private void Page_Load(object sender, System.EventArgs e)
    {
    Title = "Hello, World!";//指定页面标题
    if ( ! IsPostBack )
    {
    dropDownList.DataSource = GetPortalDataSource();//调用基类的方法
    dropDownList.DataTextField = "portalName";
    dropDownList.DataValueField = "portalId";
    dropDownList.DataBind();
    }
    }
    private void button_Click(object sender, System.EventArgs e)
    {
    dataGrid.DataSource = GetSubjectDataSource( dropDownList.SelectedValue );
    dataGrid.DataBind();
    }
    }

    从上可以看出BagePage Controller接管了大部分原来Controller的工作,使Controller变得更简单,更容易修改(为了便于讲解我没有把控件放在BasePage中,但是您完全可以那样做),但是随着应用复杂度的上升,用户需求的变化,我们很容易会将不同的页面类型分组成不同的基类,造成过深的继承树;又例如对于一个购物车程序,需要预定义好页面路径;对于向导程序来说路径是动态的(事先并不知道用户的选择)。
    面对以上这些应用来说仅仅使用Page Controller还是不够的,接下来再看看Front Controller模式。

    Front Controller模式下的WebForm

    Page Controller的实现需要在基类中为页面的公共部分创建代码,但是随着时间的推移,需求会发生较大的改变,有时不得不增加非公用的代码,这样基类就会不断增大,您可能会创建更深的继承层次结构以删除条件逻辑,这样一来我们很难对它进行重构,因此需要更进一步对Page Controller进行研究。
    Front Controller通过对所有请求的控制并传输解决了在Page Controller中存在的分散化处理的问题,它分为Handler和Command树两个部分,Handler处理所有公共的逻辑,接收HTTP Post或Get请求以及相关的参数并根据输入的参数选择正确的命令对象,然后将控制权传递到Command对象,由其完成后面的操作,在这里我们将使用到Command模式。
    Command模式通过将请求本身变成一个对象可向未指定的应用对象提出请求,这个对象可被存储并像其他的对象一样被传递,此模式的关键是一个抽象的Command类,它定义了一个执行操作的接口,最简单的形式是一个抽象的Execute操作,具体的Command子类将接收者作为其一个实例变量,并实现Execute操作,指定接收者采取的动作,而接收者具有执行该请求所需的具体信息。

    因为Front Controller模式要比上面两个模式复杂一些,我们再来看看例子的类图:

    关于Handler的原理请查阅MSDN,在这就不多讲了,我们来看看Front Controller模式的具体实现:

    首先在Web.Config里定义:

    〈!-- 指定对Dummy开头的aspx文件交由Handler处理 -->
    〈httpHandlers>
    〈add verb="*" path="/WebPatterns/FrontController/Dummy*.aspx" type="WebPatterns.FrontController.Handler,WebPatterns"/>
    〈/httpHandlers>
    〈!-- 指定名为FrontControllerMap的页面映射块,交由UrlMap类处理,程序将根据key找到对应的url作为最终的执行路径,您在这可以定义多个key与url的键值对 -->
    〈configSections>
    〈section name="FrontControllerMap" type="WebPatterns.FrontController.UrlMap, WebPatterns">〈/section>
    〈/configSections>
    〈FrontControllerMap>
    〈entries>
    〈entry key="/WebPatterns/FrontController/DummyWebForm.aspx" url="/WebPatterns/FrontController/ActWebForm.aspx" />
    。。。
    〈/entries>
    〈/FrontControllerMap>

    修改webForm.aspx.cs:
    private void button_Click( object sender, System.EventArgs e )
    {
    Response.Redirect( "DummyWebForm.aspx?requestParm=" + dropDownList.SelectedValue );
    }
    当程序执行到这里时将会根据Web.Config里的定义触发类Handler的ProcessRequest事件:
    Handler.cs:
    public class Handler : IHttpHandler
    {
    public void ProcessRequest( HttpContext context )
    {
    Command command = CommandFactory.Make( context.Request.Params );
    command.Execute( context );
    }
    public bool IsReusable
    {
    get
    {
    return true;
    }
    }
    }

    而它又会调用类CommandFactory的Make方法来处理接收到的参数并返回一个Command对象,紧接着它又会调用该Command对象的Execute方法把处理后参数提交到具体处理的页面。
    public class CommandFactory
    {
    public static Command Make( NameValueCollection parms )
    {
    string requestParm = parms["requestParm"];
    Command command = null;
    //根据输入参数得到不同的Command对象
    switch ( requestParm )
    {
    case "1" :
    command = new FirstPortal();
    break;
    case "2" :
    command = new SecondPortal();
    break;
    default :
    command = new FirstPortal();
    break;
    }
    return command;
    }
    }
    public interface Command
    {
    void Execute( HttpContext context );
    }
    public abstract class RedirectCommand : Command
    {
    //获得Web.Config中定义的key和url键值对,UrlMap类详见下载包中的代码
    private UrlMap map = UrlMap.SoleInstance;
    protected abstract void OnExecute( HttpContext context );
    public void Execute( HttpContext context )
    {
    OnExecute( context );
    //根据key和url键值对提交到具体处理的页面
    string url = String.Format( "{0}?{1}", map.Map[ context.Request.Url.AbsolutePath ], context.Request.Url.Query );
    context.Server.Transfer( url );
    }
    }
    public class FirstPortal : RedirectCommand
    {
    protected override void OnExecute( HttpContext context )
    {
    //在输入参数中加入项portalId以便页面处理
    context.Items["portalId"] = "1";
    }
    }
    public class SecondPortal : RedirectCommand
    {
    protected override void OnExecute(HttpContext context)
    {
    context.Items["portalId"] = "2";
    }
    }
    最后在ActWebForm.aspx.cs中:
    dataGrid.DataSource = GetSubjectDataSource( HttpContext.Current.Items["portalId"].ToString() );
    dataGrid.DataBind();

    上面的例子展示了如何通过Front Controller集中和处理所有的请求,它使用CommandFactory来确定要执行的具体操作,无论执行什么方法和对象,Handler只调用Command对象的Execute方法,您可以在不修改 Handler的情况下添加额外的命令。它允许让用户看不到实际的页面,当用户输入一个URL时,然后系统将根据web.config文件将它映射到特定的URL,这可以让程序员有更大的灵活性,还可以获得Page Controller实现中所没有的一个间接操作层。
    对于相当复杂的Web应用我们才会采用Front Controller模式,它通常需要将页面内置的Controller替换为自定义的Handler,在Front Controllrer模式下我们甚至可以不需要页面,不过由于它本身实现比较复杂,可能会给业务逻辑的实现带来一些困扰。
    以上两个Controller模式都是处理比较复杂的WebForm应用,相对于直接处理用户输入的应用来讲复杂度大大提高,性能也必然有所降低,为此我们最后来看一个可以大幅度提高程序性能的模式:Page Cache模式。

    Page Cache模式下的WebForm

    几乎所有的WebForm面临的都是访问很频繁,改动却很少的应用,对WebForm的访问者来说有相当多的内容是重复的,因此我们可以试着把WebForm或者某些相同的内容保存在服务器内存中一段时间以加快程序的响应速度。
    这个模式实现起来很简单,只需在页面上加入:
    〈%@ OutputCache Duration="60" VaryByParam="none" %>,
    这表示该页面会在60秒以后过期,也就是说在这60秒以内所有的来访者看到该页面的内容都是一样的,但是响应速度大大提高,就象静态的HTML页面一样。
    也许您只是想保存部分的内容而不是想保存整个页面,那么我们回到MVC模式中的SQLHelper.cs,我对它进行了少许修改:
    public static DataSet GetPortal()
    {
    DataSet dataSet;
    if ( HttpContext.Current.Cache["SELECT_PORTAL_CACHE"] != null )
    {
    //如果数据存在于缓存中则直接取出
    dataSet = ( DataSet ) HttpContext.Current.Cache["SELECT_PORTAL_CACHE"];
    }
    else
    {
    //否则从数据库中取出并插入到缓存中,设定绝对过期时间为3分钟
    dataSet = GetDataSet( SQL_SELECT_PORTAL );
    HttpContext.Current.Cache.Insert( "SELECT_PORTAL_CACHE", dataSet, null, DateTime.Now.AddMinutes( 3 ), TimeSpan.Zero );
    }
    return dataSet;
    }

    在这里把SELECT_PORTAL_CACHE作为Cache的键,把GetDataSet( SQL_SELECT_PORTAL )取出的内容作为Cache的值。这样除了程序第1次调用时会进行数据库操作外,在Cache过期时间内都不会进行数据库操作,同样大大提高了程序的响应能力。
    小结
    自从.NET框架引入设计模式以后在很大程度上提高了其在企业级应用方面的实力,可以毫不夸张的说在企业级应用方面.NET已经赶上了Java的步伐并大有后来居上之势,本文通过一个实例的讲解向读者展示了在.NET框架下实现Web设计模式所需的一些基本知识,希望能起到一点抛砖引玉的作用。
    展开全文
  • 怎么说呢,作为程序员,我们明天都应该学习新的知识。 以前我在对数据库进行操作的时候都是在同一页面对数据库进行操作。这样的话,就是操作繁琐,而且需要重复的书写对数据库操作的代码,这样不仅浪费了很多的时间...

    怎么说呢,作为程序员,我们明天都应该学习新的知识。

    以前我在对数据库进行操作的时候都是在同一页面对数据库进行操作。这样的话,就是操作繁琐,而且需要重复的书写对数据库操作的代码,这样不仅浪费了很多的时间,而且也使得代码看起来非常的凌乱。至从接触到了三层架构的思想,明白了分层的好处,还有面向对象的好处。今天在看书的时候,看到周金桥老师的对数据库访问的通用类,就试着模仿的写了一个,代码清晰,而且很实用,包括了对数据库的所有的常用的操作。

    /// <summary>
        /// 数据库访问通用类
        /// </summary>
        public class SqlHelper
        {
            private string connectionString;
    
            /// <summary>
            /// 设定数据库访问字符串
            /// </summary>
            public string ConnectionString
            {
                
                set { connectionString = value; }
            }
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="connectionString">数据库访问字符串</param>
            public SqlHelper(string connectionString)
            {
                this.connectionString = connectionString;
            }
    
            /// <summary>
            /// 执行一个查询,并返回查询结果
            /// </summary>
            /// <param name="sql">要执行的sql语句</param>
            /// <param name="commandType">要执行的查询语句的类型,如存储过程或者sql文本命令</param>
            /// <returns>返回查询结果集</returns>
            public DataTable ExecuteDataTable(string sql,CommandType commandType)
            {
                return ExecuteDataTable(sql, commandType, null);
            }
    
            /// <summary>
            /// 执行一个查询,并返回结果集
            /// </summary>
            /// <param name="sql">要执行的sql文本命令</param>
            /// <returns>返回查询的结果集</returns>
            public DataTable ExecuteDataTable(string sql)
            {
                return ExecuteDataTable(sql, CommandType.Text, null);
            }
    
    
            /// <summary>
            /// 执行一个查询,并返回查询结果
            /// </summary>
            /// <param name="sql">要执行的sql语句</param>
            /// <param name="commandtype">要执行查询语句的类型,如存储过程或者sql文本命令</param>
            /// <param name="parameters">Transact-SQL语句或者存储过程参数数组</param>
            /// <returns></returns>
            public DataTable ExecuteDataTable(string sql, CommandType commandtype, SqlParameter[] parameters)
            {
                DataTable data = new DataTable(); //实例化datatable,用于装载查询结果集
                using (SqlConnection con = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand(sql, con))
                    {
                        cmd.CommandType = commandtype;//设置command的commandType为指定的Commandtype
                        //如果同时传入了参数,则添加这些参数
                        if (parameters != null)
                        {
                            foreach (SqlParameter parameter in parameters)
                            {
                                cmd.Parameters.Add(parameter);
                            }
                        }
    
                        //通过包含查询sql的sqlcommand实例来实例化sqldataadapter
                        SqlDataAdapter adapter = new SqlDataAdapter(cmd);
                        adapter.Fill(data);//填充datatable
    
                    }
                }
                return data;
            }
    
            /// <summary>
            /// 返回一个SqlDataReader对象的实例
            /// </summary>
            /// <param name="sql">要执行的SQl查询命令</param>
            /// <returns></returns>
            public SqlDataReader ExecuteReader(string sql)
            {
                return ExecuteReader(sql, CommandType.Text, null);
            }
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="sql">要执行的sql语句</param>
            /// <param name="commandType">要执行查询语句的类型,如存储过程或者SQl文本命令</param>
            /// <returns></returns>
            public SqlDataReader ExecuteReader(string sql,CommandType commandType)
            {
                return ExecuteReader(sql, commandType, null);
            }
    
            /// <summary>
            /// 返回一个sqldatareader对象的实例
            /// </summary>
            /// <param name="sql"></param>
            /// <param name="commandType"></param>
            /// <param name="parameters"></param>
            /// <returns></returns>
            public SqlDataReader ExecuteReader(string sql, CommandType commandType, SqlParameter[] parameters)
            {
                SqlConnection con = new SqlConnection(connectionString);
                SqlCommand cmd = new SqlCommand(sql, con);
    
                if (parameters != null)
                {
                    foreach (SqlParameter parameter in parameters)
                    {
                        cmd.Parameters.Add(parameters);
                    }
                }
                con.Open();
                //CommandBehavior.CloseConnection参数指示关闭reader对象时关闭与其关联的Connection对象
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
    
            /// <summary>
            /// 执行一个查询,返回结果集的首行首列。忽略其他行,其他列
            /// </summary>
            /// <param name="sql">要执行的SQl命令</param>
            /// <returns></returns>
            public Object ExecuteScalar(string sql)
            {
                return ExecuteScalar(sql, CommandType.Text, null);
            }
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="sql"></param>
            /// <param name="commandType"></param>
            /// <returns></returns>
            public Object ExecuteScalar(string sql, CommandType commandType)
            {
                return ExecuteScalar(sql, commandType, null);
            }
    
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="sql"></param>
            /// <param name="commandType">参数类型</param>
            /// <param name="parameters"></param>
            /// <returns></returns>
            public Object ExecuteScalar(string sql,CommandType commandType, SqlParameter[] parameters)
            {
                Object result=null;
                SqlConnection con=new SqlConnection(connectionString);
                SqlCommand cmd=new SqlCommand(sql,con);
                cmd.CommandType= commandType;
                if(parameters!=null)
                {
                    foreach (SqlParameter parapmeter in parameters)
                    {
                        cmd.Parameters.Add(parapmeter);
                    }
                }
    
                con.Open();
                result=cmd.ExecuteScalar();
                con.Close();
                return result;
            }
    
            /// <summary>
            /// 对数据库进行增删改的操作
            /// </summary>
            /// <param name="sql">要执行的sql命令</param>
            /// <returns></returns>
            public int ExecuteNonQuery(string sql)
            {
                return ExecuteNonQuery(sql, CommandType.Text, null);
            }
    
            /// <summary>
            /// 数据库进行增删改的操作
            /// </summary>
            /// <param name="sql">对数据库进行操作的sql命令</param>
            /// <param name="commandType">要执行查询语句的类型,如存储过程或者sql文本命令</param>
            /// <returns></returns>
            public int ExecuteNonQuery(string sql, CommandType commandType)
            {
                return ExecuteNonQuery(sql, commandType, null);
            }
    
            /// <summary>
            /// 对数据库进行增删改的操作
            /// </summary>
            /// <param name="sql">要执行的sql语句</param>
            /// <param name="commandType">要执行的查询语句类型,如存储过程或者sql文本命令</param>
            /// <param name="parameters">Transact-SQL语句或者存储过程的参数数组</param>
            /// <returns></returns>
            public int ExecuteNonQuery(string sql, CommandType commandType, SqlParameter[] parameters)
            {
                int count = 0;
                SqlConnection con = new SqlConnection(connectionString);
                SqlCommand cmd = new SqlCommand(sql, con);
                cmd.CommandType = commandType;
                if (parameters != null)
                {
                    foreach(SqlParameter parameter in parameters)
                    {
                        cmd.Parameters.Add(parameter);
                    }
                }
    
                con.Open();
                count = cmd.ExecuteNonQuery();
                con.Close();
                return count;
            }
    
            /// <summary>
            /// 返回当前连接的数据库中所有用户创建的数据库
            /// </summary>
            /// <returns></returns>
            public DataTable GetTables()
            {
                DataTable table = null;
                using (SqlConnection con = new SqlConnection(connectionString))
                {
                    con.Open();
                    table = con.GetSchema("Tables");
    
                }
                return table;
            }
        }

    如果我们建立了一个对数据库访问的通用类以后,在随数据库进行操作的时候吗,就只需要先实例化对象,然后根据自己的需要,调用相应的方法就可以完成对数据库的所有操作。这就是数据库访问层和业务逻辑层分开的好处。

    这样书写的代码,可以大大的减少我们代码的复杂度。而且,繁琐度也大大的降低了。


    展开全文
  • 领悟Web设计模式 (转载)

    千次阅读 2004-07-22 14:44:00
    专栏作品 领悟Web设计模式 http://www.microsoft.com/china/community/Column/93.mspx袁剑 领悟Web设计模式 本文发表在《程序春秋》2004年1期 摘要 本文介绍了... 关键字 设计模式,ASP.NET,WebForm,MVC,Page Contro
       
    领悟Web设计模式

    领悟Web设计模式
    本文发表在《程序春秋》2004年1期
    摘要
    本文介绍了在.NET框架下应用Web设计模式改进WebForm程序设计的一些基本方法及要点。
    关键字
    设计模式,ASP.NET,WebForm,MVC,Page Controller,Front Controller,Page Cache
    目录
    ? 引言
    ? 经典的WebForm架构
    ? 设计模式
    ? MVC模式下的WebForm
    ? Page Controller模式下的WebForm
    ? Front Controller模式下的WebForm
    ? Page Cache模式下的WebForm
    ? 参考资源
    ? 下载地址
    ? 作者信息
    引言
    记得微软刚刚推出ASP.NET时,给人的震撼是开发Web程序不再是编写传统的网页,而像是在构造应用程序,因而微软称之为WebForm。但是两年后的今天,有相当多的开发人员仍然延用写脚本程序的思路构建一个又一个的WebForm,而没有发挥出ASP.NET的优势,就此本文希望通过实例能够启发读者一些新的思路。
    由于篇幅有限,本文不可能通过一个复杂的Web应用来向读者展示结合设计模式的WebForm,但是如果仅仅是一个小程序的确没有使用模式的必要。为了便于理解,希望您能把它想象成是一个大型系统中的小模块(如果代码是大型系统的一部分那么使用模式就变得非常重要)。
    在本文的末尾给出了所有源程序的下载地址。
    经典的WebForm架构
    首先来看一个简单的应用,数据库设计如下图,Portal是Subject的父表,通过portalId进行一对多关联,程序需要根据portalId显示不同的Subject列表。
    按照我们编写WebForm一般的习惯,首先在页面上拖放一个DropDownList、一个DataGrid、一个Button控件:
    界面(webForm.aspx):
    <form id="webForm" method="post" runat="server"> <asp:DropDownList id="dropDownList" runat="server"></asp:DropDownList> <asp:Button id="button" runat="server" Text="Button"></asp:Button> <asp:DataGrid id="dataGrid" runat="server"></asp:DataGrid> </form> 
    然后利用VS.NET代码隐藏功能编写的核心代码如下:
    后置代码(webForm.aspx.cs):
    //页面初始化事件 private void Page_Load(object sender, System.EventArgs e) { if ( ! IsPostBack ) { string SQL_SELECT_PORTAL = "SELECT * FROM PORTAL"; //使用using确保释放数据库连接 //连接字符串存放在Web.Config文件中便于修改 using( SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["ConnectionString"] ) ) { SqlDataAdapter dataAdapter = new SqlDataAdapter( SQL_SELECT_PORTAL, conn ); DataSet dataSet = new DataSet(); dataAdapter.Fill( dataSet ); //设置下拉列表的数据源与文本域、值域 dropDownList.DataSource = dataSet; dropDownList.DataTextField = "portalName"; dropDownList.DataValueField = "portalId"; dropDownList.DataBind(); } } } //Button的Click事件 private void button_Click(object sender, System.EventArgs e) { string SQL_SELECT_SUBJECT = "SELECT * FROM SUBJECT WHERE portalId = {0}"; using( SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["ConnectionString"] ) ) { //用下拉列表选择的值替换掉SQL语句中的待定字符{0} SqlDataAdapter dataAdapter = new SqlDataAdapter( string.Format( SQL_SELECT_SUBJECT, dropDownList.SelectedValue ), conn ); DataSet dataSet = new DataSet(); dataAdapter.Fill( dataSet ); dataGrid.DataSource = dataSet; dataGrid.DataBind(); } } 


    执行结果如图所示,程序将根据下拉列表框选择的值绑定DataGrid,非常典型的一个WebForm架构,体现出ASP.NET事件驱动的思想,实现了界面与代码的分离。但是仔细看看可以从中发现几个问题:
    ? 对数据库操作的代码重复,重复代码是软件开发中绝对的“坏味道”,往往由于某些原因当你修改了一处代码,却忘记要更改另外一处相同的代码,从而给程序留下了Bug的隐患。
    ? 后置代码完全依赖于界面,在WebForm下界面的变化远远大于数据存储结构和访问的变化,当界面改变时您将不得不修改代码以适应新的页面,有可能将会重写整个后置代码。
    ? 后置代码不仅处理用户的输入而且还负责了数据的处理,如果需求发生变更,比如需要改变数据的处理方式,那么你将几乎重写整个后置代码。
    一个优秀的设计需要每一个模块,每一种方法只专注于做一件事,这样的结构才清晰,易修改,毕竟项目的需求总是在不断变更的,“唯一不变的就是变化本身”,好的程序一定要为变化作出准备,避免“牵一发而动全身”,所以一定要想办法解决上述问题,下面让我们来看看设计模式。
    设计模式
    设计模式描述了一个不断重复出现的问题以及对该问题的核心解决方案,它是成功的构架、设计及实施方案,是经验的总结。设计模式的概念最早来自于西方建筑学,但最成功的案例首推中国古代的“三十六计”。
    MVC模式下的WebForm
    MVC模式是一个用于将用户界面逻辑与业务逻辑分离开来的基础设计模式,它将数据处理、界面以及用户的行为控制分为:Model-View-Controller。
    ? Model:负责当前应用的数据获取与变更及相关的业务逻辑
    ? View:负责显示信息
    ? Controller:负责收集转化用户的输入

    View和Controller都依赖于Model,但是Model既不依赖于View,也不依赖于Controller,这是分离的主要优点之一,这样Model可以单独的建立和测试以便于代码复用,View和Controller只需要Model提供数据,它们不会知道、也不会关心数据是存储在SQL Server还是Oracle数据库中或者别的什么地方。
    根据MVC模式的思想,可以将上面例子的后置代码拆分为Model和Controller,用专门的一个类来处理数据,后置代码作为Controller仅仅负责转化用户的输入,修改后的代码为:
    Model(SQLHelper.cs):封装所有对数据库的操作。
    private static string SQL_SELECT_PORTAL = "SELECT * FROM PORTAL"; private static string SQL_SELECT_SUBJECT = "SELECT * FROM SUBJECT WHERE portalId = {0}"; private static string SQL_CONNECTION_STRING = ConfigurationSettings.AppSettings["ConnectionString"]; public static DataSet GetPortal() { return GetDataSet( SQL_SELECT_PORTAL ); } public static DataSet GetSubject( string portalId ) { return GetDataSet( string.Format( SQL_SELECT_SUBJECT, portalId ) ); } public static DataSet GetDataSet( string sql ) { using( SqlConnection conn = new SqlConnection( SQL_CONNECTION_STRING ) ) { SqlDataAdapter dataAdapter = new SqlDataAdapter( sql, conn ); DataSet dataSet = new DataSet(); dataAdapter.Fill( dataSet ); return dataSet; } } Controller(webForm.aspx.cs):负责转化用户的输入 private void Page_Load(object sender, System.EventArgs e) { if ( ! IsPostBack ) { //调用Model的方法获得数据源 dropDownList.DataSource = SQLHelper.GetPortal(); dropDownList.DataTextField = "portalName"; dropDownList.DataValueField = "portalId"; dropDownList.DataBind(); } } private void button_Click(object sender, System.EventArgs e) { dataGrid.DataSource = SQLHelper.GetSubject( dropDownList.SelectedValue ); dataGrid.DataBind(); } 
    修改后的代码非常清晰,M-V-C各司其制,对任意模块的改写都不会引起其他模块的变更,类似于MFC中Doc/View结构。但是如果相同结构的程序很多,而我们又需要做一些统一的控制,如用户身份的判断,统一的界面风格等;或者您还希望Controller与Model分离的更彻底,在Controller中不涉及到Model层的代码。此时仅仅靠MVC模式就显得有点力不从心,那么就请看看下面的Page Controller模式。
    Page Controller模式下的WebForm
    MVC 模式主要关注Model与View之间的分离,而对于Controller的关注较少(在上面的MVC模式中我们仅仅只把Model和Controller分离开,并未对Controller进行更多的处理),但在基于WebForm的应用程序中,View和Controller本来就是分隔的(显示是在客户端浏览器中进行),而Controller是服务器端应用程序;同时不同用户操作可能会导致不同的Controller策略,应用程序必须根据上一页面以及用户触发的事件来执行不同的操作;还有大多数WebForm都需要统一的界面风格,如果不对此处理将可能产生重复代码,因此有必要对Controller进行更为仔细的划分。
    Page Controller模式在MVC模式的基础上使用一个公共的页基类来统一处理诸如Http请求,界面风格等,如图:

    传统的WebForm一般继承自System.Web.UI.Page类,而Page Controller的实现思想是所有的WebForm继承自定义页面基类,如图:

    利用自定义页面基类,我们可以统一的接收页面请求、提取所有相关数据、调用对Model的所有更新以及向View转发请求,轻松实现统一的页面风格,而由它所派生的Controller的逻辑将变得更简单,更具体。
    下面看一下Page Controller的具体实现:
    Page Controller(BasePage.cs): public class BasePage : System.Web.UI.Page { private string _title; public string Title//页面标题,由子类负责指定 { get { return _title; } set { _title = value; } } public DataSet GetPortalDataSource() { return SQLHelper.GetPortal(); } public DataSet GetSubjectDataSource( string portalId ) { return SQLHelper.GetSubject( portalId ); } protected override void Render( HtmlTextWriter writer ) { writer.Write( "<html><head><title>" + Title + "</title></head><body>" );//统一的页面头 base.Render( writer );//子页面的输出 writer.Write( @"<a href=""http://www.asp.net"">ASP.NET</a></body></html>" );//统一的页面尾 } } 
    现在它封装了Model的功能,实现了统一的页面标题和页尾,子类只须直接调用:
    修改后的Controller(webForm.aspx.cs):
    public class webForm : BasePage//继承页面基类 { private void Page_Load(object sender, System.EventArgs e) { Title = "Hello, World!";//指定页面标题 if ( ! IsPostBack ) { dropDownList.DataSource = GetPortalDataSource();//调用基类的方法 dropDownList.DataTextField = "portalName"; dropDownList.DataValueField = "portalId"; dropDownList.DataBind(); } } private void button_Click(object sender, System.EventArgs e) { dataGrid.DataSource = GetSubjectDataSource( dropDownList.SelectedValue ); dataGrid.DataBind(); } } 
    从上可以看出BagePage Controller接管了大部分原来Controller的工作,使Controller变得更简单,更容易修改(为了便于讲解我没有把控件放在BasePage中,但是您完全可以那样做),但是随着应用复杂度的上升,用户需求的变化,我们很容易会将不同的页面类型分组成不同的基类,造成过深的继承树;又例如对于一个购物车程序,需要预定义好页面路径;对于向导程序来说路径是动态的(事先并不知道用户的选择)。
    面对以上这些应用来说仅仅使用Page Controller还是不够的,接下来再看看Front Controller模式。
    Front Controller模式下的WebForm
    Page Controller的实现需要在基类中为页面的公共部分创建代码,但是随着时间的推移,需求会发生较大的改变,有时不得不增加非公用的代码,这样基类就会不断增大,您可能会创建更深的继承层次结构以删除条件逻辑,这样一来我们很难对它进行重构,因此需要更进一步对Page Controller进行研究。
    Front Controller通过对所有请求的控制并传输解决了在Page Controller中存在的分散化处理的问题,它分为Handler和Command树两个部分,Handler处理所有公共的逻辑,接收HTTP Post或Get请求以及相关的参数并根据输入的参数选择正确的命令对象,然后将控制权传递到Command对象,由其完成后面的操作,在这里我们将使用到Command模式。
    Command模式通过将请求本身变成一个对象可向未指定的应用对象提出请求,这个对象可被存储并像其他的对象一样被传递,此模式的关键是一个抽象的Command类,它定义了一个执行操作的接口,最简单的形式是一个抽象的Execute操作,具体的Command子类将接收者作为其一个实例变量,并实现Execute操作,指定接收者采取的动作,而接收者具有执行该请求所需的具体信息。

    因为Front Controller模式要比上面两个模式复杂一些,我们再来看看例子的类图:

    关于Handler的原理请查阅MSDN,在这就不多讲了,我们来看看Front Controller模式的具体实现:
    首先在Web.Config里定义:
    <!-- 指定对Dummy开头的aspx文件交由Handler处理 --> <httpHandlers> <add verb="*" path="/WebPatterns/FrontController/Dummy*.aspx" type="WebPatterns.FrontController.Handler,WebPatterns"/> </httpHandlers> <!-- 指定名为FrontControllerMap的页面映射块,交由UrlMap类处理,程序将根据key找到对应的url作为最终的执行路径,您在这可以定义多个key与url的键值对 --> <configSections> <section name="FrontControllerMap" type="WebPatterns.FrontController.UrlMap, WebPatterns"></section> </configSections> <FrontControllerMap> <entries> <entry key="/WebPatterns/FrontController/DummyWebForm.aspx" url="/WebPatterns/FrontController/ActWebForm.aspx" /> 。。。 </entries> </FrontControllerMap> 修改webForm.aspx.cs: private void button_Click( object sender, System.EventArgs e ) { Response.Redirect( "DummyWebForm.aspx?requestParm=" + dropDownList.SelectedValue ); } 当程序执行到这里时将会根据Web.Config里的定义触发类Handler的ProcessRequest事件: Handler.cs: public class Handler : IHttpHandler { public void ProcessRequest( HttpContext context ) { Command command = CommandFactory.Make( context.Request.Params ); command.Execute( context ); } public bool IsReusable { get { return true; } } } 
    而它又会调用类CommandFactory的Make方法来处理接收到的参数并返回一个Command对象,紧接着它又会调用该Command对象的Execute方法把处理后参数提交到具体处理的页面。
    public class CommandFactory { public static Command Make( NameValueCollection parms ) { string requestParm = parms["requestParm"]; Command command = null; //根据输入参数得到不同的Command对象 switch ( requestParm ) { case "1" : command = new FirstPortal(); break; case "2" : command = new SecondPortal(); break; default : command = new FirstPortal(); break; } return command; } } public interface Command { void Execute( HttpContext context ); } public abstract class RedirectCommand : Command { //获得Web.Config中定义的key和url键值对,UrlMap类详见下载包中的代码 private UrlMap map = UrlMap.SoleInstance; protected abstract void OnExecute( HttpContext context ); public void Execute( HttpContext context ) { OnExecute( context ); //根据key和url键值对提交到具体处理的页面 string url = String.Format( "{0}?{1}", map.Map[ context.Request.Url.AbsolutePath ], context.Request.Url.Query ); context.Server.Transfer( url ); } } public class FirstPortal : RedirectCommand { protected override void OnExecute( HttpContext context ) { //在输入参数中加入项portalId以便页面处理 context.Items["portalId"] = "1"; } } public class SecondPortal : RedirectCommand { protected override void OnExecute(HttpContext context) { context.Items["portalId"] = "2"; } } 最后在ActWebForm.aspx.cs中: dataGrid.DataSource = GetSubjectDataSource( HttpContext.Current.Items["portalId"].ToString() ); dataGrid.DataBind(); 
    上面的例子展示了如何通过Front Controller集中和处理所有的请求,它使用CommandFactory来确定要执行的具体操作,无论执行什么方法和对象,Handler只调用Command对象的Execute方法,您可以在不修改 Handler的情况下添加额外的命令。它允许让用户看不到实际的页面,当用户输入一个URL时,然后系统将根据web.config文件将它映射到特定的URL,这可以让程序员有更大的灵活性,还可以获得Page Controller实现中所没有的一个间接操作层。
    对于相当复杂的Web应用我们才会采用Front Controller模式,它通常需要将页面内置的Controller替换为自定义的Handler,在Front Controllrer模式下我们甚至可以不需要页面,不过由于它本身实现比较复杂,可能会给业务逻辑的实现带来一些困扰。
    以上两个Controller模式都是处理比较复杂的WebForm应用,相对于直接处理用户输入的应用来讲复杂度大大提高,性能也必然有所降低,为此我们最后来看一个可以大幅度提高程序性能的模式:Page Cache模式。
    Page Cache模式下的WebForm
    几乎所有的WebForm面临的都是访问很频繁,改动却很少的应用,对WebForm的访问者来说有相当多的内容是重复的,因此我们可以试着把WebForm或者某些相同的内容保存在服务器内存中一段时间以加快程序的响应速度。
    这个模式实现起来很简单,只需在页面上加入:
    <%@ OutputCache Duration="60" VaryByParam="none" %>,
    这表示该页面会在60秒以后过期,也就是说在这60秒以内所有的来访者看到该页面的内容都是一样的,但是响应速度大大提高,就象静态的HTML页面一样。
    也许您只是想保存部分的内容而不是想保存整个页面,那么我们回到MVC模式中的SQLHelper.cs,我对它进行了少许修改:
    public static DataSet GetPortal() { DataSet dataSet; if ( HttpContext.Current.Cache["SELECT_PORTAL_CACHE"] != null ) { //如果数据存在于缓存中则直接取出 dataSet = ( DataSet ) HttpContext.Current.Cache["SELECT_PORTAL_CACHE"]; } else { //否则从数据库中取出并插入到缓存中,设定绝对过期时间为3分钟 dataSet = GetDataSet( SQL_SELECT_PORTAL ); HttpContext.Current.Cache.Insert( "SELECT_PORTAL_CACHE", dataSet, null, DateTime.Now.AddMinutes( 3 ), TimeSpan.Zero ); } return dataSet; } 
    在这里把SELECT_PORTAL_CACHE作为Cache的键,把GetDataSet( SQL_SELECT_PORTAL )取出的内容作为Cache的值。这样除了程序第1次调用时会进行数据库操作外,在Cache过期时间内都不会进行数据库操作,同样大大提高了程序的响应能力。
    小结
    自从.NET框架引入设计模式以后在很大程度上提高了其在企业级应用方面的实力,可以毫不夸张的说在企业级应用方面.NET已经赶上了Java的步伐并大有后来居上之势,本文通过一个实例的讲解向读者展示了在.NET框架下实现Web设计模式所需的一些基本知识,希望能起到一点抛砖引玉的作用。
    参考资源
    下载地址
    作者信息
    袁剑
    Microsoft ASP.NET MVP
    2003年12月6日
    展开全文
  • .net应该学什么怎么学---初学者必看

    万次阅读 多人点赞 2014-07-12 14:19:41
    上篇《学了.Net做什么开发》中我讲到了目前.Net开发主要方向是Web开发,因此在本篇中我将主要讲解做Web开发要学哪些东西,怎么学,由于内容比较多,我将分几次发。下图是我推荐的学习内容和学习顺序(除了这里列出的...
  • 大年三十整理的asp.net资料!(不得不收藏)

    万次阅读 热门讨论 2007-02-26 09:30:00
    大年三十整理的asp.net资料!(不得不收藏) 使用SqlBulkCopy类加载其他源数据到SQL表 在数据回发时,维护ASP.NET Tree控件的位置 vagerent的vs2005网站开发技巧 ASP.NET2.0
  • http://blog.csdn.net/mengyao/archive/2007/02/17/1511356.aspx使用SqlBulkCopy类加载其他源数据到SQL表 在数据回发时,维护ASP.NET Tree控件的位置 vagerent的vs2005网站开发技巧 ASP.NET2.0小技巧--内部控件...
  • 非常实用的C# backgroundworker使用方法

    万次阅读 2011-02-11 11:35:00
    # BackgroundWorker 控件的几个实例(C# backgroundworker使用方法): 在 WinForms 中,有时要执行耗时的操作,在该操作未完成之前操作用户界面,会导致用户界面停止响应。 解决的方法就是新开一个...
  • 前言 在实际开发过程中,当数据量... 最近的学习过程中,使用ASP.NET MVC配合Layui框架进行开发时,根据Layui Page传回的参数要进行分页操作(虽然EntityFramework提供了非常便捷的分页方法,但是SQL什么时候都不能...
  • OWASP(开放Web软体安全项目- Open Web Application Security Project)是一个开放社群、非营利性组织,目前全球有130个分会近万名会员,其主要目标是研议协助解决Web软体安全之标准、工具与技术文件,长期 致力于协助...
  • asp.net速查手册呵呵

    千次阅读 2006-07-23 20:58:00
    GridView/DataGrid单元格不换行的问题 如何在GridView中使用DataFromatString VS2005新控件之GridView 使用高级技巧系列[一][视频] VS2005新控件之GridView 使用高级技巧 几个.Net开源的CMS、Portal系统 gridview...
  • asp.net资料

    千次阅读 2007-03-06 09:40:00
    使用SqlBulkCopy类加载其他源数据到SQL表 在数据回发时,维护ASP.NET Tree控件的位置 vagerent的vs2005网站开发技巧 ASP.NET2.0小技巧--内部控件权限的实现 图片滚动代码。 css——之三行三列等高布局 Datagird ...
  • 在 ASP.NET WebForm中使用 Route

    千次阅读 2017-03-30 23:14:23
    使用过ASP.NET MVC的同学可能对Route已经有些接触,在ASP.NET WebForm中为实现类似的功能也许会使用URL Rewrite。本文将介绍在ASP.NET WebForm中使用Route。1.找到Global.asax文件,如果没有这个文件就新建一个。2....
  • (不丢弃缓存再重新读取数据来缓存) 1.ASP.NET 数据库缓存依赖在ASP.NET中,Cache类最酷的特点是它能根据各种依赖来良好的控制自己的行为。以文件为基础的依赖是最有用的,文件依赖项是通过使用Cache.Insert 并...
  • SQL语句参数化(1)插入数据

    千次阅读 2007-04-05 15:54:00
    WebForm1.aspxImports System.TextPublic Class WebForm1 Inherits System.Web.UI.Page#Region " Web 窗体设计器生成的代码 " 该调用是 Web 窗体设计器所必需的。 Private Sub InitializeComponent() End Sub ...
  • 领悟Web设计模式

    千次阅读 2004-12-20 08:34:00
    领悟Web设计模式本文发表在《程序春秋》2004年1期摘要本文介绍了在.NET框架下应用Web设计模式改进WebForm程序设计的一些基本方法及要点。关键字设计模式,ASP.NET,WebForm,MVC,Page Controller,Front Controller...
  • ASP.NET及C#网上资料小收藏

    千次阅读 2008-01-18 14:19:00
    使用SqlBulkCopy类加载其他源数据到SQL表 在数据回发时,维护ASP.NET Tree控件的位置 vagerent的vs2005网站开发技巧 ASP.NET2.0小技巧--内部控件权限的实现 图片滚动代码。 css——之三行三列等高布局 Datagird ...
  • 软件测试面试题

    千次阅读 2008-07-16 15:31:00
    品高软件: 1. 封装,继承,多态的含义。封装就是把各种方法和变量合并到一个类,用这个类代表某个对象为完成一定的任务所能保存的范围以及它能执行的操作。继承就是根据现有类的方法和成员变量生成新的类的功能...
  • ASP.NET Core 企业级开发架构简介及框架汇总 企业开发框架包括垂直方向架构和水平方向架构。垂直方向架构是指一个应用程序的由下到上叠加多层的架构,同时这样的程序又叫整体式程序。水平方向架构是指将大应用...
  • BootStrap-table 在传统Asp.NET(webform)中基于ashx(一般处理程序)的调用  就目前的形式来看Asp.NET webform的模式已经显得过时了,可是在许多高校教学中,和一些外包项目中还大量充斥着旧代码的使用,在传统...
1 2 3 4 5 ... 20
收藏数 2,499
精华内容 999
关键字:

webform 调用sql存储过程