[转载]看了一些ASP.NET MVC开源项目后的一些想法,关于ASP.NET MVC+Repository+Service架构的一些思考

mikel阅读(952)

[转载]看了一些ASP.NET MVC开源项目后的一些想法,关于ASP.NET MVC+Repository+Service架构的一些思考 – 专注于ASP.NET技术 – 博客园.

最近在学习ASP.NET MVC 2.0的一些开源项目,发现这些项目中都普遍用到了同一种架构设计,即:

ASP.NET MVC + Service + Repository。从网上看了一些关于这方面的介绍后觉得这种架构确实满好的。以微软的一个典型的开源项目Oxite为例:

该项目由下面的Projects组成:

1)Oxite;

2)Oxite.LinqtoSQLDataProvider;

3)Oxite.Mvc;

4)Oxite.Mvc.Tests;

5)OxiteSite;

Oxite Project:

1)定义所有项目中需要用到的Model,即Entity,并且所有的Model都是纯Model,它们不依赖于任何ORM框架相关的信息。Model的作用是作为数据在UI层、业务逻辑层、数据访问层之间传递;

2)定义Repository接口。Repository和通常三层架构中的数据库访问层(DAL)从形式和功能上看差不多,个人感觉区别两者在意图上有所不同。
Repository 是DDD(Domain-Driven Design 领域驱动模型 )中的概念,强调Repository是受Domain驱动的,Repository中定义的功能要体现Domain的意图 和约束,而DAL更纯粹的就是提供数据访问的功能,并不严格受限于Business层。Repository所提供的一切接口都应该是业务逻辑层所需要 的,如果业务逻辑不需要的,它就不必提供。但是最近看到网上有一些朋友实现了一些泛型的Repository接口,个人认为不是很好。因为这违背了我们设 计Repository的初衷,Repository接口是提供给Domain层的操作契约,不同的Entity对于Domain来说可能有不同的操作约 束,比如User可能不应该被删除,BookOrder可能不应该被修改,也就是说Domain层根本就不应该能调用 _repository<User>.Delete(user),_repository<BookOrder>.Update(BookOrder) 这样的操作。因此,Repository接口还是应该单独针对每个Entity类来定义。

3)定义和实现Service层。

Servide层定义和实现了整个应用程序的所有业务逻辑。Service利用Repository接口来完成数据库操作。每个Service接口除了利用Repository来操作数据库之外,还会做很多额外的事情,如数据验证等。

Oxite.LinqtoSQLDataProvider Project:

该项目是用 Linq to SQL ORM 技术实现的一个具体的 DataProvider(Repository)。该项目中会定义一些Linq to Sql ORM框架相关的Entities,借助于LINQ强大的语法功能,我们可以很方便的把这些Entities转换为Oxite中定义的Entity。如:

1 public User GetUser(string name)
2 {
3 return (from u in context.oxite_Users
4 where string.Compare(u.Username, name, true) == 0
5 select new User()
6 {
7 ID = u.UserID,
8 Name = u.Username,
9 DisplayName = u.DisplayName,
10 Email = u.Email,
11 HashedEmail = u.HashedEmail,
12 Password = u.Password,
13 PasswordSalt = u.PasswordSalt,
14 Status = u.Status
15 }).FirstOrDefault();
16 }

oxite_User是Linq to Sql ORM框架所生成的Entity,User就是Oxite Model中定义的Entity。

Oxite.Mvc Project:

该项目包含了所有的Controller,但不包含View;Controller负责利用Service层来为View提供服务。一般来说,只要 是和ASP.NET MVC相关的技术,都不应该放在Service层中实现,而应该放在Controller中实现。这样可以确保Service层可以被非ASP.NET MVC技术的程序所重用。

OxiteSite Project:

该项目就是一个普通的ASP.NET MVC Website,但它仅仅包含了一些View以及js和css等。

总结:

上面的架构设计我觉得有以下三个好处:

1)Oxite project实际上已经完整的代表了的应用了。因为一个应用由UI、Business、Data三部分组成。而这个project包含了 Business和Data,当然,应该说它包含了对整个应用程序的业务逻辑的描述,并没有包含具体的业务逻辑实现,具体的业务逻辑的数据持久化实现是通 过Oxite.LinqtoSqlDataProvider这个项目实现的。但这并不影响我们把Oxite理解为整个应用的主体。所以我想这个项目就是 ASP.NET MVC架构中的Model吧。

2)利用Repository模式,完全把应用程序的业务逻辑和数据持久化工作分离。所以我们完全可以编写很多不同的ORM框架来完成数据的持久化 工作。我们唯一需要配置的就是在web.config文件中设置该使用哪一个DataProvider;当然,就Oxite这个开源项目而言,在 Oxite.LinqtoSqlDataProvider中单独定义了另外一套Entity,导致我们在查询数据时必须要做一个Entity的转换。这一 点也许是我觉得有点不好的地方吧。当然其实Linq to Sql是支持xml来配置ORM映射关系的。所以理论上应该支持不用另外定义一套Entity。

3)前台利用ASP.NET MVC技术实现,并且把Controller和View分离在不同的项目中实现。个人觉得主要的考虑是为了更好的团队开发。让开发View的人专注于设计View,而让Controller开发人员专注于View和Model之间的控制协调。

所以,可以设想,如果基于这样的架构开发一个应用,个人觉得可以先开发好Model,然后再开发一个或几个DataProvider来实现 Model中定义的Business,然后可以写一个Test工程来测试这个Model,等Model稳定后,再去开发View和Controller。

以上是我对我最近看的一些ASP.NET MVC开源项目的一点小小的思考,欢迎大家批评指正。

[转载]在ASP.NET MVC中进行TDD开发

mikel阅读(948)

[转载]在ASP.NET MVC中进行TDD开发 – 海纳百川 – 博客园.

TDD介绍

TDD是一种开发方法,全称是Test-Driven development,中文是测试驱动开发。作者是Kent Beck。首先让我介绍一下三种常见的开发方式:

第一种:先Coding,然后Bug Fix。

第二种:先Coding,然后Unit Test,最后Bug Fix。很显然用了单元测试的比第一种开发方式要好不少。

三种:就是本文要说的TDD,它的方式和第二种恰恰相反。TDD先设计单元测试,然后再Coding,最后修复Bug。看下图:

hkkk

TDD开发过程可以看成:给制自己制定一个目标,然后努力去完成这个目标。下面是这个比喻对应关系。斜杠前面的是TDD,斜杠后面的是完成目标:

1、写一个测试  /  制定一个目标

2、编译     / 了解完成目标的标准

3、运行测试,结果失败  /   目标没有达成

4、修改代码    /     为了完成目标,不停奋斗

5、再次运行测试,结果通过  /    目标达成。

这个说明应该比较形象啦。

下面我们看下真正的TDD循环,如下图所示:

ppp

TDD循环:

第一步:写测试类,然后拿去编译。

第二步:将编译生成好的dll之类的拿去测试,测试失败。

第三步:失败之后,就去修改测试的代码,直到测试通过。

第四步:测试成功之后,去重构你的代码,增加代码的可读性,但不能改变程序的任何功能。重构代码之后还需要测试,必须测试成功为止。

这四个步骤之后,在从写测试类重新开始其他的功能。

ASP.NET MVC中进行TDD开发

下面用一个在ASP.NET mvc2 中进行一次简单的TDD开发实战。单元测试我使用NUnit。

新建一个你类库项目命名为Test.Unit。再建一个ASP.NET mvc2的项目。项目结构如下图:

kkk

首先进行第一步,写测试确定要实现什么:我们实现一个数据列表的显示。

在类库项目中添加一个nunit.framework引用,然后再设计一个TodoControllerTest类用于测试控制器。

usingNUnit.Framework;
namespaceTest.Unit
{
[TestFixture]
public  classTodoControllerTest
{
[Test]
public voidShould_Display_A_List_Of_Todo_Items()
{

}

}
}

这个测试类用于测试Todo控制器,它需要数据实体。所以我们要在ASP.NET MVC 项目中添加Model。在Model添加下面实体:

public class Todo
{
    public static List<Todo> ThingsToBeDone = new List<Todo>
    {
      new Todo {Title = "Get Milk", Completed = false},
      new Todo {Title = "Bring Home Bacon", Completed = false}
    };

    public bool Completed { get; set; }
    public string Title { get; set; }
}

我们修改TodoCpmtroller,代码如下:

[TestFixture]
public  class TodoControllerTest
{
    [Test]
    public void Should_Display_A_List_Of_Todo_Items()
    {
        var viewResult = (ViewResult)new TodoController().Index();
        Assert.AreEqual(Todo.ThingsToBeDone, viewResult.ViewData.Model); 

    }

}

测试TodoCpmtroller中的Index方法返回的数据是否与Todo的ThingsToBeDone相同,而此时我们还没有在MVC项目中添加TodoController类和Index方法。

故我们需要在MVC项目中添加TodoController类,代码如下:

public class TodoController : Controller
{
    //
    // GET: /Todo/

    public ActionResult Index()
    {
        return View();
    }

}

我们编译项目发现没有错误。

使用Nunit进行测试,发现测试不通过,这对应TDD循环的的第二步。

nnn

因为Index中没有返回任何的数据,所有才会测试失败。修改Index方法,代码如下。

public class TodoController : Controller
{
    //
    // GET: /Todo/

    public ActionResult Index()
    {
        ViewData.Model = Todo.ThingsToBeDone;
        return View();
    }

}

编译继续测试,发现测试通过。对应上文的第三步。

kkkkk

这个例子非常简单,也就用不着去重构代码,所以TDD循环的第四步可以省去。然后接着下一轮的循环。

总结:本文首先简单的介绍了一下TDD,随后在ASP.NET MVC中进行了实战。

参考:Test-Drive ASP.NET MVC

[转载]设计模式——Adapter Pattern(适配器模式)

mikel阅读(892)

[转载]设计模式——Adapter Pattern(适配器模式) – 探索、挖掘、研究、致用、创新 – 博客园.

本文通过对象适配器模式模拟一个翻译人员或翻译系统的工作流程,希望能体现出Adapter适配器模式的精髓。

先看本次模拟的UML图

源角色(Adaptee)

01 /// <summary>
02 /// 源角色(Adaptee)
03 /// </summary>
04 public abstract class Languages
05 {
06 public virtual string Translate(string languageText)
07 {
08 return "暂不支持这种语言。";
09 }
10 }
11
12 public class Chinese : Languages
13 {
14 /// <summary>
15 /// 翻译为英文
16 /// </summary>
17 /// <param name="chinese"></param>
18 /// <returns></returns>
19 public override string Translate(string englishText)
20 {
21 //翻译过程...
22 return "罗伊 明";
23 }
24 }
25
26 public class English : Languages
27 {
28 /// <summary>
29 /// 翻译为英文
30 /// </summary>
31 /// <param name="chinese"></param>
32 /// <returns></returns>
33 public override string Translate(string chineseText)
34 {
35 //翻译过程...
36 return "Roy Ming";
37 }
38 }

目标角色(Target)

1 /// <summary>
2 /// 目标角色(Target)
3 /// </summary>
4 public interface ITarget
5 {
6 string Translate(string languageText);
7 }

适配器角色(Adapter)

01 /// <summary>
02 /// 适配器角色(Adapter)
03 /// </summary>
04 public class Translator : ITarget
05 {
06 private Languages _language;
07 public Translator(Languages language)
08 {
09 this._language = language;
10 }
11
12 /// <summary>
13 ///
14 /// </summary>
15 /// <param name="language"></param>
16 /// <returns></returns>
17 public string Translate(string languageText)
18 {
19 string result = this._language.Translate(languageText);
20 Console.WriteLine("翻译完成。");
21 return result;
22 }
23 }

客户端调用

1 ITarget target1 = new Translator(new English());
2 string english = target1.Translate("Roy Ming");
3 Console.WriteLine("翻译结果:" + english);
4
5 ITarget target2 = new Translator(new Chinese());
6 string chinese = target2.Translate("罗伊 明");
7 Console.WriteLine("翻译结果:" + chinese);

适配器模式简介

将一个类的接口转换为客户希望的另外一个接口。Adapter模式使原本由于接口不兼容而不能在一起的工作的类可以一起工作。—源自<设计模式>

适配器模式(Adatper Patterns)分为类适配器模式、对象适配器模式。

类适配器模式的实现方式通过多重继承。如下图

而对象适配器模式依赖于一个对象(适配对象)包含另一个对象(被适配对象)。如下图

Adapter模式与Facade模式看起来确实比较类似。他们都是通过封装的手法展示的,不过他们的本质还是有很多的区别存在的。

比较一下

  • Adapter模式和Facade模式都存在既有的类。
  • Facade模式所针对的是简化接口,而Adapter模式所针对的是将一个已有的接口转换成另一个接口。
  • Adapter模式必须遵循某个特定的接口进行设计,而Facade模式无需如此。
  • Facade模式无需多态行为,Adapter模式在某些复杂的设计中将会使用到多态行为。

Adapter模式涉及到的角色

  1. 目标角色(Target):对客户提供的接口,必须为接口。
  2. 源角色(Adaptee):带适配的源(一般为类Class)。
  3. 适配器角色(Adapter):将源(Adaptee)接口适配为目标接口。

适配器模式使用场景:

  1. 需要使用原有的接口,而此接口不符合系统的需要。
  2. 想要建立一个可以重复使用的接口,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
  3. 对象适配器而言在设计中,需要改变多个现有的源接口,类适配器模式要针对每一个源接口做一个适配器,而显然不实际。

小结

适配器模式是一个很常用的模式,它将一个(或多个)类的接口转换成我们需要类所具备的另一个接口,它的实现方式是:创建一个所需接口的新类,然后包装原有类的方法,这样实际上就包含了被适配的对象。

源码下载

[转载]疯狂的想法——基于.NET的软件超市平台构想与5年实现之路

mikel阅读(809)

[转载]疯狂的想法——基于.NET的软件超市平台构想与5年实现之路 – 道法自然 – 博客园.

2005年的时候,我曾经基于.NET 2003开发了一个小的组件,这个组件的目的是为了解决模块化开发和模块复用的问题。我将该组件命名为Common Form Framework,它的目的是允许每一个开发人员独立的开发自己的模块且可以直接专注于业务模块,然后通过配置可以快速将所有开发人员开发的业务逻辑窗体集成到这个组件中。

该组件的思路如下图所示。该组件提供了一个如“2”标识的空的窗体,每一个开发人员通过编写一个如“1”的XML配置文件即可将一个模块的功能附加到空窗体,最终组合成一个如“3”所示的软件产品。

这个组件成功的应用在一个由9个人合作开发,历时1年的应用系统开发中。它的想法和Microsoft Composite Application Block有一些类似,不过没有CAB那么强大了。

在参考了CAB和经历更多应用系统之后,我发现该组件有不少缺点,比如:模块化定义不够标准、界面元素无法扩展、模块交互非常复杂、功能复用低、不能应用于Web或者其它应用环境等。为此,我参考了CABSCSF的一些功能,并在2007设计了一个Commom UI Platform规范,旨在设计一个更为强大简单的模块化快速开发平台。

不过在2008年的时候,我有了更多的想法,提出了一个UIShell产品构思。UIShell是英文User Interface Shell的缩写,中文译为“用户界面外壳”。它是由一个软件模板框架。它由框架层、服务层、Shell层和系统模块层组成,提供了基于UIShell的软件设计和开发规范。 Shell中文译为“外壳”,它是应用系统的主界面,由可扩展的界面元素(如菜单、工具栏)和可替换的界面(如显示区)元素组成,如下图所示。

这个产品面向的用户有2种:(1)开发人员——该产品能够为开发人员提供一个模块化设计规范、通用的界面框架和通用的服务,从而使得开发人员可以直接设计业务模块,不需要关心软件的界面、用户体验等;(2)最终用户——最终用户不需要去购买任何软件,可以通过基于该平台的软件超市中下载到所需的界面框架和应用模块,然后自己组装成最终的软件。

在我现在看来,当时的想法确实有点疯狂,因为我想的太简单太远大了(不过,有时候还真需要疯狂才能干点什么,:)),不过我那会一点都没有意识到这点。我当时组了一个UIShellDev Team。我很骄傲的告诉团队,“一旦我们实现了UIShell,我们或许能够为软件行业开辟一个新的方向,为其贡献点什么”。

于是我们便开始了UIShell产品之路,我们疯狂的学习了Enterprise LibrarySCSFSharpDevelopEgeye AddinMAFMEF等,分析了SCSF的源代码、SD源代码,学习了Framework Design Guideline,关注每一个新出现的产品并分析竞争优势与劣势(如Google App EngineSina App EngineMEF等,我们不能开发一个对开发人员来讲没有用且过时的产品,因此需要时刻保持警惕),制定了产品开发规范——“用户场景设计规范、需求规范、设计规范、质量保证体系等”…… 这个产品设计目标以“易用性”为首要目标,这意味着我们做任何功能都应该先想到用户,并模拟用户的行为习惯来不断的优化产品的设计。然而这条路并没有像我预想的那么容易,我原来以为这个产品早该在2009年底就发布了。设计的过程中,问题一个接一个,且由于我们团队是兼职的,进度比我预想的慢了许多。更为重要的是,当时想法是基于SCSF来做的,SCSF太过于复杂,并不能够满足我们的需求。在一个偶然的机会,我接触了OSGi规范,并利用业余时间将OSGi规范翻译了。看了OSGi后,我眼前一亮,我意识到了这就是我想要的。然而OSGi是基于Java的规范,由于.NET平台和Java平台的差异,我们需要设计一个符合.NET平台的规范。于是,我们便动手自己设计了OSGi.NET规范,在设计这个规范时,我们借用了OSGi规范但调整了它的目标,即OSGi.NET的定位是一个满足.NET不同应用环境的通用模块化运行时,它实现了OSGi的模块化与插件化、面向服务、模块扩展和安全性的功能。

OSGi.NET规范及接口设计在2008年底设计完成,我记得当时完成设计的时候,我正在美国的Dublin,通过Email把设计的图纸和规范发送给UIShellDev Team。这是该规范的初稿。在接下来的日子里,我们不断的对设计进行重构,最终在20098月份实现了内核原型,在200910月份完成了OSGi.NET设计最终稿。当然,在重构的过程中,团队其他成员已经开始动手设计了。在这里我们设计了一个能够通用于各种.NET运行环境的模块化运行时,它实现了UIShell产品所有功能,并且易用性依然保持。我们自行设计了模块化规范、模块运行时类加载规范、SOA规范和扩展规范、开发与调试规范。不过,中间有一个决策比较困难,因为ASP.NET不同于WinFormWPFConsole,它必须宿主在Web Server。那么,我们的争论就在于——是IIS宿主模块运行时还是模块运行时宿主IIS呢?如果模块运行时宿主IIS,那么它就有完全的控制权,不够运行于IIS的模块与模块运行时的其它模块间的通讯就麻烦了,因为这是跨进程通讯。如果IIS宿主模块运行时,那么模块运行时就比较被动了。最终讨论的结果是采用第二种方案,因为这种方案性能高、简单。在完成最终稿设计时,产品设计的所有问题便解决了。我们便投入所有的精力去实现。

目前UIShell产品设计与实现已经进入尾声,这也意味着软件超市的基础平台已经基本构建完成。我们实现的OSGi.NET内核已经能够成功的宿主在.NET各种不同环境,并且各种环境的设计思路、开发思路完全一致。软件超市以后将会有不同环境的Shell模块、通用服务和应用模块,这样,用户和开发人员都可以去下载和组装软件,并且也可以去贡献自己开发的东西。

还需要提到的是,UIShell产 品在实现的过程中,关于质量保证体系的构建。事实上,产品设计的初始阶段,我是很希望所有的东西都能够非常的完善,包括质量保证体系,我当时是一个完美主 义者。不过,我们并没有足够的资源来支撑“完美”。在这一过程中,我学会了妥协、学会了“软件中庸”,我们只能把有限的资源投入到最需要的地方,况且每一 个阶段的目标还不同。当然了,我们现在已经构建了一个简单且有效的质量保证体系,它基于“Subversion/TotoiseSVN/AnkhSVN + CruiseControl.NET/NAnt + BugTracker.NET”实现。Subversion提供了类似ClearCase的配置管理功能,是一个开源免费的产品,它提供了强大的Branch/Tag管理,Branch/Tag是我当时选择配置管理工具的首要要求,这是产品线管理的必备功能。CruiseControl.NET/Nant用于持续集成,在每一个代码更新时,它都会自动Build,我们可以看到产品线是否健康,此外,还有一个很重要功能,我们可以随时构建一个新的用于测试的安装包。BugTracker.NET也是一个开源的缺陷管理工具,我们可以随时创建Bug。它在每次Bug更新时,都会向团队发送邮件。它提供了强大的缺陷统计管理,在Bug Fixing阶段,我们可以方便的安排产品不同阶段需要Fix的所有Bug,也可以用于统计每一个人的工作量。当然了,我们还根据需要对BugTracker.NET进行了改进,主要有2个:(1)当代码提交时,Bug状态自动变为Check in并发送邮件;(2)加入代码审计功能,可以方便的为每一个Bug生成代码审计包,从而使得我们可以方便查看每一个Bug所做的更改。以下是一个BugTracker.NET Email通知示例。

目前UIShell内核产品由安装包工程、VS插件工程、Remote Console工程、OSGi.NET工程、SaaS工程、Web Extension工程、Shell工程、测试工程和Help工程构成。只要在不同环境中采用如下方式宿主模块运行时,这个环境便具有了OSGi.NET的所有特性。现在经过测试的环境有控制台、WinFormASP.NET,接下来我们在完善了文档、Sample之后将发布第一个版本,并在下个版本中实现对更多环境的集成测试,完善产品,并构建软件超市网站。此外,我们还将构建一个SaaS商店,不过这是另一个产品了,我将会在以后介绍我们SaaS商 店产品了。最后我要感谢UIShellDev Team的所有成员,他们为产品的构建付出了很大的努力,提出了很多有建设意义的想法,这个产品是一个团队的结晶。在产品研发过程中,我们体验了团队 1+1>2的力量。没有他们的付出,UIShell产品是不可能实现的,更别提其它宏伟的想法了。每次想起与团队开发过程中的细节,我都非常的骄傲 和感动,这些人真nice!

namespace SimpleBundleShell

{

class Program

{

static void Main(string[] args)

{

// 启动模块化运行时,这样,它就会自动的从plugins加载所有模块

// 并启动它们。在这里,一个模块=普通.NET项目 + Manifest.xml

// 因此,模块的开发和我们原来开发一个.NET项目的方式是完全一样

// 的,我们不需要学习新的知识。

using (BundleRuntime bundleRuntime = new BundleRuntime())

{

bundleRuntime.Start();

Console.WriteLine(“Press enter to exit…”);

Console.ReadLine();

}

}

}

}

[转载]ASP.NET MVC2右键菜单和最简单分页

mikel阅读(1040)

[转载]ASP.NET MVC2右键菜单和最简单分页 – 海纳百川 – 博客园.

右键菜单非常方便,很多时候会用到。这篇文章将使用一个JQuery的插件在ASP.NET mvc中实现右键菜单。本文还将介绍一下在ASP.NET mvc中如何实现简单的分页。效果如下图:

首先,下载此插件

新建一个ASP.NET mvc应用程序。将此插件放入Scripts文件夹。并在页面上引用。

这个demo使用到NORTHWND数据库的Product表。

定义右键菜单:

1 <div class="contextMenu" id="myMenu1"> 2 <ul> 3 <li id="detail"><img src="http://www.cnblogs.com/Content/detail.ico" />detail</li> 4 <li id="new"><img src="http://www.cnblogs.com/Content/new.ico" />new</li> 5 <li id="delete"> <img src="http://www.cnblogs.com/Content/delete.ico"/>delete</li> 6 <li id="modify"><img src="http://www.cnblogs.com/Content/modify.ico"/>modify</li> 7 </ul> 8  </div>

将此菜单定义在产品名上,故在在产品名上添加一个class供JQuery选择。

<td class="showContext" id="<%= item.ProductID %>"><%: item.ProductName %></td>

在页面上插入下面脚本。用于绑定菜单项的行为。为了简单起见,将所以的菜单项的行为都定义成导航到详情页面。

1 <script type="text/javascript"> 2 3 $(document).ready(function () { 4 $('td.showContext').contextMenu('myMenu1', { 5 bindings: { 6 'detail': function (t) { 7 document.location.href = '/Products/Detail/'+t.id; 8 }, 9 'new': function (t) { 10 document.location.href = '/Products/Detail/' + t.id; 11 }, 12 'delete': function (t) { 13 confirm("你确定删除吗?"); 14 document.location.href = '/Products/Detail/' + t.id; 15 }, 16 'modify': function (t) { 17 document.location.href = '/Products/Detail/' + t.id; 18 } 19 } 20 }); 21 }); 22 23  </script>

这样就非常简单的实现了右键菜单的功能。

下面说下实现简单的分页。ASP.NET MVC中分页非常简单。

看下面定义的table的html代码:

1 <table> 2 <tr> 3 <th> 4 ProductName 5 </th> 6 <th> 7 SupplierID 8 </th> 9 <th> 10 CategoryID 11 </th> 12 <th> 13 QuantityPerUnit 14 </th> 15 <th> 16 UnitPrice 17 </th> 18 <th> 19 UnitsInStock 20 </th> 21 <th> 22 UnitsOnOrder 23 </th> 24 <th> 25 ReorderLevel 26 </th> 27 <th> 28 Discontinued 29 </th> 30 </tr> 31 32 <% foreach (var item in Model.Products) 33 { %> 34 <tr> 35 <td class="showContext" id="<%= item.ProductID %>"><%: item.ProductName %></td> 36 <td> 37 <%: item.SupplierID %> 38 </td> 39 <td> 40 <%: item.CategoryID %> 41 </td> 42 <td> 43 <%: item.QuantityPerUnit %> 44 </td> 45 <td> 46 <%: String.Format("{0:F}", item.UnitPrice) %> 47 </td> 48 <td> 49 <%: item.UnitsInStock %> 50 </td> 51 <td> 52 <%: item.UnitsOnOrder %> 53 </td> 54 <td> 55 <%: item.ReorderLevel %> 56 </td> 57 <td> 58 <%: item.Discontinued %> 59 </td> 60 </tr> 61 62 <% } %> 63 64 </table>

我们只要在这个table下面插入一段分页的HTML脚本就行了。分页的脚本当然要生成,使用Htmlhelper的扩展方法去生成这个脚本。看下面的扩展方法,非常的简单的生成了分页的html代码:

1 public static string Pager(this HtmlHelper helper, int currentPage, int currentPageSize, int totalRecords, string urlPrefix) 2 { 3 StringBuilder sb1 = new StringBuilder(); 4 5 int seed = currentPage % currentPageSize == 0 ? currentPage : currentPage - (currentPage % currentPageSize); 6 7 if (currentPage > 0) 8 sb1.AppendLine(String.Format("<a href=\"{0}/{1}\">Previous</a>", urlPrefix, currentPage)); 9 10 if (currentPage - currentPageSize >= 0) 11 sb1.AppendLine(String.Format("<a href=\"{0}/{1}\">...</a>", urlPrefix, (currentPage - currentPageSize) + 1)); 12 13 for (int i = seed; i < Math.Round((totalRecords / 10) + 0.5) && i < seed + currentPageSize; i++) 14 { 15 sb1.AppendLine(String.Format("<a href=\"{0}/{1}\">{1}</a>", urlPrefix, i + 1)); 16 } 17 18 if (currentPage + currentPageSize <= (Math.Round((totalRecords / 10) + 0.5) - 1)) 19 sb1.AppendLine(String.Format("<a href=\"{0}/{1}\">...</a>", urlPrefix, (currentPage + currentPageSize) + 1)); 20 21 if (currentPage < (Math.Round((totalRecords / 10) + 0.5) - 1)) 22 sb1.AppendLine(String.Format("<a href=\"{0}/{1}\">Next</a>", urlPrefix, currentPage + 2)); 23 24 return sb1.ToString(); 25 }

然后在table后面添加下面的代码,在table下面输出分页的html代码:

<div class="pager"> <%=Html.Pager(Model.CurrentPage, Model.TotalPages,Model.TotalItems ,"/Products/List")%> </div>

这样就完成分页和右键菜单的功能了。是不是非常的简单呢。:)

效果:

显示:

如果有兴趣可以下载代码。

总结:ASP.NET MVC中实现右键菜单和简单的分页。

代码http://cid-aef1e64945224a20.office.live.com/self.aspx/.Public/ContextMenuDemo.rar

[转载]【基本原理】使用托管代码实现一个寄宿ASP.NET的HTTP服务器(上)

mikel阅读(883)

[转载]【基本原理】使用托管代码实现一个寄宿ASP.NET的HTTP服务器(上) – Technology Based On Windows Platform – 博客园.

在这两篇文章中我将带着大家实现一个简单的ASP.NET的寄宿环境,本文是第一部分,主要讲述一些原理性的内容,其中大多使用自己的通俗的语 言描述一些概念和基础性的内容,为了给读者带来更多的认识,本文也解释了一些最原始的概念,有些内容是自己的总结,如有不对或理解不一致的地方请读者指 出。唉,本人实在不善于写文章,写了只是为了总结一些。

1.ASP.NET的Pipeline模型

ASP.NET随着.NET Framework一起分发,从1.0开始就一直以IIS作为寄宿环境。ASP.NET更是一个HTTP的处理引擎,内部实现了一套Pipeline模 型,实现流水线处理请求的设计,从构造上线文环境(HttpContext)开始,经过一系列的过滤器(HttpModule,至于如何安装网上已经有很 多介绍,HttpModule类似win32下的钩子Hook,可以做一些过滤处理,很实用的),请求会被送达处理器(HttpHandler,这个处理 器可以完成对这个请求的最终处理),最后再经过一些过滤器而通过寄宿环境返回给客户端。需要说明的是这个Pipeline是一些讲述ASP.NET Runtime的文章中经常提到的概念,学习win32的同学们千万不要把这个Pipeline理解成Windows下的匿名或命名管道,它们完全是两个 不同的概念,最初学习时很容易混淆,虽然在IIS内处理请求时与ASP.NET的WORKER进程之间通信等很多地方采用了命名管道,但是他们也不是一回 事。至于讲述ASP.NET中Pipeline详细的文章网上到处都是,这里只是提及而已。

2.IIS的寄宿ASP.NET的基本工作原理

这里提及一下IIS的进程模型,IIS的进程模型从IIS5到IIS6是一个很大的飞跃,主要是IIS6把http协议栈实现在了内核模式中, 那就是http.sys协议型驱动程序。这对于Windows作为Http服务器的性能是一个很大的提升,http.sys完成了连接的管理和请求路由 (根据配置路由给相应的工作进程处理,这个配置可根据IIS的配置动态调整),http.sys与tcp.sys类似都是内核级的协议驱动程 序,http.sys构建在tcpip.sys之上,就像http使用tcp协议承载一样,但是tcpip.sys实现的不仅是tcp/ip协议栈,它实 现了很多协议的处理。在IIS5中使用socket处理http协议,但是socket是在用户态下运行的,所以处理http请求时会有大量的CPU从用 户态和核心态之间的切换过程,这种切换的代价还是很大的,这对于大并发的http请求是不适合的。socket(这里的socket确切的说应该是 win sock)依赖于tcpip.sys完成标准接口功能,而tcpip.sys运行在内核态。从Windows xp sp2和Windows server 2003以后的操作系统中,微软提供了http.sys,http.sys在处理请求时,只有对于有必要投递给WORKER进程的请求,http.sys 才会把请求数据根据动态的路由表路由给相应的进程处理。而且http.sys还完成了以下工作:如果当前要路由的进程没有启动,那么它会缓存住这个请求 (放入请求队列中),直到有工作进程处理这个请求它才会投递。毫不夸张的说IIS在http.sys出现以后才成为真正的Http服务器。

这里插一句:之前有人曾经问过java与.net相互调用有几种方式,之后有人会说使用webservice,socket等。从上文的分析 来看这种说法可能不太合适,说socket不如说tcp或udp等准确。因为从上文我们就可以看出socket只不过是一种标准的编程接口,除此之外并不 代表什么,它的作用只是使编程风格统一化,使用统一的模型使用tcp等协议,实际上在调用时完全可以一端使用socket,而另一端使用ndis模拟协议 实现,但是估计没有人会这么干,因为已经有现成的东西可用了。
也就是说使用底层的驱动程序还有很多机制,例如本文说到的http.sys这也是使用底层协议的很好的例子。以后我可能会详细介绍ndis驱动模型,使用ndis可以模拟各种协议发送数据包。
还要插一句:说一说我们常说的网络端口是什么?例如http常用的80端口,那么80到底是什么啊,看过tcp协议详解的同学会马上注意到端口的这种概 念是出现在传输层的,IP层只涉及到IP地址为止,而传输层协议,例如tcp,udp等都会加入一个端口号,这其实是标准协议里规定的,实际上端口号在操 作系统中只是一种标识,协议驱动程序会根据端口号和更详细的信息进行路由,例如我们说某个应用程序监听了80端口,那么驱动程序就会把协议中具有80端口 的请求路由给这个监听程序。说白了就是一种标识,实现时也没有复杂的数据结构。当然这些概念都已经标准化了。

2.托管的HttpListener类型

HttpListener是一个.net的类型,可以使用它在.net平台下实现一个http的服务器,这个类型有很多成员,这个同学们可以参 考msdn的相关文档,这也不是本文的重点。HttpListener实际上是基于httpapi.dll的,那么httpapi.dll是做什么的呢? 我理解这个dll就是win32下对于http.sys核心态驱动程序功能的一个用户态的封装而已,使用httpapi.dll的导出函数,用户程序可以 从用户态切换到到内核态使用http.sys,这就像kernel32.dll中的很多函数一样,实现了内核态系统服务的调用。那么有了这个用户带的 dll就好办了。而HttpListener正是这个dll在.net平台下的封装,这样.net也可以间接的使用http.sys了。这也就为构 建.net下的http服务器成为可能,因为这些基础性的东西微软已经为我们做好了。无论从理论还是实际上来看IIS6和HttpListener处理请 求的性能是相当的。

3.如何寄宿ASP.NET运行时环境

观察IIS寄宿Asp.net的原理,我们会发现,真正的托管代码是从构建HttpWorkerRequest对象开始的,可以理解为IIS搜集了 http请求的大量信息,之后构造了托管的HttpWorkerRequest对象,这个对象也就是托管和非托管的过度的关键,而后 HttpWorkerRequest再去生成HttpContext对象,而这个HttpContext就是贯穿于Asp.net的pipeline模型 始终的上下文对象。在.net进程中如何寄宿Asp.net的运行时呢?我们可以使用微软专门为寄宿Asp.net运行时而提供的的 ApplicationHost类型(位于System.Web.Hosting)。System.Web.Hosting这个名称空间中有很多类型来处 理其他种进程寄宿Asp.net运行时。代码类似:

代码

internal AspxNetEngine(String virtualAlias, String physicalDir)
{
m_VirtualAlias
= virtualAlias;
m_PhysicalDirectory
= physicalDir;

Console.WriteLine(Creating a new AspxEngine.);

//m_ExecutingEngine will be the actual object that the hosting API created for
//us and so to execute a page in the Application we will call this object to
//process requests
m_ExecutingAppDomain = (AspxNetEngine)ApplicationHost.CreateApplicationHost(typeof(AspxNetEngine), m_VirtualAlias,m_PhysicalDirectory);

Console.WriteLine(New AspxEngine created for alias + m_VirtualAlias);
}

ApplicationHost.CreateApplicationHost方法会创建一个新的应用程序域,并AspxNetEngine作为应用程序域通信的对象,所以AspxNetEngine必须是MarshalByRefObject。

基于以上资料我们就完全可以使用托管代码实现一个简单寄宿Asp.net的运行时的环境,这里的说的“简单”主要是比起IIS还有一些性能问题 和其他方面的问题,下文将详细说明遇到的相关问题。其实这种实现网上有很多种,例如classin或vs自带的开发服务器,这种东西应用到实际的生产环境 还是有问题的,IIS我们轻易不能放弃。

[转载]Asp.net MVC2中你必须知道的扩展点(二):Model Binder

mikel阅读(921)

[转载]Asp.net MVC2中你必须知道的扩展点(二):Model Binder – 海纳百川 – 博客园.

Model Binder在ASP.NET MVC中非常简单。简单的说是你控制器中的Action方法需要参数数据;而这些参数数据包含在HTTP请求中,这些数据包括Post上来的表单上的值和 URL中的数据等。而DefaultModelBinder就是将这些个表单上的值和路由值换成对象,然后将这些对象绑定到Action的参数上面。我简 单的画了一个图,看起来会更加直观。

iii

ASP.NET mvc中你可以写类似下面这样的代码:

[HttpPost]
public ActionResult Create()
{
    Book book = new Book();
    book.Title = Request.Form["Title"];
    // ...
    return View();
}

这样的写法是非常不可取的,代码不容易阅读,也不易测试。再看下面的写法:

[HttpPost]
public ActionResult Create(FormCollection values)
{
    Book book = new Book();
    book.Title = values["Sex"];
    // ...
    return View();
}

这样的写法就可以不从Request中获取数据了,这样能满足一些情况,比直接从Request中获取数据要好。但是如果在Action需要的数据既要来自表单上的值,又要来自URL的query string。

这种情况单单FormCollection是不行的。看下面代码:

[HttpPost]
public ActionResult Create(Book book)
{
    // ...
    return View();
}

上面的代码就非常的直观了,这需要我们的model binder创建一个ping对象,然后直接从这个对象的属性中取值。自然这个book对象的数据也是来自Form和URL。

有时候,我们的DefaultModelBinder转换的能力必经有限,也不够透明化,一些特殊和复杂的情况就需要我们自定义Model Binder。

下面我讲讲如何去自定义Model Binder。

1、首先我们定义一个Book的实体类:

public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public DateTime DatePublished { get; set; }
}

2、自定义的model binder需要继承IModelBinder或者它的子类。数据可以从bindingContext获取。

public class BookModelBinder : IModelBinder
{

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var book = (Book)(bindingContext.Model ?? new Book());
        book.Title = GetValue<string>(bindingContext, "Title");
        book.Author = GetValue<string>(bindingContext, "Author");
        book.DatePublished = GetValue<DateTime>(bindingContext, "DatePublished");
        if (String.IsNullOrEmpty(book.Title))
        {
            bindingContext.ModelState.AddModelError("Title", "书é名?不?能ü为a空?");
        }

        return book;
    }


    private T GetValue<T>(ModelBindingContext bindingContext, string key)
    {
        ValueProviderResult valueResult= bindingContext.ValueProvider.GetValue(key);
        bindingContext.ModelState.SetModelValue(key, valueResult);
        return (T)valueResult.ConvertTo(typeof(T));
    }
}
从上面代码可以看出,自定义的ModelBinde非常的自由,可以自由的将Form上的一个key对应实体的一个属性,
也可以加入一些验证的逻辑。当然还可以加入一些其他的自定义逻辑。
3、写好BookModelBinder之后,我们只需要简单的注册一下就行了,在Global.asax添加下面代码:
ModelBinders.Binders.Add(typeof(Book), new BookModelBinder());
总结:本文简单介绍了一下Asp.net MVC的Model Binder机制。如果叙述有问题,欢迎指正。

[转载]彻底学通string.Format以及IFormattable,IFormatProvider,ICustomFormatter

mikel阅读(1069)

[转载]彻底学通string.Format以及IFormattable,IFormatProvider,ICustomFormatter – 喆 喆 – 博客园.

自从使用.net以来就一直都在使用string.Format方法,一直没有空或者其他原因都没有深入去了解,主要还是因为项目上似乎没有这 么高的要求,也没必要去深入了解,就算碰到了自定义的格式化内容也是写几个通用的方法而已。今天空下来仔细去理解了一下,在这里和大家分享一下,也希望大 家一起交流。

string.Format方法是string类提供的静态方法,一般最多使用的是其两个参数的重载,例如:

var name = Zhezhe;
var msg
= string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}., name, DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg);

后面一个参数是.net语法简写的可变参数,在.net内部实际是数组而已,实质还是两个参数的方法重载。

你也可以不使用这种方法,将字符串相加即可

var msg1 = Hello Cnblogs, I am + name + ,Today is + DateTime.Now.ToString(yyyy-MM-dd) + + DateTime.Now.DayOfWeek + .;

上面两种方法的结果是一样的。

之前普遍使用第一种方法的原因是相比string的多个加号相加在性能上有一定优势,因为其内部是使用StringBuilder类的,还有一个原因是代码的可读性比起+这样的方式更好一些。

分析一下第一种方法的实现原理:

1.Format方法的内部解析方式和原理

Format方法在取到第一个参数Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.之后便将其分解成多个部分

① Hello Cnblogs, I am “{0}”,Today is ““{1:yyyy-MM-dd}” {2}”.”

分解的原则是按照{}配对的数量进行的,{}是微软定义好的标记而已,你自己也可以去实现个用 []表示都无所谓。既然{}已经被定义为了特殊的标记,所以如果是自己需要在字符串中包含大括号的话就必须进行转义,这个转义也和我们平时使用的”/”转 义表示法不同,需要使用两个大括号进行转义如 {{ 或者 }}。 如:

var msg2 = string.Format(Hello {{}},I am {0}, name);

将{}分解出来之后根据中间的序号来对应第二个参数,如果第二个参数的实际个数小于需要的数量,则会出现运行错误(编译时不会报错), 如果参数个数大于序号的数量,则其后的忽略不计。

参数个数小于序号的实际数量,错误

var msg4 = string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}., name, DateTime.Now);

参数个数大于序号的实际数量,多出的参数忽略不计

var msg4 = string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd}., name, DateTime.Now,DateTime.Now.DayOfWeek);

序号的顺序不一定必须是0,1,2,3,4可以任意排列,但是序号永远和第二个参数(实质是数组)的索引一致。

var msg4 = string.Format(Hello Cnblogs, I am {2},Today is {0:yyyy-MM-dd} {1}., DateTime.Now, DateTime.Now.DayOfWeek, name);

序号还能跳跃,但是中间跳跃过的序号参数里必须有

var msg5 = string.Format(Hello Cnblogs, I am {0},Today is {2:yyyy-MM-dd} {3}., name, test, DateTime.Now, DateTime.Now.DayOfWeek);

上面讲了一下用法,接下来继续

分解完毕之后使用 StringBuilder的Append方法将各个部分添加进去,最后再用ToString方法转成string,其实现原理非常类似于下面的代码

var s = new StringBuilder();
s.Append(
Hello Cnblogs, I am );
s.Append(name);
s.Append(
,Today is );
s.Append(DateTime.Now.ToString(
yyyy-MM-dd));
s.Append(
);
s.Append(DateTime.Now.DayOfWeek);
s.Append(
.);
var msg3
= s.ToString();

顺便解释一下string和StringBuilder:string虽然也是引用类型,但是该类型.net内部进行了特殊处理,让其表现出和值类 型相似的特征,特别是在每次变动之后就会重新分配内存空间,而StringBuilder就不会,所以如果有很多个字符串相加拼接,则string性能较 低。

在用 Append方法进行添加的时候会有两种情况:

一种是{0},{1}这样的不带有特殊格式化的则直接会调用该对象的ToString方法,比如上面的 s.Append(DateTime.Now.DayOfWeek);其实就是 s.Append(DateTime.Now.DayOfWeek.ToString());在.net中,如果是自己定义的类,并且没有重写ToString方法,则会输出类的全名,下面会详细讨论。

另一种是{0:yyyy-MM-dd}带有特殊格式化的则继续分解,将冒号后面的内容分解出来,并且在调用ToString时作为参数传入,上面的s.Append(DateTime.Now.ToString(yyyy-MM-dd));就体现了这一点。所以这些其实都没什么奥妙可言,冒号也是一个预定义好的标记而已,如果微软让你去实现这个,你也可以用其他符号。

2.ToString方法的深入理解

通过第一步的分析如果纯粹从分析Format这个方法来说已经足够了,大括号的特殊标记作用以及和后面参数的对应关系也已经解释清楚了。但是这里还是需要深入了解一下ToString方法。

上面1中提到如果一个自己定义的类不去重写ToString方法的话则会 输出类的全名,例如

public class Person
{
public string Name { get; set; }
}

如果写如下代码

var msg6 = string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.,
new Person() {Name = Zhezhe}, DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg6);

则会输出:

这里再次强调一下,如果某个对象需要转换成ToString,并且没有手动调用该方法,程序会自动调用该方法,上面的new Person() {Name = Zhezhe}没有手工调用,程序会自动调用方法(new Person() {Name = Zhezhe}).ToString(); 这个是微软让你少些代码而已,好的习惯是始终写上 .ToString();

.net中的任何对象都具有该方法,因为该方法在object对象中定义,任何类或者结构都会继承object,所以不用担心一个对象没有ToString方法。

接下来定义带有ToString重载方法的类

public class PersonWithToString
{
public string Name { get; set; }

public override string ToString()
{
return Name;
}
}

编写如下代码:

//使用自己定义类,但是重写了ToString方法
var msg7 = string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}., new PersonWithToString(){ Name = Zhezhe }, DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg7);

输入结果为 输出就正常了,自己重写的方法起作用了。

总结:对自己定义的类始终重写 ToString方法。 这样在 string.Format 中或者其他需要程序自动转换成string类型时不会出现 输出类全名的情况。

3.ToString带有自定义格式化参数的理解

上面讲到的ToString都是不带格式化参数的,像 {1:yyyy-MM-dd} 这样的情况是没法处理的,也许有人会说像 DateTime.Now.ToString(yyyy-MM-dd) 这样的情况自己去重载一个ToString方法就可以了,果真如此吗? 下面就测试一下

public class PersonWithToString
{
public string Name { get; set; }

public override string ToString()
{
return Name;
}

public string ToString(string format)
{
switch (format)
{
case UPP:
return Name.ToUpper();
case LOW:
return Name.ToLower();
default:
return Name;
}
}
}

var msg9 = string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.,
new PersonWithToString() { Name = Zhezhe }.ToString(UPP), DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg9);

msg9的实际输出为 Hello Cnblogs, I am ZHEZHE,Today is 2010-07-30 Friday.  这个正是我们需要的,当然,这个肯定是对的,要不然就是.net的bug了

接下来再看看下面的

var msg8 = string.Format(Hello Cnblogs, I am {0:UPP},Today is {1:yyyy-MM-dd} {2}.,
new PersonWithToString() { Name = Zhezhe }, DateTime.Now, DateTime.Now.DayOfWeek);

实际输出是: Hello Cnblogs, I am Zhezhe,Today is 2010-07-30 Friday.  并不是我们所期望的。实际上上面的代码是调用了PersonWithToString类的不带参数的ToString()方法。言外之意就是  {0:UPP}这样的格式实际上内部处理的是和 {0}

一样的效果了。在1中提到了分解的原理用了类似两个字,实际情况并不是这么简单。

{0:UPP} 真正调用的方法签名是    string ToString(string format,IFormatProvider formatProvider)

而且也不是直接调用该对象的此方法。而是通过 IFormattable 接口实现的方法

现在定义实现了该接口的 Person2类

Person2

public class Person2 : IFormattable
{
public string Name { get; set; }

public override string ToString()
{
return Name;
}

#region IFormattable Members

public string ToString(string format, IFormatProvider formatProvider)
{
if (string.IsNullOrEmpty(format))
return ToString();

switch (format)
{
case UPP:
return Name.ToUpper();
case LOW:
return Name.ToLower();
default:
return Name;
}
}

#endregion
}

运行一下代码得到预期的结果

//使用实现了IFormattable接口的Person2对象
var msg10 = string.Format(Hello Cnblogs, I am {0:UPP},Today is {1:yyyy-MM-dd} {2}.,
new Person2() { Name = ZhezheToUpper }, DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg10);

ZhezheToUpper已经输出成全部大写形式了。

既然{0:UPP}会调用接口定义的ToString方法,那么{0}呢? 如果该类没有实现IFormattable接口,上面已经说了,会调用重载的或者是基类的ToString()方法。但是如果该类已经实现了IFormattable接口,那么{0}也不会去调用重载的或者是基类的ToString()方法了,它始终是去调用 接口定义的 ToString方法。下面具体印证一下

Person3

public class Person3 : IFormattable
{
public string Name { get; set; }

public override string ToString()
{
return Name;
}

#region IFormattable Members

public string ToString(string format, IFormatProvider formatProvider)
{
if (string.IsNullOrEmpty(format))
return Name + IFormattable Method;

switch (format)
{
case UPP:
return Name.ToUpper();
case LOW:
return Name.ToLower();
default:
return Name + IFormattable Method;
}
}

#endregion
}

运行下面的测试代码

var msg11 = string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.,
new Person3() { Name = ZhezheToUpper }, DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg11);

输出为: Hello Cnblogs, I am ZhezheToUpper IFormattable Method,Today is 2010-07-30 Friday.

证明了确实是调用了接口定义的方法,而不是重载的ToString方法,否则是输出ZhezheToUpper

再来看一下Person2中实现的ToString方法,

if (string.IsNullOrEmpty(format))
return ToString();
如果是刚才的{0}不带格式化参数的调用,则format参数传过来的是null值,这里需要自己判断,如果是null值,一般情况下是手工去调用重载的ToString()方法。
所以Person2的做法是好的,而Person3中的做法是不好的,Person3只是为了测试分辨出调用的是哪个方法才这么设计的。

总结:一.对于实现IFormattable 接口时,如果format参数为null(即不带格式化参数的情况,如{0})则应该调用重载的 ToString()方法,而不应该自己去另外写代码。

二.如果找不到相应的格式化参数,例如{0:AAA},在Person2的switch中并无匹配的AAA,这种情况一般也应该去调用重载的 ToString()方法。

否则就会出现

//以下两个输出结果不一样,是不合理的
var msg12 = string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.,new Person3() { Name = ZhezheToUpper }, DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg12);

var msg13 = string.Format(Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.,new Person3() { Name = ZhezheToUpper }.ToString(), DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg13);

不同的结果的情况

上面的输出结果不同:

这是不好的设计

4.继续了解 IFormatProvider 和 ICustomFormatter 接口

到这里为止,应该说灵活应用string.Format()已经没什么多大的问题了,但是也还是存在一些问题,比如我们必须得为每个类单独去实现IFormattable接口才能实现自定义的格式化参数。在一些场后还是觉得不太方便或者说代码冗余。

.net的string.Format静态方法还提供了重载方法,具体签名如下:public static string Format(IFormatProvider provider,string format,params Object[] args)

这个方法比起原来使用的方法最前面增加了 IFormatProvider类型参数。使用此方法的优点是不需要为后面的参数对象实现 IFormattable 接口就可以使用自定义的格式化参数。既然这样的话也就解决了第4部分开头提到的问题了。

还是用例子说话吧

下面是正方形类

Square类

public class Square
{
public string Name { get; set; }

/// <summary>
/// 边长
/// </summary>
public double Side { get; set; }

public override string ToString()
{
return string.Format({0}(Side:{1}),Name, Side);
}
}

下面是长方形类

Rectangle类

public class Rectangle
{
public string Name { get; set; }

/// <summary>
///
/// </summary>
public double Width { get; set; }

/// <summary>
///
/// </summary>
public double Height { get; set; }

public override string ToString()
{
return string.Format({0}(Width:{1},Height:{2}),Name, Width, Height);
}
}

两个类都重写了ToString方法

定义MyHelloFormatProvider类,该类从名称上就可以看出是格式化的提供者

public class MyHelloFormatProvider : IFormatProvider
{
#region IFormatProvider Members

public object GetFormat(Type formatType)
{
return new MyHelloFormatter();
}

#endregion
}

该类实现了 IFormatProvider 接口,接口只有一个唯一的方法需要实现,GetFormat返回的是真正进行格式化操作的类,这里很像是工厂模式。

返回 MyHelloFormatter 对象之后,在MyHelloFormatter 中具体进行格式化操作。

public class MyHelloFormatter : ICustomFormatter
{
#region ICustomFormatter Members

public string Format(string format, object arg, IFormatProvider formatProvider)
{
var t
= Hello ;
switch (format)
{
case UPP:
t
= t.ToUpper();
break;
case LOW:
t
= t.ToLower();
break;
default:
break;
}

return t + arg.ToString();
}

#endregion
}

MyHelloFormatter 实现了ICustomFormatter接口,该接口也只有一个唯一的方法,即实际执行格式化的方法

如果不使用格式化参数或者格式化参数不匹配,情况会怎么样?

代码

var msg15 = string.Format(new MyHelloFormatProvider(), {0}  {1}, new Rectangle() { Name = MyRectangle, Width = 14.3, Height = 10 }, new Square() { Name = MySquare, Side = 24.2 });
Console.WriteLine(msg15);

var msg16 = string.Format(new MyHelloFormatProvider(), {0}  {1}, new Rectangle() { Name = MyRectangle, Width = 14.3, Height = 10 }.ToString(), new Square() { Name = MySquare, Side = 24.2 }.ToString());
Console.WriteLine(msg16);

var msg17 = string.Format(new MyHelloFormatProvider(), {0:AAA}  {1:BBB}, new Rectangle() { Name = MyRectangle, Width = 14.3, Height = 10 }, new Square() { Name = MySquare, Side = 24.2 });
Console.WriteLine(msg17);

以上输出都是一样的: Hello MyRectangle(Width:14.3,Height:10)  Hello MySquare(Side:24.2)

上面的运行结果表明,如果提供了new MyHelloFormatProvider() ,那么执行过程过是: 根据MyHelloFormatProvider 对象得到 MyHelloFormatter 对象,利用MyHelloFormatter 对象的Format方法进行格式化

这里还有一个问题,如果 MyHelloFormatProvider 的 GetFormat返回的不是一个实现了 ICustomFormatter 接口的对象又会是什么情况呢?

答案是会报异常。 那么如果返回的是 null 呢? 答案是直接调用了对象的ToString()方法了。如果返回null,则运行结果如下:

MyRectangle(Width:14.3,Height:10)  MySquare(Side:24.2)

带上格式化参数的运行结果

var msg18 = string.Format(new MyHelloFormatProvider(), {0:UPP}  {1:LOW}, new Rectangle() { Name = MyRectangle, Width = 14.3, Height = 10 }, new Square() { Name = MySquare, Side = 24.2 });
Console.WriteLine(msg18);

HELLO MyRectangle(Width:14.3,Height:10)  hello MySquare(Side:24.2)
通过上面的例子我们知道如果我们需要定义一种通用的格式化方式的 话,不需要让类实现 IFormattable 接口,可以通过定义实现 IFormatProvider,ICustomFormatter接口的类去做,上面的无论是正方形还是长方形类都需要在前面加上 Hello 进行格式化,可以是普通的,小写的,大写的等等,不需要两个类单独去实现了,就选以后增加了圆形,三角形等等,也都能用我们已经定义好的 MyHelloFormatProvider 和 MyHelloFormatter  去进行格式化。

使用这种方式还能解决另外一个问题,假如我们已经为圆形类实现了 IFormattable  接口,并且已经实现了{0:UPP}格式化参数,但是实现的方法中没有加{0:LOW}格式化参数,而且这个类我们又不能更改(可能是.net自带的类, 可能是第三方dll提供的类等等),那该怎么办呢? 显然已经不可能靠IFormattable  接口来解决了

使用这节讲的方法就可以实现我们要求了。以下是具体实现

圆形类

public class Circle : IFormattable
{
public string Name { get; set; }

/// <summary>
/// 半径
/// </summary>
public double Radius { get; set; }

public override string ToString()
{
return string.Format({0}(Radius:{1}), Name, Radius);
}

#region IFormattable Members

public string ToString(string format, IFormatProvider formatProvider)
{
if (string.IsNullOrEmpty(format))
return ToString();

var t = Hello ;
switch (format)
{
case UPP:
t
= t.ToUpper();
break;
default:
break;
}

return t + Name;
}

#endregion
}

该类可以实现UPP格式化参数的格式化。

var msg19 = string.Format(Test: {0}, new Circle() { Name = MyCircle, Radius = 10 });
Console.WriteLine(msg19);

var msg20 = string.Format(Test: {0:UPP}, new Circle() {Name = MyCircle, Radius = 10});
Console.WriteLine(msg20);

运行上面的代码得到:

Test: MyCircle(Radius:10)
Test: HELLO MyCircle

第一个无格式化参数,实际调用ToString()方法得到,由代码 if (string.IsNullOrEmpty(format))决定

第二个带UPP格式化参数,也得到了预期的结果。

现在需要实现LOW的格式化参数

var msg21 = string.Format(new MyHelloFormatProvider(),Test: {0:LOW}, new Circle() { Name = MyCircle, Radius = 10 });
Console.WriteLine(msg21);

在不修改Circle类并且不重新定义其他类的情况下就可以达到我们的要求了

显示结果如下: Test: hello MyCircle(Radius:10)      hello已经是全部小写了。

写了这么多,感觉有些乱七八糟了,发现还有很多没有提到,很多都讲重复了。本人也难得写博客,文字水平表达能力欠佳,还望阅者理解。

以下是全部代码

/Files/szp1118/ConsoleApplication3.rar

[转载]ASP.NET MVC: 使用自定义 ModelBinder 过滤敏感信息

mikel阅读(1044)

[转载]ASP.NET MVC: 使用自定义 ModelBinder 过滤敏感信息 – 鹤冲天 – 博客园.

昨天发表了一篇随笔《ASP.NET MVC: 使用 Filters 附加过滤敏感信息功能》(以下简称《Filter过滤》),今天一早醒来发现一处重大漏洞,于是在发了一条评论指出存在问题,并希望有朋友能指正。可到现在也没见有朋友找出问题,索引再发一篇随笔,进行更正。

存在的漏洞

《Filter过滤》一文中使用的代码如下:

1 public class SensitiveWordsFilterAttribute: ActionFilterAttribute
2 {
3 public override void OnActionExecuting(ActionExecutingContext filterContext)
4 {
5 var parameters = filterContext.ActionDescriptor.GetParameters();
6 foreach (var parameter in parameters)
7 {
8 if (parameter.ParameterType == typeof(string))
9 {
10 //获取字符串参数原值
11 var orginalValue = filterContext.ActionParameters[parameter.ParameterName] as string;
12 //使用过滤算法处理字符串
13 var filteredValue = SensitiveWordsFilter.Instance.Filter(orginalValue);
14 //将处理后值赋给参数
15 filterContext.ActionParameters[parameter.ParameterName] = filteredValue;
16 }
17 }
18 }
19 }

问题在第8行,SensitiveWordsFilterAttribute 目前只能对字符串类型这样的简单数据进行过滤,没有考虑到复杂类型,对下面代码就则无法实现过滤:

1 public class ArticlesController : Controller
2 {
3 [HttpPost]
4 public ActionResult Create(Article article)
5 {
6 //
7 }
8 //
9 }

Article是一个自定义类:

1 public class Article: DomainModel
2 {
3 public int ID { get; set; }
4 public string Title { get; set; }
5 public string Content { get; set; }
6 public DateTime CreationTime { get; set; }
7 }

SensitiveWordsFilterAttribute 对自定义类型是不太好处理的,当然也有解决办法,如使用反射遍历访问Article的每一个字符串属性,进行过滤。

本文采用 ASP.NET MVC Model Binding 来实现目标。

ASP.NET MVC Model Binding

ASP.NET MVC中,我们可以使用下面的方式来接收用户提交的数据:

1 public class ArticlesController : Controller
2 {
3 public ActionResult Query(string title, DateTime? creationDate)
4 {
5 //
6 }
7 [HttpPost]
8 public ActionResult Create(Article article)
9 {
10 //
11 }
12 }

这得益于 ASP.NET MVC ModelBinding 特性。使用这个特性,我们无需再使用 Request.Form[“title”] 从 HTTP 请求中获取数据,也不需要再使用 DateTime.Parse() 或 DateTime.TryParse() 方法进行类型转换,也不再需要对实例的每一个属性逐一赋值。ModelBinding 特性将我们从这些无聊的低级工作中解放了出来,让我们专心去做高级的工作。

如上面代码所示,ModelBinding 既可以绑定简单数据类型,也可以绑定自定义类型,还可以绑定数组、集合、字典、二进制数据(byte[]),甚至还能用来接收文件上传。

ModelBinding 在绑定自定义类型时,还能够有选择的绑定指定属性或排除指定属性:

1 public ActionResult Create([Bind(Include = Name, Sex, Birthday)] Person person)
2 {
3 //
4 }
5 public ActionResult Create([Bind(Exclude = ID)] Person person)
6 {
7 //
8 }

ASP.NET MVC ModelBinding 的功能相当完善,实现也比较复杂,具体是由 DefaultModelBinder 类来完成的。对于一些特定功能(如本文中的敏感信息过滤)可通过扩展 DefaultModelBinder 类的功能来实现。

具有敏感信息过滤功能 ModelBinder

在面向对象的世界中,扩展功能最简单的方式就是继承。我们新建一个类 SensitiveWordsFilterModelBinder,继承自 DefaultModelBinder,重写 SetPropertyt 和 BindModel 方法,如下:

1 public class SensitiveWordsFilterModelBinder : DefaultModelBinder
2 {
3 protected override void SetProperty(ControllerContext controllerContext,
4 ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
5 {
6 if (propertyDescriptor.PropertyType == typeof(string)/* && value is string*/)
7 value = SensitiveWordsFilter.Instance.Filter(value as string);
8 base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
9 }
10 public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
11 {
12 var value = base.BindModel(controllerContext, bindingContext);
13 if(bindingContext.ModelType == typeof(string)/* && value is string*/)
14 value = SensitiveWordsFilter.Instance.Filter(value as string);
15 return value;
16 }
17 }

重写 SetProperty 方法(3~9行)用来对自定义类型进行敏感信息过滤:对自定义类型来说,数据绑定主要针对属性,所有属性绑定完成,自定义类型的实例也就绑定完成了。重写 SetProperty 就是在给 string 类型的属性赋值前,增加敏感信息过滤这一步操作。相对使用 SensitiveWordsFilterAttribute + 反射 的方式处理自定义类型,SensitiveWordsFilterModelBinder 更加合理,效率也要高些。重写 SetProperty 时,可以通过 bindingContext.ModelType.Name 获取所在类的名字,也可以通过 propertyDescriptor.Name 获取属性的名字,通过这两个名字,我们可以有选择的进行过滤操作,以提高效率。

重写 BindModel 方法(10~16行)用来对 string 类型进行敏感信息过滤,string 是简单数据库类型,对它进行绑定时不会调用 SetProperty 方法,因此要单独进行处理。

仅仅有这个 ModelBinder 是不够的,MVC不知道什么情况下使用它,因此我们还要进行配置,使 SensitiveWordsFilterModelBinder 生效。

配置使用 SensitiveWordsFilterModelBinder

方式一:直接在参数上使用
1 public ActionResult Create([ModelBinder(typeof(SensitiveWordsFilterModelBinder))]Article article)
2 {
3 return null;
4 }
5 public ActionResult Create([ModelBinder(typeof(SensitiveWordsFilterModelBinder))]string title,
6 [ModelBinder(typeof(SensitiveWordsFilterModelBinder))]string content, DateTime? creationTime)
7 {
8 return null;
9 }

使用这种方式比较“ugly”,尤其是 ModelBinder 名字比较长的时候。这种方式的最大缺点是需要对每个参数进行标记。

方式二:标记在 Model 上
1 [ModelBinder(typeof(SensitiveWordsFilterModelBinder))]
2 public class Article: DomainModel
3 {
4 //
5 }

这种比较不错,只需在 Article 类上标记一次,所有 Controller 中的 Action 的 Article 类型的参数都将使用 SensitiveWordsFilterModelBinder。

但这种方式仅对自定义类型有效,对系统定义的类型,如:string,我们是很难给它加上 Attribute 的。

方式三:在 Global.asax.cs 文件中处理:
1 protected void Application_Start()
2 {
3 ModelBinders.Binders.Add(typeof(Article), new SensitiveWordsFilterModelBinder());
4 ModelBinders.Binders.Add(typeof(string), new SensitiveWordsFilterModelBinder());
5 }

如上面代码,我们可为每一种类型,设置一个 ModerBinder。也可以像下面这样进行批量设置:

1 protected void Application_Start()
2 {
3 var sensitiveWordsFilterModelBinder = new SensitiveWordsFilterModelBinder();
4 var types = typeof(Article).Assembly.GetTypes().Where(t=>t.IsSubclassOf(typeof(DomainModel)));
5 foreach (var type in types)
6 ModelBinders.Binders.Add(type, sensitiveWordsFilterModelBinder);
7 }

这个就不多解释了。

使用 SensitiveWordsFilterModelBinder 方式的缺点

有些 MVC 的初学者之前大多是做 ASP.NET,可能有时会使用 Request.Form[“…”] 这种方式来接收数据,使用这种方式的地方都会成为系统的漏洞。

总结

ASP.Net MVC 采用了非常先进的设计思想,具有良好的可扩展性,可以通过各种方式轻松解决我们遇到的各种问题。

[转载]Spring.NET实用技巧4——NHibernate分布式事务(下)

mikel阅读(1145)

[转载]Spring.NET实用技巧4——NHibernate分布式事务(下) – 刘冬的博客 – 博客园.

上篇, 我们已实现了在同一应用程序下的分布式事务——即多Dao层+同Service层,每个Dao对应一个数据库,一个Service调用多个Dao。但是在 一些特定的子系统较多的项目中,开发人员是无法访问到某个子系统的数据库,这就意味着不能通过增加Dao层来实现分布式事务。正如一个银行的软件系统,记 录了客户的账户信息和存款金额,北京的分公司和上海的分公司分别有自己的数据库和软件系统。现在,要实现北京的系统向上海的系统转账,然而各自作为开发人 员来说,没有足够的权限去访问对方的数据库,但是可以提供Web Service的方式去访问其系统服务。这样,我们就需要实现基于Web Service的分布式事务。

实现基于Web Service的分布式事务的方法比较多,可以通过.NET企业服务的方式。但是为了更好的实现,我们选择WCF作为一个分布式应用程序框架。WCF在实现分布式事务中有它的优越之处。其思路在于启动MSDTC服务,将客户端的事务以流的方式传递到服务器端,在服务器端执行通过时,客户端再提交事务,相反则回滚事务。

我们模仿上篇的场景做一个demo,并使用上篇的Dao和Domain。

一、启动MSDTC服务。

二、Service层

①.Customer

CustomerManager

public interface ICustomerManager
{
CustomerInfo Get(
object id);

object Save(CustomerInfo entity);

void Update(CustomerInfo entity);
}

public class CustomerManager : ICustomerManager
{
private ICustomerDao Dao { get; set; }

public CustomerInfo Get(object id)
{
return Dao.Get(id);
}

public object Save(CustomerInfo entity)
{
return Dao.Save(entity);
}

public void Update(CustomerInfo entity)
{
if (entity.Money > 3000)
{
throw new Exception(订金上限);
}
Dao.Update(entity);
}
}

Service.xml

<?xml version=”1.0″ encoding=”utf-8″ ?>
<objects xmlns=”http://www.springframework.net”>

<object id=”transactionManager”
type
=”Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21″>
<property name=”DbProvider” ref=”DbProvider”/>
<property name=”SessionFactory” ref=”NHibernateSessionFactory”/>
</object>

<object id=”transactionInterceptor” type=”Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data”>
<property name=”TransactionManager” ref=”transactionManager”/>
<property name=”TransactionAttributeSource”>
<object type=”Spring.Transaction.Interceptor.AttributesTransactionAttributeSource, Spring.Data”/>
</property>
</object>

<object id=”BaseTransactionManager” type=”Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data” abstract=”true”>
<property name=”PlatformTransactionManager” ref=”transactionManager”/>
<property name=”TransactionAttributes”>
<name-values>
<add key=”*” value=”PROPAGATION_REQUIRED”/>
</name-values>
</property>
</object>

<object id=”Customer.CustomerManager” parent=”BaseTransactionManager”>
<property name=”Target”>
<object type=”Customer.Service.Implement.CustomerManager, Customer.Service”>
<property name=”Dao” ref=”Customer.CustomerDao”/>
</object>
</property>
</object>

</objects>

②.Order

OrderManager

public interface IOrderManager
{
object Save(OrderInfo entity);
}

public class OrderManager : IOrderManager
{
public IOrderDao Dao { get; set; }

public object Save(OrderInfo entity)
{
return Dao.Save(entity);
}
}

Service.xml

<?xml version=”1.0″ encoding=”utf-8″ ?>
<objects xmlns=”http://www.springframework.net”>

<object id=”transactionManager”
type
=”Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21″>
<property name=”DbProvider” ref=”DbProvider”/>
<property name=”SessionFactory” ref=”NHibernateSessionFactory”/>
</object>

<object id=”transactionInterceptor” type=”Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data”>
<property name=”TransactionManager” ref=”transactionManager”/>
<property name=”TransactionAttributeSource”>
<object type=”Spring.Transaction.Interceptor.AttributesTransactionAttributeSource, Spring.Data”/>
</property>
</object>

<object id=”BaseTransactionManager” type=”Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data” abstract=”true”>
<property name=”PlatformTransactionManager” ref=”transactionManager”/>
<property name=”TransactionAttributes”>
<name-values>
<add key=”*” value=”PROPAGATION_REQUIRED”/>
</name-values>
</property>
</object>

<object id=”Order.OrderManager” parent=”BaseTransactionManager”>
<property name=”Target”>
<object type=”Order.Service.Implement.OrderManager, Order.Service”>
<property name=”Dao” ref=”Order.OrderDao”/>
</object>
</property>
</object>

</objects>

三、服务契约和Host。

1、契约

作为服务契约,需要启用Session,并且设置TransactionFlowOption的等级为Allowed或Mandatory来接收客户端事务流。

作为契约的实现部分,需要设置TransactionScopeRequired为true来启用事务作用域。

①.Customer

CustomerContract

[ServiceContract(SessionMode = SessionMode.Required)]
public interface ICustomerContract
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
CustomerInfo Get(
object id);

[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
object Save(CustomerInfo entity);

[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
void Update(CustomerInfo entity);
}

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class CustomerServer : ICustomerContract
{
public ICustomerManager Manager { get; set; }

[OperationBehavior(TransactionScopeRequired = true)]
public CustomerInfo Get(object id)
{
return Manager.Get(id);
}

[OperationBehavior(TransactionScopeRequired = true)]
public object Save(CustomerInfo entity)
{

return Manager.Save(entity);
}

[OperationBehavior(TransactionScopeRequired = true)]
public void Update(CustomerInfo entity)
{
Manager.Update(entity);
}

②.Order

IOrderContract

[ServiceContract(SessionMode = SessionMode.Required)]
public interface IOrderContract
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
object Save(OrderInfo entity);
}

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class OrderServer : IOrderContract
{
public IOrderManager Manager { get; set; }

[OperationBehavior(TransactionScopeRequired = true)]
public object Save(OrderInfo entity)
{
return Manager.Save(entity);
}
}

2、配置

然而,Spring.NET针对NHibernate的 Session管理使用的是OSIV模式(Open Session In View),即使用httpModule去拦截HTTP请求,在每次请求开始时打开Session作用域(SessionScope),最后在请求结束后 关闭SessionScope。这样一来,在客户端每请求一次时都会打开SessionScope,在请求结束会关闭SessionScope,然后当请 求结束后再去处理分布式就会提示“无法使用已释放对象”的错误。所以说,OSIV是无法正常管理分布式事务的。出于上述原因,我们决定在 Global.asax的配置,在Session(这里的Session是ASP.NET中的Session)启动时候打开SessionScope,在 Session结束时关闭SessionScope。这样分布式事务就会与SessionScope同步了。

最后,在配置appSettings节点增加
<add key=”Spring.Data.NHibernate.Support.SessionScope.SessionFactoryObjectName” value=”NHibernateSessionFactory”/>

另外配置WCF的binding时需要选择一种支持Session的binding(如wsHttpBinding)并且将binding中的transactionFlow属性设置为true。

Global.asax

public class Global : System.Web.HttpApplication
{

protected void Application_Start(object sender, EventArgs e)
{
log4net.Config.XmlConfigurator.Configure();
}

protected void Session_Start(object sender, EventArgs e)
{
SessionScope sessionScope
= new SessionScope(appSettings, typeof(SessionScope), false);
sessionScope.Open();
HttpContext.Current.Session[
SessionScope] = sessionScope;
}

protected void Session_End(object sender, EventArgs e)
{
SessionScope sessionScope
= HttpContext.Current.Session[SessionScope] as SessionScope;
if (sessionScope != null)
{
sessionScope.Close();
}
}

}

①.Customer

Web.config

<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>

…………..
<!–spring配置–>
<spring xmlns=”http://www.springframework.net”>
<parsers>
<parser type=”Spring.Data.Config.DatabaseNamespaceParser, Spring.Data” />
<parser type=”Spring.Transaction.Config.TxNamespaceParser, Spring.Data” />
</parsers>
<context>
<resource uri=”config://spring/objects” />

<!–Dao–>
<resource uri=”assembly://Customer.Dao/Customer.Dao.Config/Dao.xml” />
<!–Service–>
<resource uri=”assembly://Customer.Service/Customer.Service.Config/Service.xml” />

</context>
<objects xmlns=”http://www.springframework.net”
xmlns:aop
=”http://www.springframework.net/aop”>

<object id=”Customer.Host” type=”Customer.Host.Implement.CustomerServer, Customer.Host”>
<property name=”Manager” ref=”Customer.CustomerManager” />
</object>

</objects>
</spring>

<appSettings>
<add key=”Spring.Data.NHibernate.Support.SessionScope.SessionFactoryObjectName” value=”NHibernateSessionFactory”/>
</appSettings>

<system.web>
<compilation Debug=”true” targetFramework=”4.0″ />

<httpModules>
<add name=”Spring” type=”Spring.Context.Support.WebSupportModule, Spring.Web” />
</httpModules>

</system.web>
<system.serviceModel>
<services>
<service name=”Customer.Host”>
<endpoint address=”” binding=”wsHttpBinding” bindingConfiguration=”ServerBinding” contract=”Customer.Host.ICustomerContract”/>
<endpoint address=”mex” binding=”mexHttpBinding” contract=”IMetadataExchange”/>
</service>
</services>
<bindings>
<wsHttpBinding >
<binding name=”ServerBinding” transactionFlow=”true”>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<!– 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 –>
<serviceMetadata httpGetEnabled=”true”/>
<!– 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 –>
<serviceDebug includeExceptionDetailInFaults=”true”/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled=”true” aspNetCompatibilityEnabled=”true”/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests=”true”/>
</system.webServer>

</configuration>

<%@ ServiceHost Language=C# Debug=true Service=Customer.Host Factory=Spring.ServiceModel.Activation.ServiceHostFactory%>

②.Order

Web.config

<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>

……….

<!–spring配置–>
<spring xmlns=”http://www.springframework.net”>
<parsers>
<parser type=”Spring.Data.Config.DatabaseNamespaceParser, Spring.Data” />
<parser type=”Spring.Transaction.Config.TxNamespaceParser, Spring.Data” />
</parsers>
<context>
<resource uri=”config://spring/objects” />

<!–Dao–>
<resource uri=”assembly://Order.Dao/Order.Dao.Config/Dao.xml” />
<!–Service–>
<resource uri=”assembly://Order.Service/Order.Service.Config/Service.xml” />

</context>
<objects xmlns=”http://www.springframework.net”
xmlns:aop
=”http://www.springframework.net/aop”>

<object id=”Order.Host” type=”Order.Host.Implement.OrderServer, Order.Host”>
<property name=”Manager” ref=”Order.OrderManager” />
</object>

</objects>
</spring>

<appSettings>
<add key=”Spring.Data.NHibernate.Support.SessionScope.SessionFactoryObjectName” value=”NHibernateSessionFactory”/>
</appSettings>

<system.web>
<compilation debug=”true” targetFramework=”4.0″ />

<httpModules>
<add name=”Spring” type=”Spring.Context.Support.WebSupportModule, Spring.Web” />
</httpModules>

</system.web>
<system.serviceModel>
<services>
<service name=”Order.Host”>
<endpoint address=”” binding=”wsHttpBinding” bindingConfiguration=”ServerBinding” contract=”Order.Host.IOrderContract”/>
<endpoint address=”mex” binding=”mexHttpBinding” contract=”IMetadataExchange”/>
</service>
</services>
<bindings>
<wsHttpBinding >
<binding name=”ServerBinding” transactionFlow=”true” >
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<!– 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 –>
<serviceMetadata httpGetEnabled=”true”/>
<!– 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 –>
<serviceDebug includeExceptionDetailInFaults=”true”/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled=”true” aspNetCompatibilityEnabled=”true”/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests=”true”/>
</system.webServer>

</configuration>

<%@ ServiceHost Language=C# Debug=true Service=Order.Host Factory=Spring.ServiceModel.Activation.ServiceHostFactory%>

四、客户端

HostTest

[TestFixture]
public class HostTest
{
private CustomerContractClient customerProxy;

private OrderContractClient orderProxy;

[SetUp]
public void Init()
{
customerProxy
= new CustomerContractClient();
orderProxy
= new OrderContractClient();
}

[Test]
public void InitData()
{
using (TransactionScope scope = new TransactionScope())
{
customerProxy.Save(
new CustomerInfo
{
Name
= 刘冬
});

scope.Complete();
}
}

[Test]
public void DistributedTransactionTest()
{
using (TransactionScope scope = new TransactionScope())
{
try
{
CustomerInfo customer
= customerProxy.Get(1);
orderProxy.Save(
new OrderInfo
{
Address
= 中国北京,
CustomerId
= (int)customer.ID,
OrderDate
= DateTime.Now
});
customer.Money
+= 1000;
customerProxy.Update(customer);
scope.Complete();
Console.WriteLine(
分布式事务已提交);
}
catch (Exception ex)
{
Transaction.Current.Rollback();
Console.WriteLine(
发送错误:分布式事务已回滚);
}
}
}
}

五、运行效果

1.初始化数据

2.建立第一张订单,订金小于3000

3.建立第一张订单,订金小于3000

4.建立第一张订单,订金等于3000

5.建立第一张订单,订金大于3000,事务回滚。

好了,基于Web Service的分布式事务已经实现了。

代码下载