Community Server专题八:MemberRole之Membership深入篇

server

专题八的上篇大致讨论了MemberRole中的Membership实现,对于运用Membership进行web开发足够,但是对于想更深入了解Membership实现机理的朋友那是远远不够的,这个专题我们更深入一下了解Membership。

其实MemberRole是一个非常好的资源包,借住Reflector这个优秀的工具,你可以对其进行代码分析。它无论是在组建的构架、代码的设计、数据库表的建立、存储过程的使用等都是非常优秀的,你是程序员也好构架师也罢,其中可以学习的真的很多很多,我在整个分析的过程中也深深受益。

由于MemberRole中的Membership只实现了对SQL Server的操Provider类,即SqlMembershipProvider类。因此我们从SqlMembershipProvider开始分析。Provider模型在上篇已经做过介绍,SqlMembershipProvider类继承了MembershipProvider,并实现其所有的抽象方法。在分析之前先看两个类:MembershipUser与MembershipUserCollection。

MembershipUser,先看看代码:(代码中省略的具体实现,只有方法与属性名称)

public class MembershipUser
{
      // Methods
      protected MembershipUser();
      public MembershipUser(MembershipProvider provider, string name, object providerUserKey, string email, string passwordQuestion, string comment, bool isApproved, bool isLockedOut, DateTime creationDate, DateTime lastLoginDate, DateTime lastActivityDate, DateTime lastPasswordChangedDate, DateTime lastLockoutDate);
      public virtual bool ChangePassword(string oldPassword, string newPassword);
      public virtual bool ChangePasswordQuestionAndAnswer(string password, string newPasswordQuestion, string newPasswordAnswer);
      public virtual string GetPassword();
      public virtual string GetPassword(string passwordAnswer);
      public virtual string ResetPassword();
      public virtual string ResetPassword(string passwordAnswer);
      public override string ToString();
      public virtual bool UnlockUser();
      internal virtual void Update();
      private void UpdateSelf();
      // Properties
      public virtual string Comment { get; set; }
      public virtual DateTime CreationDate { get; }
      public virtual string Email { get; set; }
      public virtual bool IsApproved { get; set; }
      public virtual bool IsLockedOut { get; }
      public bool IsOnline { get; }
      public virtual DateTime LastActivityDate { get; set; }
      public virtual DateTime LastLockoutDate { get; }
      public virtual DateTime LastLoginDate { get; set; }
      public virtual DateTime LastPasswordChangedDate { get; }
      public virtual string PasswordQuestion { get; }
      public virtual MembershipProvider Provider { get; }
      public virtual object ProviderUserKey { get; }
      public virtual string UserName { get; }
      // Fields
      private string _Comment;
      private DateTime _CreationDate;
      private string _Email;
      private bool _IsApproved;
      private bool _IsLockedOut;
      private DateTime _LastActivityDate;
      private DateTime _LastLockoutDate;
      private DateTime _LastLoginDate;
      private DateTime _LastPasswordChangedDate;
      private string _PasswordQuestion;
      private MembershipProvider _Provider;
      private object _ProviderUserKey;
      private string _UserName;
}

这是一个实体类,表示一个由Membership创建的User,该类中有这个User的一些基本状态,如该User的UserName、Email等,还有一些方法,如ChangePassword()、ResetPassword()等(如果你是初学者,还在为建立一个对象需要什么属性,包含什么方法发愁,那这就是你应该好好学的,这也是OOP最基本的要求)。

MembershipUserCollection,这是一个MembershipUser类的容器,用来存放MembershipUser列表,记得上次广州.net俱乐部聚会时,我的演讲中有朋友在提出CS是否使用自定义类来存储用户列表,其实在这里可以看到CS中使用的就是自定义的类而不是DataSet(我想在asp.net 2.0正式发布后这也不会改变),这样做主要是因为考虑到性能与灵活性。

好了,回到SqlMembershipProvider类上来,我们具体分析一个有代表性质的方法:

public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)

{

      string text3;

      MembershipUser user1;

      if (!SecUtility.ValidateParameter(ref password, true, true, false, 0x80))

      {

            status = MembershipCreateStatus.InvalidPassword;

            return null;

      }

      string text1 = base.GenerateSalt();

      string text2 = base.EncodePassword(password, (int) this._PasswordFormat, text1);

      if (text2.Length > 0x80)

      {

            status = MembershipCreateStatus.InvalidPassword;

            return null;

      }

      if (passwordAnswer != null)

      {

            passwordAnswer = passwordAnswer.Trim();

      }

      if ((passwordAnswer != null) && (passwordAnswer.Length > 0))

      {

            if (passwordAnswer.Length > 0x80)

            {

                  status = MembershipCreateStatus.InvalidAnswer;

                  return null;

            }

            text3 = base.EncodePassword(passwordAnswer.ToLower(CultureInfo.InvariantCulture), (int) this._PasswordFormat, text1);

      }

      else

      {

            text3 = passwordAnswer;

      }

      if (!SecUtility.ValidateParameter(ref text3, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, false, 0x80))

      {

            status = MembershipCreateStatus.InvalidAnswer;

            return null;

      }

      if (!SecUtility.ValidateParameter(ref username, true, true, true, 0x100))

      {

            status = MembershipCreateStatus.InvalidUserName;

            return null;

      }

      if (!SecUtility.ValidateParameter(ref email, this.RequiresUniqueEmail, this.RequiresUniqueEmail, false, 0x100))

      {

            status = MembershipCreateStatus.InvalidEmail;

            return null;

      }

      if (!SecUtility.ValidateParameter(ref passwordQuestion, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, false, 0x100))

      {

            status = MembershipCreateStatus.InvalidQuestion;

            return null;

      }

      if ((providerUserKey != null) && !(providerUserKey is Guid))

      {

            status = MembershipCreateStatus.InvalidProviderUserKey;

            return null;

      }

      if (password.Length < this.MinRequiredPasswordLength)

      {

            status = MembershipCreateStatus.InvalidPassword;

            return null;

      }

      int num1 = 0;

      for (int num2 = 0; num2 < password.Length; num2++)

      {

            if (!char.IsLetterOrDigit(password, num2))

            {

                  num1++;

            }

      }

      if (num1 < this.MinRequiredNonAlphanumericCharacters)

      {

            status = MembershipCreateStatus.InvalidPassword;

            return null;

      }

      if ((this.PasswordStrengthRegularExpression.Length > 0) && !Regex.IsMatch(password, this.PasswordStrengthRegularExpression))

      {

            status = MembershipCreateStatus.InvalidPassword;

            return null;

      }

      ValidatePasswordEventArgs args1 = new ValidatePasswordEventArgs(username, password, true);

      this.OnValidatingPassword(args1);

      if (args1.Cancel)

      {

            status = MembershipCreateStatus.InvalidPassword;

            return null;

      }

      try

      {

            SqlConnectionHolder holder1 = null;

            try

            {

                  holder1 = SqlConnectionHelper.GetConnection(this._sqlConnectionString, true);

                  this.CheckSchemaVersion(holder1.Connection);

                  SqlCommand command1 = new SqlCommand("dbo.aspnet_Membership_CreateUser", holder1.Connection);

                  command1.CommandTimeout = this.CommandTimeout;

                  command1.CommandType = CommandType.StoredProcedure;

                  command1.Parameters.Add(this.CreateInputParam("@ApplicationName", SqlDbType.NVarChar, this.ApplicationName));

                  command1.Parameters.Add(this.CreateInputParam("@UserName", SqlDbType.NVarChar, username));

                  command1.Parameters.Add(this.CreateInputParam("@Password", SqlDbType.NVarChar, text2));

                  command1.Parameters.Add(this.CreateInputParam("@PasswordSalt", SqlDbType.NVarChar, text1));

                  command1.Parameters.Add(this.CreateInputParam("@Email", SqlDbType.NVarChar, email));

                  command1.Parameters.Add(this.CreateInputParam("@PasswordQuestion", SqlDbType.NVarChar, passwordQuestion));

                  command1.Parameters.Add(this.CreateInputParam("@PasswordAnswer", SqlDbType.NVarChar, text3));

                  command1.Parameters.Add(this.CreateInputParam("@IsApproved", SqlDbType.Bit, isApproved));

                  command1.Parameters.Add(this.CreateInputParam("@UniqueEmail", SqlDbType.Int, this.RequiresUniqueEmail ? 1 : 0));

                  command1.Parameters.Add(this.CreateInputParam("@PasswordFormat", SqlDbType.Int, (int) this.PasswordFormat));

                  command1.Parameters.Add(this.GetTimeZoneAdjustmentParam());

                  SqlParameter parameter1 = this.CreateInputParam("@UserId", SqlDbType.UniqueIdentifier, providerUserKey);

                  parameter1.Direction = ParameterDirection.InputOutput;

                  command1.Parameters.Add(parameter1);

                  parameter1 = new SqlParameter("@ReturnValue", SqlDbType.Int);

                  parameter1.Direction = ParameterDirection.ReturnValue;

                  command1.Parameters.Add(parameter1);

                  object obj1 = command1.ExecuteScalar();

                  DateTime time1 = this.RoundToSeconds(DateTime.Now);

                  if ((obj1 != null) && (obj1 is DateTime))

                  {

                        time1 = (DateTime) obj1;

                  }

                  int num3 = (parameter1.Value != null) ? ((int) parameter1.Value) : -1;

                  if ((num3 < 0) || (num3 > 11))

                  {

                        num3 = 11;

                  }

                  status = (MembershipCreateStatus) num3;

                  if (num3 != 0)

                  {

                        return null;

                  }

                  providerUserKey = new Guid(command1.Parameters["@UserId"].Value.ToString());

                  return new MembershipUser(this, username, providerUserKey, email, passwordQuestion, null, isApproved, false, time1, time1, time1, time1, new DateTime(0x6da, 1, 1));

            }

            finally

            {

                  if (holder1 != null)

                  {

                        holder1.Close();

                        holder1 = null;

                  }

            }

      }

      catch

      {

            throw;

      }

      return user1;

}

该方法实现建立一个用户的过程,建立后返回一个被建立的MembershipUser对象,如果建立失败MembershipUser对象为null(其实我早期做过一些项目的时候喜欢在建立对象成功后返回一个ID)。可以看到在这个方法中有很多的if语句,它们是为了检验数据是否合法,这是必须的吗?其实不是,但对于构建一个强壮的底层代码这是必须的,不然一点点的错误都有可能导致系统的瘫痪。其实做项目与做开发有的时候不太一样,企业的有些项目开发很多时候只要能实现功能就可以了,而且开发过程也集中在一些现有的代码或者组建的基础上,个人的错误不会影响全局的运行,PM也不做过多要求。但如果是做产品,这个情况可能会有所改变,很多时候要求很严格,至少我是这样。在做完对输入参数的验证后,CreateUser建立与数据库的连接,这里是调用SqlConnectionHelper类下的GetConnection方法进行的,为了照顾初学者阅读,我这里这一下为什么需要把对数据库连接与操作写在SqlConnectionHelper类下,而不是直接采用SqlConnection提供的方法,其实这是一个设计模式的问题,Membership的实现需要很多的方法与数据库进行交换数据库,如果每次方法都调用一次SqlConnection的方法建立数据库连接,一来会造成大量的代码冗余,而且一旦数据库连接语句一旦改变,你就要去修改很多个方法,如果你把这个过程都包装在一个类下面,连接数据库就有统一的入口,一来容易维护,二来不会有太多的代码冗余,再者如果需要查找错误也非常容易。这里Membership采用的是存储过程,我们可以看到使用的是dbo.aspnet_Membership_CreateUser存储过程,好了,打开你的数据库,找到这个存储过程:

CREATE PROCEDURE dbo.aspnet_Membership_CreateUser

    @ApplicationName                        NVARCHAR(256),

    @UserName                               NVARCHAR(256),

    @Password                               NVARCHAR(128),

    @PasswordSalt                           NVARCHAR(128),

    @Email                                  NVARCHAR(256),

    @PasswordQuestion                       NVARCHAR(256),

    @PasswordAnswer                         NVARCHAR(128),

    @IsApproved                             BIT,

    @TimeZoneAdjustment                     INT,

    @CreateDate                             DATETIME = NULL,

    @UniqueEmail                            INT      = 0,

    @PasswordFormat                         INT      = 0,

    @UserId                                 UNIQUEIDENTIFIER OUTPUT

AS

BEGIN

    DECLARE @ApplicationId UNIQUEIDENTIFIER

    SELECT  @ApplicationId = NULL

    DECLARE @NewUserId UNIQUEIDENTIFIER

    SELECT @NewUserId = NULL

    DECLARE @IsLockedOut BIT

    SET @IsLockedOut = 0

    DECLARE @LastLockoutDate  DATETIME

    SET @LastLockoutDate = CONVERT( DATETIME, '17540101', 112 )

    DECLARE @FailedPasswordAttemptCount INT

    SET @FailedPasswordAttemptCount = 0

    DECLARE @FailedPasswordAttemptWindowStart  DATETIME

    SET @FailedPasswordAttemptWindowStart = CONVERT( DATETIME, '17540101', 112 )

    DECLARE @FailedPasswordAnswerAttemptCount INT

    SET @FailedPasswordAnswerAttemptCount = 0

    DECLARE @FailedPasswordAnswerAttemptWindowStart  DATETIME

    SET @FailedPasswordAnswerAttemptWindowStart = CONVERT( DATETIME, '17540101', 112 )

    DECLARE @NewUserCreated BIT

    DECLARE @ReturnValue   INT

    SET @ReturnValue = 0

    DECLARE @ErrorCode     INT

    SET @ErrorCode = 0

    DECLARE @TranStarted   BIT

    SET @TranStarted = 0

    IF( @@TRANCOUNT = 0 )

    BEGIN

           BEGIN TRANSACTION

           SET @TranStarted = 1

    END

    ELSE

           SET @TranStarted = 0

    EXEC dbo.aspnet_Applications_CreateApplication @ApplicationName, @ApplicationId OUTPUT

    IF( @@ERROR <> 0 )

    BEGIN

        SET @ErrorCode = -1

        GOTO Cleanup

    END

    IF (@CreateDate IS NULL)

        EXEC dbo.aspnet_GetUtcDate @TimeZoneAdjustment, @CreateDate OUTPUT

    ELSE

        SELECT  @CreateDate = DATEADD(n, -@TimeZoneAdjustment, @CreateDate) -- switch TO UTC time

    SELECT  @NewUserId = UserId FROM dbo.aspnet_Users WHERE LOWER(@UserName) = LoweredUserName AND @ApplicationId = ApplicationId

    IF ( @NewUserId IS NULL )

    BEGIN

        SET @NewUserId = @UserId

        EXEC @ReturnValue = dbo.aspnet_Users_CreateUser @ApplicationId, @UserName, 0, @CreateDate, @NewUserId OUTPUT

        SET @NewUserCreated = 1

    END

    ELSE

    BEGIN

        SET @NewUserCreated = 0

        IF( @NewUserId <> @UserId AND @UserId IS NOT NULL )

        BEGIN

            SET @ErrorCode = 6

            GOTO Cleanup

        END

    END

    IF( @@ERROR <> 0 )

    BEGIN

        SET @ErrorCode = -1

        GOTO Cleanup

    END

    IF( @ReturnValue = -1 )

    BEGIN

        SET @ErrorCode = 10

        GOTO Cleanup

    END

    IF ( EXISTS ( SELECT UserId

                  FROM   dbo.aspnet_Membership

                  WHERE  @NewUserId = UserId ) )

    BEGIN

        SET @ErrorCode = 6

        GOTO Cleanup

    END

    SET @UserId = @NewUserId

    IF (@UniqueEmail = 1)

    BEGIN

        IF (EXISTS (SELECT *

                    FROM  dbo.aspnet_Membership m WITH ( UPDLOCK, HOLDLOCK )

                    WHERE ApplicationId = @ApplicationId AND LoweredEmail = LOWER(@Email)))

        BEGIN

            SET @ErrorCode = 7

            GOTO Cleanup

        END

    END

    INSERT INTO dbo.aspnet_Membership

                ( ApplicationId,

                  UserId,

                  Password,

                  PasswordSalt,

                  Email,

                  LoweredEmail,

                  PasswordQuestion,

                  PasswordAnswer,

                  PasswordFormat,

                  IsApproved,

                  IsLockedOut,

                  CreateDate,

                  LastLoginDate,

                  LastPasswordChangedDate,

                  LastLockoutDate,

                  FailedPasswordAttemptCount,

                  FailedPasswordAttemptWindowStart,

                  FailedPasswordAnswerAttemptCount,

                  FailedPasswordAnswerAttemptWindowStart )

         VALUES ( @ApplicationId,

                  @UserId,

                  @Password,

                  @PasswordSalt,

                  @Email,

                  LOWER(@Email),

                  @PasswordQuestion,

                  @PasswordAnswer,

                  @PasswordFormat,

                  @IsApproved,

                  @IsLockedOut,

                  @CreateDate,

                  @CreateDate,

                  @CreateDate,

                  @LastLockoutDate,

                  @FailedPasswordAttemptCount,

                  @FailedPasswordAttemptWindowStart,

                  @FailedPasswordAnswerAttemptCount,

                  @FailedPasswordAnswerAttemptWindowStart )

    IF( @@ERROR <> 0 )

    BEGIN

        SET @ErrorCode = -1

        GOTO Cleanup

    END

    IF (@NewUserCreated = 0)

    BEGIN

        UPDATE dbo.aspnet_Users

        SET    LastActivityDate = @CreateDate

        WHERE  @UserId = UserId

        IF( @@ERROR <> 0 )

        BEGIN

            SET @ErrorCode = -1

            GOTO Cleanup

        END

    END

    SELECT @CreateDate = DATEADD( n, @TimeZoneAdjustment, @CreateDate )

    IF( @TranStarted = 1 )

    BEGIN

           SET @TranStarted = 0

           COMMIT TRANSACTION

    END

    RETURN 0

Cleanup:

    IF( @TranStarted = 1 )

    BEGIN

        SET @TranStarted = 0

           ROLLBACK TRANSACTION

    END

    RETURN @ErrorCode

END

GO

够长的,不过没有关系,分几个部分看,首先是定义一些要发挥得参数,然后初始化,接着EXEC dbo.aspnet_Applications_CreateApplication,调用aspnet_Applications_CreateApplication存储过程,建立一个名字为@ApplicationName 的Application,如果该Application不存在的话.并且返回该Application的ID,这里的ApplicationName在web.config membership节点中设置过,即:dev。如果执行以上过程有错误,通过SQL的GOTO语句跳至Cleanup部分,执行ROLLBACK TRANSACTION,回滚这次操作。如果没有错误存储过程就接着向下执行,EXEC dbo.aspnet_GetUtcDate @TimeZoneAdjustment, @CreateDate OUTPUT,这是获得当前Utc时间。再下来就判断aspnet_Users表中用户的UserId是否在数据库中有该UserId(UserId是一个Guid),如果没有就在表aspnet_Users中建立。这时在进行一次失分发生错误的判断,执行的方法与前一次一样。再下来判断aspnet_Membership表中是否有该UserId存在,如果没有就根据@UniqueEmail参数判断是否允许Email在数据库中重复。最后才是把User的信息插入aspnet_Membership表,再下来还有一些对错误的处理...

其实这个存储过程并不复杂,但是非常繁琐的,也可以看出设计者对数据库检验的严格性的要求非常高。有了对存储过程一定的了解后,我们接下来那些传递的参数也就明白有何用处了,最后关闭数据库的连接,把返回的这些参数通过实例化一个MembershipUser类传递过去,然后返回这个实例化的MembershipUser,这样该方法就完成了一次操作。

最后我们看看数据库,Membership直接关联的有3个表

表很简单,关系也很明了,我就不多说了,总要给我留点时间吧,也给你自己留一些分析的空间,我要是全都说完了那你做什么?呵呵。

    如果你了解CS系统,你肯定会提出这样一个疑问:用户信息不只表Membership中这一点呀,保存用户个性化设置的如选用什么语言、什么皮肤等等信息的数据都在哪里?期待吧,那是后面的Profile专题需要叙述的问题。

时间: 2024-09-10 22:24:59

Community Server专题八:MemberRole之Membership深入篇的相关文章

Community Server专题八:MemberRole之Membership

server MemberRole是一个在asp.net 1.1下实现用户管理.角色管理.用户特性信息存储(profile)等的一个组件,该组件被ASP.NET 2.0 Beta 2所采用,也就是ASP.NET 2.0 Beta 2中所说的Membership and Roles.如果你在asp.net 1.1下采用了MemberRole,那么你的web程序将会很容易的过渡到asp.net 2.0,另外多个采取MemberRole进行用户管理的web程序需要整合时也非常容易.我将分4个专题来分析

Community Server专题一:概述Community Server_实用技巧

Community Server专题一:概述Community Server Community Server(CS)是一个非常优秀的Asp.net开源软件,目前官方发布的系统中包括三个部分:Asp.net Forums.DotText.Gallery.如果你是某个以CS构架网站的会员,你可以很容易的就拥有一个Blog.一个相册.还能在论坛上与他人一起进行讨论,这样就形成一个以User为中心的社区,这也就是起名为 Community Server的意义所在了. CS的构架很巧妙,三套原本不同的开

Swift语法专题八——闭包

Swift讲解专题八--闭包 一.引言         Swift中的闭包是有一定功能的代码块,这十分类似于Objective-C中的block语法.Swift中的闭包语法风格十分简洁,其作用和函数的作用相似. 二.从一个系统函数看闭包         Swift标准函数库中提供了一个sort排序函数,对于已经元素类型的数组,调用sort函数会进行重新排序并返回新的排序后的数组.这个sort函数可以接收一个返回值为Bool类型的闭包,来确定第一个元素是否排在第二个元素前面.代码示例如下: var

RDS SQL Server - 专题分享 - 巧用执行计划缓存之索引缺失

title: RDS SQL Server - 专题分享 - 巧用执行计划缓存之索引缺失 author: 风移 摘要 执行计划缓存是MSSQL Server内存管理十分重要的部分,同样如何巧用执行计划缓存来解决我们平时遇到的一系列问题也是一个值得深入研究的专题.这篇文章是如何巧用执行计划缓存的开篇,分享如何使用执行计划缓存来分析索引缺失(Missing Indexes). 问题引入 缺失索引是SQL Server CPU使用率居高不下的第一大杀手,也是SQL Server数据库非常大的潜在风险点

Community Server专题二:体系结构_实用技巧

Community Server专题二:体系结构 在进行CS细节分析的之前,有必要先了解CS工程(解决方案)的组成,以及组成CS工程中项目的结构,本文分为三个部分:1.工程结构 2.三层构架 3.数据库构架. 1:工程结构 =538) {this.width=538;}" border=0> CS工程主要分为4个部分 a:系统底层构架项目CommunityServerComponents.CommunityServerControls,提供给其他项目父类.接口.全局变量.CS系统设置.公用

精通Windows Server 2008多元密码策略之PowerShell篇

前言 在上两篇文章<精通Windows Server 2008 多元密码策略之ADSIEDIT篇>和<精通Windows Server 2008 多元密码策略之LDIFDE篇>中我向大家介绍了如何通过ADSIEDIT工具.活动目录用户和计算机管理单元和LDIFDE命令行工具创建.管理密码设置对象PSO.在这篇文章中,我将向大家展示如何使用Quese公司出品的针对AD管理的PowerShell来实现.管理多元密码策略. 按照惯例,为了让大家在操作的时候有一个清晰的思路,我将主要的操作

精通Windows Server 2008多元密码策略之LDIFDE篇

前言 在上一篇文章<精通Windows Server 2008 多元密码策略之ADSIEDIT篇>中我向大家介绍了如何通过ADSIEDIT工具和活动目录用户和计算机管理单元创建.管理密码设置对象PSO.原理性的东西和需要注意的地方我就这这篇文章和之后的文章中不再赘述了.有需要的请查看上篇文章.接下来的重点主要是动手部分.废话少说,开始!为了让大家在操作的时候有一个清晰的思路,我将主要的操作步骤写出来: 步骤 1:创建 PSO 步骤 2:将 PSO 应用到用户和/或全局安全组 步骤 3:管理 P

SQL Server 临时表和变量系列之对比篇

摘要 在SQL Server代码编写过程中,经常会有需要临时"暂存"一部分数据结果集,供上下文使用,这个时候,我们有两种选择,即临时表和表变量.这篇文章从以下几个方面来对临时表和表变量进行对比: 创建和析构方式 存储方式 作用域 对事务的支持 性能影响 创建和析构方式 临时表和表变量在创建和析构方式上是完全不一样的,在这一节,我们会从以下几点来看看他们的不同. 结构定义 索引创建 DDL 析构方式 结构定义 在上一篇文章SQL Server 临时表和变量系列之概念篇中

Community Server专题三:HttpModule

server 从专题三开始分析Community Server的一些具体的技术实现,根据IIS对请求的处理流程,从HttpModule& HttpHandler切入话题,同时你也可以通过一系列的专题了解CS的运行过程,不只如此,所有的.Net 1.1 构架的Web App都是以同样的顺序执行的. 先了解一下IIS系统.它是一个程序,负责对网站的内容进行管理并且处理对客户的请求做出反应.当用户对一个页面提出请求时,IIS做如下反应(不考虑权限问题): 1.把对方请求的虚拟路径转换成物理路径 2.根