[转载]JavaScript单元测试工具Qunit

mikel阅读(941)

[转载]JS单元测试工具Qunit – 前端组qianduanzu.com – 博客园.

随着html5的普及和客户端脚本引擎的强大,之前由后端进行的页面渲染和部分业务逻辑逐渐移至到前端,照成前端逻辑和代码迅速复杂 化。如果没有一款合适的单元测试工具保证前端单元模块的正确功能,对后期的代码调试和维护会造成很大的麻烦,今天就介绍一款单元测试框架 — Qunit

Qunit刚开始属于JQuery项目,专门用于测试JQuery单元模块。现在已经从JQuery项目中独立出来,你可以在https://github.com/jquery/qunit下载到最新版的Qunit,解压后里面有很多目录,对我们有用的其实只有qunit目录下的qunit.js和qunit.css。

 

下面来个简单的示例:
新建一个html文档,贴入以下代码:




<!-- 这里引用的是jQuery官网上的Qunit,项目中为了加快下载可以放到本地 -->
<script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>

<h1 id="qunit-header">QUnit example</h1>

&nbsp;

&nbsp;

<h2 id="qunit-banner"></h2>

&nbsp;

<div id="qunit-testrunner-toolbar"></div>

&nbsp;

<h2 id="qunit-userAgent"></h2>

&nbsp;

&nbsp;

<div id="qunit-fixture">test markup, will be hidden</div>

<script type="text/javascript">// <![CDATA[
             test("a basic test example", function () {
                 ok(true, "this test is fine");
                 var value = "hello";
                 equal(value, "hello", "We expect value to be hello");
             });

             module("Module A");

             test("first test within module", function () {
                 ok(true, "all pass");
             });

             test("second test within module", function () {
                 ok(true, "all pass");
             });

             module("Module B");

             test("some other test", function () {
                 expect(2);
                 equal(true, false, "failing test");
                 equal(true, true, "passing test");
             });

// ]]></script>

&nbsp;

&nbsp;

用浏览器打开这个html你将会看到如下情况:

1,2,3模块通过测试,4模块出现异常,我们需求预期是false,但却返回了true,未能符合需求,那么下面不用说你也知道该如何了

Qunit的API这里就不在做细致介绍,你可以去http://docs.jquery.com/QUnit下面去找,里面有官方API,绝对详细!祝还在纠结的前端童鞋早日摆脱痛苦

文章作者:前端组-Newton

 

[转载]ASP.NET MVC的Model元数据与Model模板:将”ListControl”引入ASP.NET MVC

mikel阅读(955)

[转载]ASP.NET MVC的Model元数据与Model模板:将”ListControl”引入ASP.NET MVC – Artech – 博客园.

我们不仅可以创建相应的模板来根据Model元数据控制种类型的数据在UI界面上的呈现方法,还可以通过一些扩展来控制Model元数据本身。在某些情况下通过这两者的结合往往可以解决很多特殊数据的呈现问题,我们接下来演示的实例就是典型的例子。[本文已经同步到《How ASP.NET MVC Works?》中]

传 统的ASP.NET具有一组重要的控件类型叫做列表控件(ListControl),它的子类包括DropDownList、ListBox、 RadioButtonList和CheckBoxList等。对于ASP.NET MVC来说,我们可以通过HtmlHelper/HtmlHelper<TModel>的扩展方法DropDownList /DropDownListFor和ListBox/ListBox在界面上呈现一个下拉框和列表框,但是我们需要手工指定包含的所有列表选项。在一般的 Web应用中,尤其是企业应用中,我们会选择将这些列表进行单独地维护,如果我们在构建“列表控件”的时候能够免去手工提供列表的工作,这无疑会为开发带 来极大的遍历,而这实际上很容易实现。[源代码从这里下载]

一、实现的效果

我们先来看看通过该扩展最终实现的效果。在通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中,我们定义一个作为Model表示员工的Employee类型。如下面的代码片断所示,表示性别、学历、部门和技能的属性分别应用了RadioButtonListAttributeDropdownListAttributeListBoxAttributeCheckBoxListAttribubte四 个特性。从名称可以看出来,这四个特性分别代表了目标元素呈现在UI界面上的形式,即对应着传统ASP.NET Web应用中的四种类型的列表控件:RadioButtonList、DropdownList、ListBox和CheckBoxList。特性中指定 的字符串表示预定义列表的名称。

1: public class Employee

2: {

3: [DisplayName("姓名")]

4: public string Name { get; set; }

5:

6: [RadioButtonList("Gender")]

7: [DisplayName("性别")]

8: public string Gender { get; set; }

9:

10: [DropdownList("Education")]

11: [DisplayName("学历")]

12: public string Education { get; set; }

13:

14: [ListBox("Department")]

15: [DisplayName("所在部门")]

16: public IEnumerableDepartments { get; set; }

17:

18: [CheckBoxList("Skill")]

19: [DisplayName("擅长技能")]

20: public IEnumerableSkills { get; set; }

21: }

在创建的默认HomeController中,我们定义了如下一个Index操作方法。在该方法中,我们创建了一个具体的Employee对象并对它的所有属性进行了相应设置,最终将该对象呈现在默认的View中。

1: public class HomeController : Controller

2: {

3: public ActionResult Index()

4: {

5: Employee employee = new Employee

6: {

7: Name = "张三",

8: Gender = "M",

9: Education = "M",

10: Departments= new string[] { "HR", "AD" },

11: Skills = new string[] { "CSharp", "AdoNet" }

12: };

13: return View(employee);

14: }

15: }

如下所示的是上面的Index操作对应的View定义,这是一个以Model类型为Employee的强类型View,我们通过调用HtmlHelper的模板方法EditorFor将作为Model的Employee对象的所有属性以编辑模式呈现出来。

1: @model Employee

2:

3:
4:@Html.LabelFor(m =&gt; m.Name)@Html.EditorFor(m =&gt; m.Name)5:6:
7:@Html.LabelFor(m =&gt; m.Gender)@Html.EditorFor(m =&gt; m.Gender)8:9:
10:@Html.LabelFor(m =&gt; m.Education)@Html.EditorFor(m =&gt; m.Education)11:12:
13:@Html.LabelFor(m =&gt; m.Departments)@Html.EditorFor(m =&gt; m.Departments)14:15:
16:@Html.LabelFor(m =&gt; m.Skills)@Html.EditorFor(m =&gt; m.Skills)17:18:

图体现了该Web应用运行时的效果。我们可以看到,四个属性分别以四种不同的“列表控件”呈现出来,并且对应在它们上面的四个字定义的列表特性 (RadioButtonListAttribute、DropdownListAttribute、ListBoxAttribute和 CheckBoxListAttribubte)。

image

二、ListItem与ListProvider

现在对体现在上面演示实例的基于列表数据的UI定制的设计进行简单地介绍。我们首先来定义如下一个表示列表中某个条目(列表项)的类型 ListItem,简单起见,我们紧紧定义Text和Value两个属性,它们分别表示显示的文字和代表的值。比如对于一组表示国家的列表,列表项的 Text属性表示成国家名称(比如“中国”),具体的值则可能是国家的代码(比如“CN”)。

1: public class ListItem

2: {

3: public string Text { get; set; }

4: public string Value { get; set; }

5: }

我们将提供列表数据的组件称为ListProvider,它们实现了IListProvider接口。如下面的代码片断所示,IListProvider具有唯一的方法GetListItems根据指定的列表名称获取所有的列表项。通过实现IListProvider,我们定义了一个默认的DefaultListProvider。简单起见,DefaultListProvider直接通过一个静态字段模拟列表的存储,在真正的项目中一般会保存在数据库中。DefaultListProvider维护了四组列表,分别表示性别、学历、部门和技能,它们正好对应着Employee的四个属性。

1: public interface IListProvider

2: {

3: IEnumerableGetListItems(string listName);

4: }

5: public class DefaultListProvider : IListProvider

6: {

7: private static Dictionary&gt; listItems = new Dictionary&gt;();

8: static DefaultListProvider()

9: {

10: var items = new ListItem[]{

11: new ListItem{ Text = "男", Value="M"},

12: new ListItem{ Text = "女", Value="F"}};

13: listItems.Add("Gender", items);

14:

15: items = new ListItem[]{

16: new ListItem{ Text = "高中", Value="H"} ,

17: new ListItem{ Text = "大学本科", Value="B"},

18: new ListItem{ Text = "硕士", Value="M"} ,

19: new ListItem{ Text = "博士", Value="D"}};

20: listItems.Add("Education", items);

21:

22: items = new ListItem[]{

23: new ListItem{ Text = "人事部", Value="HR"} ,

24: new ListItem{ Text = "行政部", Value="AD"},

25: new ListItem{ Text = "IT部", Value="IT"}};

26: listItems.Add("Department", items);

27:

28: items = new ListItem[]{

29: new ListItem{ Text = "C#", Value="CSharp"} ,

30: new ListItem{ Text = "ASP.NET", Value="AspNet"},

31: new ListItem{ Text = "ADO.NET", Value="AdoNet"}};

32: listItems.Add("Skill", items);

33: }

34: public IEnumerableGetListItems(string listName)

35: {

36: IEnumerableitems;

37: if (listItems.TryGetValue(listName, out items))

38: {

39: return items;

40: }

41: return new ListItem[0];

42: }

43: }

接下来我们定义如下一个ListProviders类型,它的静态只读属性Current表示当前的ListProvider,而对当前ListProvider的注册通过静态方法SetListProvider来实现。如果没有对当前ListProvider进行显式注册,则默认采用DefaultListProvider。

1: public static class ListProviders

2: {

3: public static IListProvider Current { get; private set; }

4: static ListProviders()

5: {

6: Current = new DefaultListProvider();

7: }

8: public static void SetListProvider(FuncproviderAccessor)

9: {

10: Current = providerAccessor();

11: }

12: }

三、通过对HtmlHelper/HtmlHelper的扩展生成“ListControl”的HTML

基于四种“列表控件”的HTML生成是通过定义HtmlHelper的扩展方法来实现的,如下面的代码所示,定义在ListControlExtensions中的四个扩展方法实现了针对这四种列表控件的UI呈现。参数listName表示使用的预定义列表的名称,而value和values则表示绑定的值。RadioButtonList/DropdownList只允许单项选择,而ListBox/CheckBoxList允许多项选择,所以对应的值类型分别是string和IEnumerable。

1: public static class ListControlExtensions

2: {

3: //其他成员

4: public static MvcHtmlString RadioButtonList( this HtmlHelper htmlHelper,string name, string listName, string value)

5: {

6: return RadioButtonCheckBoxList(htmlHelper, listName, item =&gt;

7: htmlHelper.RadioButton(name, item.Value, value == item.Value));

8: }

9:

10: public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, string listName, IEnumerablevalues)

11: {

12: return RadioButtonCheckBoxList(htmlHelper, listName, item =&gt; CheckBoxWithValue(htmlHelper, name, values.Contains(item.Value), item.Value));

13: }

14:

15: public static MvcHtmlString ListBox(this HtmlHelper htmlHelper, string name, string listName, IEnumerablevalues)

16: {

17: var listItems = ListProviders.Current.GetListItems(listName);

18: ListselectListItems = new List();

19: foreach (var item in listItems)

20: {

21: selectListItems.Add(new SelectListItem { Value = item.Value,

22: Text = item.Text,

23: Selected = values.Any(value =&gt; value == item.Value) });

24: }

25: return htmlHelper.ListBox(name, selectListItems);

26: }

27:

28: public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, string listName, string value)

29: {

30: var listItems = ListProviders.Current.GetListItems(listName);

31: ListselectListItems = new List();

32: foreach (var item in listItems)

33: {

34: selectListItems.Add(new SelectListItem { Value = item.Value,

35: Text = item.Text, Selected = value == item.Value});

36: }

37: return htmlHelper.DropDownList(name, selectListItems);

38: }

39: }

从上面的代码片断可以看到,在ListBox和DropDownList方法中我们通过当前的ListProvider获取指定列表名称的所有列表项并生成相应的SelectListItem列表,最终通过调用HtmlHelper现有的扩展方法ListBox和DropDownList实现HTML的呈现。而RadioButtonList和MvcHtmlString最终调用了辅助方法RadioButtonCheckBoxList显示了最终的HTML生成,该方法定义如下。

1: public static class ListControlExtensions

2: {

3: public static MvcHtmlString CheckBoxWithValue(this HtmlHelper htmlHelper, string name, bool isChecked, string value)

4: {

5: string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

6: ModelState modelState;

7:

8: //将ModelState设置为表示是否勾选布尔值

9: if (htmlHelper.ViewData.ModelState.TryGetValue(fullHtmlFieldName, out modelState))

10: {

11: htmlHelper.ViewData.ModelState.SetModelValue(fullHtmlFieldName, new ValueProviderResult(isChecked, isChecked.ToString(), CultureInfo.CurrentCulture));

12: }

13: MvcHtmlString html;

14: try

15: {

16: html = htmlHelper.CheckBox(name, isChecked);

17: }

18: finally

19: {

20: //将ModelState还原

21: if (null != modelState)

22: {

23: htmlHelper.ViewData.ModelState[fullHtmlFieldName] = modelState;

24: }

25: }

26: string htmlString = html.ToHtmlString();

27: var index = htmlString.LastIndexOf('

28: //过滤掉类型为"hidden"的<input type="text" />元素

29: XElement element = XElement.Parse(htmlString.Substring(0, index));

30: element.SetAttributeValue("value", value);

31: return new MvcHtmlString(element.ToString());

32: }

33:

34: private static MvcHtmlString RadioButtonCheckBoxList(HtmlHelper htmlHelper, string listName, FuncelementHtmlAccessor)

35: {

36: var listItems = ListProviders.Current.GetListItems(listName);

37: TagBuilder table = new TagBuilder("table");

38: TagBuilder tr = new TagBuilder("tr");

39: foreach (var listItem in listItems)

40: {

41: TagBuilder td = new TagBuilder("td");

42: td.InnerHtml += elementHtmlAccessor(listItem).ToHtmlString();

43: td.InnerHtml += listItem.Text;

44: tr.InnerHtml += td.ToString();

45: }

46: table.InnerHtml = tr.ToString();

47: return new MvcHtmlString(table.ToString());

48: }

49: }

方法RadioButtonCheckBoxList在生成RadioButtonList和CheckBoxList的时候才用

进行布局。组成RadioButtonList的单个RadioButton最终是调用HtmlHelper现有的扩展方法RadioButton生成的,而CheckBoxList中的CheckBox则是通过调用我们自定义的CheckBoxWithValue方法生成的。CheckBoxWithValue最终还是调用HtmlHelper现有的扩展方法CheckBox生成单个CheckBox对应的HTML,但是方法值支持布尔值的绑定,并且会生成一个在这里不需要的Hidden元素,所以我们不得不在调用该方法的前后作一些手脚。 四、ListAttribute

现在我们来介绍应用在Employee属性上的四个特性的定义。如下面的代码片断所示,基于四种“列表控件”的特性均继承自抽象特性ListAttribute。ListAttribute实现了IMetadataAware接口,在实现的OnMetadataCreated方法中将在构造函数中指定的代表列表名称的ListName属性添加到表示Model元数据的ModelMetadata对象的AdditionalValues属性中。四个具体的列表特性重写了OnMetadataCreated方法,并在此基础上将ModelMetadata的TemplateHint分别设置为DropdownList、ListBox、RadioButtonList和CheckBoxList。

1: [AttributeUsage(AttributeTargets.Property)]

2: public abstract class ListAttribute : Attribute, IMetadataAware

3: {

4: public string ListName { get; private set; }

5: public ListAttribute(string listName)

6: {

7: this.ListName = listName;

8: }

9: public virtual void OnMetadataCreated(ModelMetadata metadata)

10: {

11: metadata.AdditionalValues.Add("ListName", this.ListName);

12: }

13: }

14:

15: [AttributeUsage(AttributeTargets.Property)]

16: public class DropdownListAttribute : ListAttribute

17: {

18: public DropdownListAttribute(string listName)

19: : base(listName)

20: { }

21: public override void OnMetadataCreated(ModelMetadata metadata)

22: {

23: base.OnMetadataCreated(metadata);

24: metadata.TemplateHint = "DropdownList";

25: }

26: }

27:

28: [AttributeUsage(AttributeTargets.Property)]

29: public class ListBoxAttribute : ListAttribute

30: {

31: public ListBoxAttribute(string listName)

32: : base(listName)

33: { }

34: public override void OnMetadataCreated(ModelMetadata metadata)

35: {

36: base.OnMetadataCreated(metadata);

37: metadata.TemplateHint = "ListBox";

38: }

39: }

40:

41: [AttributeUsage(AttributeTargets.Property)]

42: public class RadioButtonListAttribute : ListAttribute

43: {

44: public RadioButtonListAttribute(string listName)

45: : base(listName)

46: { }

47:

48: public override void OnMetadataCreated(ModelMetadata metadata)

49: {

50: base.OnMetadataCreated(metadata);

51: metadata.TemplateHint = "RadioButtonList";

52: }

53: }

54:

55: [AttributeUsage(AttributeTargets.Property)]

56: public class CheckBoxListAttribute : ListAttribute

57: {

58: public CheckBoxListAttribute(string listName)

59: : base(listName)

60: { }

61:

62: public override void OnMetadataCreated(ModelMetadata metadata)

63: {

64: base.OnMetadataCreated(metadata);

65: metadata.TemplateHint = "CheckBoxList";

66: }

67: }

五、模板View的定义

由于四个具体的ListAttribute已经对表示模板名称的ModelMetadata的TemplateHint进行了设置,那么我们针对它们定义相应的分部View作为对应的模板,那么在调用HtmlHelper/HtmlHelper相应模板方法的时候就会按照这些模板对目标元素进行呈现。实现如上图所示的效果的四个模板定义如下,它们被保存在View\Shared\EditorTemplates目录下面。

1: DropdownList.cshtml:

2: @model string

3: @{

4: string listName = (string)ViewData.ModelMetadata.AdditionalValues["ListName"];

5: @Html.DropDownList("",listName,Model)

6: }

7:

8: ListBox.cshtml:

9: @model IEnumerable

10: @{

11: string listName = (string)ViewData.ModelMetadata.AdditionalValues["ListName"];

12: @Html.ListBox("",listName,Model)

13: }

14:

15: RadioButtonList.cshtml:

16: @model string

17: @{

18: string listName = (string)ViewData.ModelMetadata.AdditionalValues["ListName"];

19: @Html.RadioButtonList("",listName,Model)

20: }

21:

22: CheckBoxList.cshtml:

23: @model IEnumerable

24: @{

25: string listName = (string)ViewData.ModelMetadata.AdditionalValues["ListName"];

26: @Html.CheckBoxList("", listName, Model)

27: }

ASP.NET MVC的Model元数据与Model模板:预定义模板
ASP.NET MVC的Model元数据与Model模板:模板的获取与执行策略
ASP.NET MVC的Model元数据与Model模板:将ListControl引入ASP.NET MVC

[转载]Jeditable - jQuery就地编辑插件使用

mikel阅读(1127)

[转载]Jeditable – jQuery就地编辑插件使用 – 孟晨 – 博客园.

jeditable是一个JQuery插件,它的优点是可以就地编辑,并且提交到服务器处理,是一个不可多得的就地编辑插件。(注: 就地编辑,也有称即时编辑?一般的流程是这样的,当用户点击网页上的文字时,该文字就会出现在一个编辑框中,用户对文字进行修改完成后点击提交按钮,新的文本将发送到服务器上,然后表单消失,显示最新编辑的文本。),你可以通过这个演示页面来亲自体验下。

官网:http://www.appelsiini.net/projects/jeditable

基本的使用方法如下:

首先编辑一个 html 文件,包含这么一段:

<div class="edit" id="div_1">Dolor</div>
<div class="edit_area" id="div_2">Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</div>

然后我们使用如下的 JS 代码来实现即时编辑(要先引入 Jeditable 插件):

$(document).ready(function() { $('.edit').editable('http://www.example.com/save.php'); });

 

实现不同内容的编辑以及更多的定制项:

$(document).ready(function() { $('.edit').editable('http://www.example.com/save.php', { indicator : 'Saving...', tooltip : 'Click to edit...' }); $('.edit_area').editable('http://www.example.com/save.php', { type : 'textarea', cancel : 'Cancel', submit : 'OK', indicator : '<img src="img/indicator.gif">', tooltip : 'Click to edit...' }); });

上面的定制项包括按钮的文本,提示信息以及提交时的 loading 图片显示等等。

那么当用户点击了确定按钮时,发送到服务器上的是什么数据呢?

数据内容包含了编辑框的 ID 以及新的内容:id=elements_id&value=user_edited_content

你也可以使用下面的方法来修改默认的参数名:

$(document).ready(function() { $('.edit').editable('http://www.example.com/save.php', { id : 'elementid', name : 'newvalue' }); });

修改后传递的数据变成:elementid=elements_id&newvalue=user_edited_content

如果单行文本框不注意满足你的要求,可以使用 textarea 多行文本编辑框:

$(document).ready(function() { $('.edit_area').editable('http://www.example.com/save.php', { loadurl : 'http://www.example.com/load.php', type : 'textarea', submit : 'OK' }); });

另外 Jeditable 还支持下拉选择框哦:

$('.editable').editable('http://www.example.com/save.php', { data : " {'E':'Letter E','F':'Letter F','G':'Letter G', 'selected':'F'}", type : 'select', submit : 'OK' });

 

或者你也可以从服务器获取下拉选择的数据内容:

<?php /* http://www.example.com/json.php */
 $array['E'] =  'Letter E'; $array['F'] =  'Letter F'; $array['G'] =  'Letter G'; $array['selected'] =  'F'; print json_encode($array); ?>

然后通过 loadurl 指定这个服务器输出数据的 URL 地址:

$('.editable').editable('http://www.example.com/save.php', { loadurl : 'http://www.example.com/json.php', type : 'select', submit : 'OK' });

 

如果你希望给组件设定不同的样式,可以这样:

$('.editable').editable('http://www.example.com/save.php', { cssclass : 'someclass' }); $('.editable').editable('http://www.example.com/save.php', { loadurl : 'http://www.example.com/json.php', type : 'select', submit : 'OK', style : 'display: inline' });  

或者:

$('.editable').editable('http://www.example.com/save.php', { loadurl : 'http://www.example.com/json.php', type : 'select', submit : 'OK', style : 'inherit' });

最后来点高级的内容,如果你希望使用一个 JS 函数而不是 URL 来处理提交,可以这样:

$('.editable').editable(function(value, settings) { console.log(this); console.log(value); console.log(settings); return(value); }, { type : 'textarea', submit : 'OK', });

处理回调:

$('.editable').editable('http://www.example.com/save.php', { type : 'textarea', submit : 'OK', callback : function(value, settings) { console.log(this); console.log(value); console.log(settings); } });

使用附加参数:

$(".editable").editable("http://www.example.com/save.php";, { submitdata : {foo: "bar"}; });

直接从URL获取显示内容:

$(".editable").editable("http://www.example.com/save.php";, { loadurl : "http://www.example.com/load.php" });

英文原文:http://www.appelsiini.net/projects/jeditable

[转载]ASP.NET MVC中加载WebForms用户控件(.ascx)

mikel阅读(1026)

[转载]ASP.NET MVC中加载WebForms用户控件(.ascx) – dudu – 博客园.

问题背景

博客园博客中的日历用的是ASP.NET WebForms的日历控件(System.Web.UI.WebControls.Calendar),它会为“上一月”、“下一月”的链接生成”__doPostBack()”的js调用,如下图:

目前发现它会带来两个问题:

1. 不支持IE10;

2. 某些电脑不允许执行__doPostBack。

问题提炼

前提:

  1. 我们想以最低的成本解决这个问题,也就是对当前代码尽可能少的改动。所以要尽可能重用现有的日历控件代码。
  2. 日历改为Ajax加载,点击“上一月”、“下一月”时Ajax更新日历内容。
  3. ASP.NET MVC处理Ajax请求。

要解决的问题:

如何在ASP.NET MVC Controller中加载包含WebForms日历控件的用户控件(.ascx),并得到其输出的字符串,然后将__doPostBack的代码替换为ajax调用代码。

核心问题:

如何在ASP.NET MVC Controller中得到用户控件(.ascx)输出的字符串。

解决方法

先看代码

public ActionResult Calendar()
{
var page = new Page();
var form = new HtmlForm();
var calendar = page.LoadControl("~/Controls/CNBlogsCalendar.ascx");
form.Controls.Add(calendar);
page.Controls.Add(form);
var sw = new StringWriter();
System.Web.HttpContext.Current.Server.Execute(page, sw, true);
return Content(sw.ToString());
}

代码很简单,但得到这个代码花了今天一上午时间。

代码说明:

  • 必须要new Page(),只有Page才能LoadControl。
  • 必须要new HtmlForm(),因为日历控件必要要放在<form runat=”server”>之间。
  • 关键功臣是HttpContext.Current.Server.Execute,动态加载控件并输出字符串全靠它。这个功臣是在这篇文章中找到的(感谢Sam Mueller)。之前我用过的方法(继续不走寻常路:ASP.NET MVC中使用Web Forms用户控件)不仅麻烦,而且在这个场景下会有问题。

代码运行结果:

完整代码下载:

http://files.cnblogs.com/dudu/CNBlogsDemoMvcAscx.rar

[转载]ASP.NET MVC的Model元数据与Model模板:模板的获取与执行策略

mikel阅读(995)

[转载]ASP.NET MVC的Model元数据与Model模板:模板的获取与执行策略 – Artech – 博客园.

当我们调用HtmlHelper或者HtmlHelper<TModel>的模板方法对整个Model或者Model的某个数据成员以 某种模式(显示模式或者编辑模式)进行呈现的时候,通过预先创建的代表Model元数据的ModelMetadata对象都可以找到相应的模板。如果模板 对应着某个自定义的分部View,那么只需要执行该View即可;对于默认模板,则直接可以得到相应的HTML。本篇文章着重讨论模板的获取和执行机制, 不过在这之前,顺便来讨论一下DataTypeAttribute和模板的关系。[本文已经同步到《How ASP.NET MVC Works?》中]

一、 DataTypeAttribute和模板有何关系?

通过《初识Model元数据》 针对Model元数据定义的介绍,我们知道通过DataTypeAttribute特性对目标元素设置的数据类型最终会反映在表示Model元数据的 ModelMetadata对象的DataTypeName属性上。此外,对于某些设置的数据类型,比如Date、Time、Duration和 Currency等,还会随之创建一个DisplayFormatAttribute应用到ModelMetadata上。那么 ModelMetadata的DataTypeName属性对目标元素的最终呈现具有怎样的影响呢?

实际上在模板匹配的过程中会将ModelMetadata的DataTypeName属性当作模板名称来看待,所以下面两种形式的Model类型定义可以看成是等效的。通过UIHintAttribute特性设置的模板名称和通过DataTypeAttribute特性设置的数据类型的唯一不同之处在于前者具有更高的优先级。换句话说,如果将UIHintAttribute和DataTypeAttribute同时应用到同一个数据成员分别将模板名称和数据类型设置为ABC和123,自定义模板123只有在模板ABC不存在的情况下才会被使用。

1: public class Model

2: {

3: [DataType(DataType.Html)]

4: public string Foo { get; set; }

5:

6: [DataType(DataType.MultilineText)]

7: public string Bar { get; set; }

8:

9: [DataType(DataType.Url)]

10: public string Baz { get; set; }

11: }

12:

13: public class Model

14: {

15: [UIHint("Html")]

16: public string Foo { get; set; }

17:

18: [UIHint("MultilineText")]

19: public string Bar { get; set; }

20:

21: [UIHint("Url")]

22: public string Baz { get; set; }

23: }

实例演示:证明DataTypeName与模板名称的等效性

为了证明通过DataTypeAttribute特性设置数据类型在针对目标元素进行可视化呈现过程中被视为模板名称,我们来做一个简单的实例演示。在这个实例中我们定义了如下一个表示三角形的数据类型Triangle,其属性A、B和C是一个Point对象,表示三个角所在的坐标。

1: public class Triangle

2: {

3: [DataType("PointInfo")]

4: public Point A { get; set; }

5:

6: [DataType("PointInfo")]

7: public Point B { get; set; }

8:

9: [DataType("PointInfo")]

10: public Point C { get; set; }

11: }

12:

13: [TypeConverter(typeof(PointTypeConverter))]

14: public class Point

15: {

16: public double X { get; set; }

17: public double Y { get; set; }

18: public Point(double x, double y)

19: {

20: this.X = x;

21: this.Y = y;

22: }

23:

24: public static Point Parse(string point)

25: {

26: string[] split = point.Split(',');

27: if (split.Length != 2)

28: {

29: throw new FormatException("Invalid point expression.");

30: }

31: double x;

32: double y;

33: if (!double.TryParse(split[0], out x) ||!double.TryParse(split[1], out y))

34: {

35: throw new FormatException("Invalid point expression.");

36: }

37: return new Point(x, y);

38: }

39: }

40:

41: public class PointTypeConverter : TypeConverter

42: {

43: public override bool CanConvertFrom(ITypeDescriptorContext context,Type sourceType)

44: {

45: return sourceType == typeof(string);

46: }

47:

48: public override object ConvertFrom(ITypeDescriptorContext context,CultureInfo culture, object value)

49: {

50: if (value is string)

51: {

52: return Point.Parse(value as string);

53: }

54: return base.ConvertFrom(context, culture, value);

55: }

56: }

对于类型Triangle和Point的定义,有两点值得注意:其一,Triangle的三个A、B和C属性上应用了DataTypeAttribute特性并将自定义数据类型设置为PointInfo(不是Point);其二,Point类型上应用了TypeConverterAttribute特性并将TypeConverter类型设置为PointTypeConverter,后者支持源自字符串的类型转换。通过前面对复杂类型(Complex Type)的介绍,这样会将Triangle的三个属性从复杂类型成员转换成简单类型成员。根据前提介绍的关于Object模板对数据成员的便利规则,Triangle的这三个属性才能被最终呈现出来。

现在我们创建一个Model类型为Point的强类型分部View作为模板,并将其命名为PointInfo(和前面通过DataTypeAttribute特性指定的自定义数据类型一致)。我们只为Point定义关于显示模式的模板,所以我们将该分部View文件放在Views\Shared\DisplayTemplates中。如下面的代码片断所示,我们将一个Point对象显示为(X,Y)的形式。

1: @model MvcApp.Models.Point

2: (@Model.X, @Model.Y)

现在我们创建一个默认的HomeCtroller。如下面的代码片断所示,在默认的Index操作方法中我们创建了一个Triangle对象将其呈现在默认的View中。

1: public class HomeController : Controller

2: {

3: public ActionResult Index()

4: {

5: Triangle triangle = new Triangle

6: {

7: A = new Point(1,2),

8: B = new Point(2,3),

9: C = new Point(3,4)

10: };

11: return View(triangle);

12: }

13: }

下面是对应的View的定义,这是一个Model类型为Triangle的强类型View,我们仅仅调用了HtmlHelper<TModel>的DisplayModel方法将作为Model的Triangle对象以显示模式呈现出来。

 1: @model MvcApp.Models.Triangle
 2: @Html.DisplayForModel()

运行该Web应用会在浏览器中得到如下图所示的呈现效果,我们可以看到作为我们创建的Triangle对象的A、B和C属性表示的三个角的坐标是完全按照我们定义的PointInfo模板的方式进行呈现的。

image

二、模板的获取与执行

当我们调用HtmlHelper或者HtmlHelper<TModel>的模板方法对整个Model或者Model的某个数据成员以 某种模式(显示模式或者编辑模式)进行呈现的时候,通过预先创建的代表Model元数据的ModelMetadata对象都可以找到相应的模板。如果模板 对应着某个自定义的分部View,那么只需要执行该View即可;对于默认模板,则直接可以得到相应的HTML。

根据Model元数据对目标模板的解析是整个模板方法执行流程中最核心的部分,也是本篇讨论的重点。我们以针对 HtmlHelper<TModel>的扩展方法DisplayFor为例,看看针对通过表达式expression获取的Model对象是 如何以显示模式呈现出来的。

 1: public static class DisplayExtensions
 2: {
 3:     public static MvcHtmlString DisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName);
 4: }

在DisplayFor被调用的时候,如果通过参数expression表示的Model获取表达式是针对某个属性的,那么属性名会被获取出来。然 后执行表达式得到一个作为Model的对象,该对象连同属性名(如果有)一起被用于表示Model元数据的Metadatadata对象。接下来会根据该 Metadatadata对象得到一系列表示分部模板View名称的列表,这些View名称按照优先级排列如下:

  • 作为参数templateName传入的模板名称(如果不为空)。
  • Metadatadata的TemplateHint属性值(如果不为空)。
  • Metadatadata的DataTypeName属性值(如果不为空)。
  • 如果Model对象的真实类型为非空值类型,该类型名作为模板View名;否则底层(Underlying)类型名作为模板View名(比如说,对于int?类型则将Int32作为模板View名)。
  • 如果Model对象的真实类型为非复杂类型,则使用String模板(由于非复杂类型能够实现与String类型之间的转换,所以可以转换成String进行呈现)。
  • 在Model的声明类型为接口情况下,如果该接口继承自IEnuerable则采用Collection模板。
  • 在Model的声明类型为接口情况下,使用Object模板。
  • 如果Model声明类型不是接口类型,按照其类型继承关系向上追溯知道Object类型,逐个将类型名称作为模板View名称。如果声明类型实现了IEnuerable接口,则将最后的Object替换成Collection。

对于得到的这个表示列表,会按照先后顺序便利所有的元素。针对具体的表示模板View名称的某个字符串,会根据呈现的模式在指定的路径(显示模式和编辑模式分别为“/DisplayTemplates/{TemplateName}”和“/EditorTemplates/{TemplateName}”) 去寻找定义模板的View。如果这样的View存在,则直接执行该View并返回。如果不能找到自定义模板分部View,则根据该模板名称在默认的模板列 表中查找,如果存在名称匹配的默认模板,则直接返回默认模板对应的HTML。如果默认的模板列表中的名称均与指定的名称不匹配,在进入下一次迭代。

ASP.NET MVC的Model元数据与Model模板:预定义模板
ASP.NET MVC的Model元数据与Model模板:模板的获取与执行策略
ASP.NET MVC的Model元数据与Model模板:将ListControl引入ASP.NET MVC

[转载]ASP.NET MVC通过OAuth调用Google API获取用户信息

mikel阅读(1233)

[转载]ASP.NET MVC通过OAuth调用Google API获取用户信息 – dudu – 博客园.

一边享受着乡村的宁静,一边写着博客,也是一种惬意。

喜欢解决问题后写一篇博客。通过文字表达出来,会加深自己的理解,还经常会有新的收获,甚至会找到更好的解决方法。同时,还能分享给别人。一举多得,何乐而不为呢?

这次要解决的问题是如何在用户注册时验证用户的邮箱?

通常的解决方法是给用户的邮箱发一封激活邮件。但这个方法有以下几个问题:

  • 从发出邮件至收到邮件,可能会有延时。
  • 邮件可能会被当作垃圾邮件处理
  • 用户可能会填错了邮箱,更糟糕的情况是用户不知道自己填错了邮箱。

这次要解决的问题的更具体的场景是如何验证用户的gmail邮箱?

我们采用的解决方法是通过OAuth调用Google API获取用户的gmail地址

于是,问题就变为如何在ASP.NET MVC中通过OAuth调用Google API

必看的两个文档:

Using OAuth 2.0 to Access Google APIs

Using OAuth 2.0 for Web Server Applications

我们的OAuth应用场景是Web Server Applications,对应的序列图如下:

简单描述一下整个流程:

  1. 你在网站上提供一个Google OAuth登录的链接
  2. 用户点击这个链接进入Google登录页面进行登录
  3. 用户登录成功后,会显示授权页面。
  4. 用户授权成功后,Google会自动重定向至你的网站页面,并传递authorization code给你。
  5. 通过这个authorization code,向Google OAuth服务器请求access_token。
  6. 拿到access_token之后,调用Google API获取用户信息。

下面是具体的实现步骤:

1. 进入Google APIs Console,创建一个Project,并创建Client ID,如下图:

得到这些信息 —— Client ID, Email address, Client secret, Redirect URIs

2. 创建一个空的ASP.NET MVC项目

3. 在web.config中添加相应的appSetting保存第1步得到的Client ID相关信息







4. 创建MVC控制器OAuthController,并添加名为GoogleLogin的Action,用于重定向至Google页面进行登录。代码如下:

public class OAuthController : Controller
{
public ActionResult GoogleLogin()
{
var url = "https://accounts.google.com/o/oauth2/auth?"+
"scope={0}&amp;state={1}&amp;redirect_uri={2}&amp;response_type=code&amp;client_id={3}&amp;approval_prompt=force";
//userinfo.email表示获取用户的email
var scope = HttpUtility.UrlEncode("https://www.googleapis.com/auth/userinfo.email");
//对应于userinfo.email
var state = "email";
var redirectUri = HttpUtility.UrlEncode(ConfigurationManager.AppSettings["RedirectURI"]);
var cilentId = HttpUtility.UrlEncode(ConfigurationManager.AppSettings["ClientID"]);
return Redirect(string.Format(url, scope, state, redirectUri, cilentId));
}
}

编译后,通过浏览器访问,假设域名是passport.cnblogs.cc,访问网址就是passport.cnblogs.cc/oauth/googlelogin,访问后,如果你的google帐户已经处于登录状态,则直接显示授权页面,如下图:

点击”Allow access”之后,页面会被重定向回你的网站,我们这里重定向过来的网址是 passport.cnblogs.cc/oauth2callback?state=email&code=4 /BSCUqsaY6S5GYk9tFR-45-_UhL4-,查询参数code的值就是authorization code,接下来就是对这个重定向网址passport.cnblogs.cc/oauth2callback的处理(这个网址就是第1步中得到的 Redirect URIs)。

routes.MapRoute(
“oauth2callback”,
“oauth2callback”,
new { controller = “OAuth”, action = “GoogleCallback”, id = UrlParameter.Optional }
);

6. 在OAuthController中添加名为GoogleCallback的Action

public class OAuthController : Controller
{
public ActionResult GoogleCallback()
{
}
}

接下来的操作都在GoogleCallback()中完成。

7. 这一步是关键的地方,主要完成两个操作:

a) 通过authorization code,向Google OAuth服务器请求access_token(访问令牌,每次调用Google API都需要这个)。
b) 拿到access_token之后,调用Google API获取用户信息(这里是email)。

主要参考文档:https://developers.google.com/accounts/docs/OAuth2WebServer

7.1 根据authorization code获取access_token的代码流程是:

向https://accounts.google.com/o/oauth2/token发送HTTP POST请求,并传递相应的参数。
获取服务器的响应,响应内容的格式时json格式。
将json反序列化为匿名类型的实例,并获取access_token。

代码如下:

//由于是https,这里必须要转换为HttpWebRequest
var webRequest = WebRequest.Create("https://accounts.google.com/o/oauth2/token") as HttpWebRequest;
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";

//参考https://developers.google.com/accounts/docs/OAuth2WebServer
var postData = string.Format("code={0}&amp;client_id={1}&amp;client_secret={2}&amp;redirect_uri={3}" +
"&amp;grant_type=authorization_code",
Request.QueryString["code"],
ConfigurationManager.AppSettings["ClientID"],
ConfigurationManager.AppSettings["ClientSecret"],
ConfigurationManager.AppSettings["RedirectURI"]);

//在HTTP POST请求中传递参数
using (var sw = new StreamWriter(webRequest.GetRequestStream()))
{
sw.Write(postData);
}

//发送请求,并获取服务器响应
var resonseJson = "";
using (var response = webRequest.GetResponse())
{
using (var sr = new StreamReader(response.GetResponseStream()))
{
resonseJson = sr.ReadToEnd();
}
}

//通过Json.NET对服务器返回的json字符串进行反序列化,得到access_token
var accessToken = JsonConvert.DeserializeAnonymousType(resonseJson, new { access_token = "" }).access_token;

7.2 根据access_token读取用户信息的代码流程是:

向https://www.googleapis.com/oauth2/v1/userinfo发送HTTP GET请求,在请求头中包含access_token信息。
获取服务器的json格式的响应内容,并从中读取用户的email信息。

代码如下:

webRequest = WebRequest.Create("https://www.googleapis.com/oauth2/v1/userinfo") as HttpWebRequest;
webRequest.Method = "GET";
webRequest.Headers.Add("Authorization", "Bearer " + accessToken);

using (var response = webRequest.GetResponse())
{
using (var sr = new StreamReader(response.GetResponseStream()))
{
return Content(JsonConvert.DeserializeAnonymousType(sr.ReadToEnd(), new { Email = "" }).Email);
}
}

完整代码下载

http://files.cnblogs.com/dudu/CNBlogsDemoMvcOAuth.rar

[转载]ASP.NET Session丢失问题原因及解决方案

mikel阅读(1191)

[转载]ASP.NET Session丢失问题原因及解决方案 – kdkler – 博客园.

正常操作情况下会有ASP.NET Session丢失的情况出现。因为程序是在不停的被操作,排除Session超时的可能。另外,Session超时时间被设定成60分钟,不会这么快就超时的。

现在我就把原因和解决办法写出来。

ASP.NET Session丢失原因:

由于ASP.NET程序是默认配置,所以Web.Config文件中关于Session的设定如下:

<sessionState mode=’InProc’ stateConnectionString=’tcpip=127.0.0.1:42424′ SQLConnectionString=’data source=127.0.0.1;Trusted_Connection=yes’ cookieless=’true’ timeout=’60’/>

我 们会发现sessionState标签中有个属性mode,它可以有3种取值:InProc、StateServer?SQLServer(大小 写敏感) 。默认情况下是InProc,也就是将Session保存在进程内(IIS5是aspnet_wp.exe,而IIS6是W3wp.exe),这个进程不 稳定,在某些事件发生时,进程会重起,所以造成了存储在该进程内的Session丢失。

哪些情况下该进程会重起呢?微软的一篇文章告诉了我们:

1、配置文件中processModel标签的memoryLimit属性

2、Global.asax或者Web.config文件被更改

3、Bin文件夹中的Web程序(DLL)被修改

4、杀毒软件扫描了一些.config文件。

更多的信息请参考PRB: Session variables are lost intermittently in ASP.NET applications

ASP.NET Session丢失解决办法:

前面说到的sessionState标签中mode属性可以有三个取值,除了InProc之外,还可以为StateServer、SQLServer。这两种存Session的方法都是进程外的,所以当aspnet_wp.exe重起的时候,不会影响到Session。

现在请将mode设定为StateServer。StateServer是本机的一个服务,可以在系统服务里看到服务名为ASP.NET State Service的服务,默认情况是不启动的。当我们设定mode为StateServer之后,请手工将该服务启动。

这样,我们就能利用本机的StateService来存储Session了,除非电脑重启或者StateService崩掉,否则Session是不会丢的(因Session超时被丢弃是正常的)。

除 此之外,我们还可以将Session通过其他电脑的StateService来保存。具体的修改是这样的。同样还在sessionState标签 中,有个stateConnectionString=’tcpip=127.0.0.1:42424’属性,其中有个ip地址,默认为本机 (127.0.0.1),你可以将其改成你所知的运行了StateService服务的电脑IP,这样就可以实现位于不同电脑上的Asp.net程序互通 Session了。

如果你有更高的要求,需要在服务期重启时Session也不丢失,可以考虑将mode设定成SQLServer,同样需要修改SQLConnectionString属性。关于使用SQLServer保存Session的操作,请访问这里。

在使用StateServer或者SQLServer存储Session时,所有需要保存到Session的对象除了基本数据类型(默认的数据类型,如int、string等)外,都必须序列化。只需将[Serializable]标签放到要序列化的类前就可以了。

如:

[Serializable]

public class MyClass

{

……

}

具体的序列化相关的知识请参这里。

至此,ASP.NET Session丢失问题解决。

关于asp.net Session丢失问题的总结

asp中Session的工作原理:

asp的Session是具有进程依赖性的。ASP Session状态存于IIS的进程中,也就是inetinfo.exe这个程序。所以当inetinfo.exe进程崩溃时,这些信息也就丢失。另外,重起或者关闭IIS服务都会造成信息的丢失。

asp.net Session的实现

ASP.NET的Session是基于HttpModule技术做的,HttpModule可以在请求被处理之前,对请求进行状态控制,由于Session本身就是用来做状态维护的,因此用HttpModule做Session是再合适不过了。

原因1:

bin目录中的文件被改写,asp.net有一种机制,为了保证dll重新编译之后,系统正常运行,它会重新启动一次网站进程,这时就会导致Session丢失,所以如果有access数据库位于bin目录,或者有其他文件被系统改写,就会导致Session丢失

原因2:

文件夹选项中,如果没有打开“在单独的进程中打开文件夹窗口”,一旦新建一个窗口,系统可能认为是新的Session会话,而无法访问原来的Session,所以需要打开该选项,否则会导致Session丢失

原因3:

似乎大部分的Session丢失是客户端引起的,所以要从客户端下手,看看cookie有没有打开

原因4:

Session的时间设置是不是有问题,会不会因为超时造成丢失

原因5:

IE中的cookie数量限制(每个域20个cookie)可能导致session丢失

原因6:

使用web garden模式,且使用了InProc mode作为保存session的方式

ASP.NET Session丢失问题解决经验

1. 判断是不是原因1造成的,可以在每次刷新页面的时候,跟踪bin中某个文件的修改时间

2. 做Session读写日志,每次读写Session都要记录下来,并且要记录SessionID、Session值、所在页面、当前函数、函数中的第几次Session操作,这样找丢失的原因会方便很多

3. 如果允许的话,建议使用state server或sql server保存session,这样不容易丢失

4. 在global.asa中加入代码记录Session的创建时间和结束时间,超时造成的Session丢失是可以在SessionEnd中记录下来的。

5. 如果有些代码中使用客户端脚本,如JavaScript维护Session状态,就要尝试调试脚本,是不是因为脚本错误引起Session丢失。(转帖来源:杰之蓝

[转载]广告影响网站打开速度解决方案

mikel阅读(990)

[转载]广告影响网站打开速度解决方案 – 前端组qianduanzu.com – 博客园.

在自己博客上放广告联盟的广告也是一种收入来源。就我个人知道的有谷歌广告、阿里妈妈、百 度、网易游戏,还有个台湾的BloggerAds,是从无限博客那得知的。然而放入广告后会对网页打开速度有一些影响,有一个致命的问题。比如你把广告放 在了网页顶部或靠上的位置,如果不做处理的话,那要等到这个广告加载完之后才能显示下面的正文内容。如果这个广告加载速度很慢,那这段时间下面的正文就会 空白一片。用户体验非常不好。

前端组在这里提供解决方案,有需要的朋友认真看看下面的代码,相信会对你的博客有好处。

方法一:

使用onload事件,某些广告不支持这个方法,如BloggerAds。Google广告是支持的。BloggerAds请看第二种方法。

HTML(代码放在广告位置)

<div id="sidebarAdBox">
<p>广告正在努力加载中...</p>
<textarea id="sidebarAd" style="display:none;"> 注意:此处放广告联盟提供的广告代码 </textarea>
</div>

JS(放在页面最后面)

1 var sidebarAdBox = document.getElementById('sidebarAdBox'); 2 var sidebarAd = document.getElementById('sidebarAd'); 3 window.onload = function(){ 4     sidebarAdBox.innerHTML = sidebarAd.value; 5 };

方法二:

原理是把广告代码放到页面底部,则就是差不多最后加载了。先把生成的广告隐藏,再把它拿到自己相应的位置,再显示出来,OK!

HTML 1 (代码放在广告位置)

1 <div id="sidebarAdBox">
2 <p>广告正在努力加载中...</p>
3 </div>

HTML 2 (代码要放在页面最后面)

1 <div id="sidebarAd">
2 注意:此处放广告联盟提供的广告代码 3 </div>

JQuery代码(放在页面最后面)(原生态JS代码暂不提供,麻烦..)

1 var $sidebarAdBox = $('#sidebarAdBox'); 2 $('#sidebarAd').appendTo($sidebarAdBox).show(); 3 $sidebarAdBox.find('p').remove();

转载ASP.NET MVC的Model元数据与Model模板:预定义模板

mikel阅读(977)

转载ASP.NET MVC的Model元数据与Model模板:预定义模板 – Artech – 博客园.

通过ModelMetadata表示的Model元数据的一个主要的作用在于为定义在HtmlHelper和 HtmlHelper<TModel>中的模板方法(这些模板方法包括Display/DisplayFor、Editor /EditorFor、DisplayForModel/EditForModel、Lable/LabelFor和DisplayText /DisplayTextFor等)提供用于最终生成HTML的元数据信息。在调用这些方法的时候,如果我们指定了一个具体的通过分部View定义的模 板,或者对应的ModelMetadata的TemplateHint属性具有一个模板名称,会自动采用该模板来生成最终的HTML。如果没有指定模板名 称,则会根据数据类型在预定义的目录下去寻找做模板的分部View。如果找不到,则会利用默认的模板进行HTML的呈现。为了让读者对模板具有一个大概的 认识,我们来做一个简单的实例演示。[本文已经同步到《How ASP.NET MVC Works?》中]

目录
一、 实例演示:通过模板将布尔值显示为RadioButton
二、 预定义模板
EmailAddress
HiddenInput
Html
Text与String
Url
MultilineText
Password
Decimal
Boolean
Collection
Object

一、 实例演示:通过模板将布尔值显示为RadioButton

在默认的情况下,不论是对于编辑模式还是显示模式,一个布尔类型的属性值总是以一个CheckBox的形式呈现出来。我们创建如下一个表示员工的类型Employee,它具有一个布尔类型的属性IsPartTime表示该员工是否为兼职。

 1: public class Employee
 2: {
 3:     [DisplayName("姓名")]
 4:     public string Name { get; set; }
 5:
 6:     [DisplayName("部门")]
 7:     public string Department { get; set; }
 8:
 9:     [DisplayName("是否兼职")]
 10:     public bool IsPartTime { get; set; }
 11: }

如果我们直接调用HtmlHelper<TModel>的EditorForModel方法将一个Employee对象显示在某个将 Employee类型作为Model的强类型View中,下图体现了默认的呈现效果。我们可以看到表示是否为兼职的IsPartTime属性对应着一个 CheckBox。

image

现在我们希望的是将所有布尔类型对象显示为两个RadioButton,具体的显示效果如下图所示。那么我们就可以通过创建一个Model类型为Boolean的View来创建一个模板,使之改变所有布尔类型对象的默认呈现效果。

image

由于我们需要改变的是布尔类型对象在编辑模式下的呈现形式,所以我们需要将作为模板的分布View定义在EditorTemplates目录下,这个目录可以存在于Views/ Shared下,也可以存在于Views/{ControllerName}下。 由于ASP.NET MVC是采用数据类型作为匹配条件来寻找对应的模板的,所以我们需要将分部模板View命名为Boolean。下面的代码片断体现了这个分部试图的整个定 义,我们通过调用HtmlHelper的RadioButton方法将两个布尔值(True/False)映射为对应的RadioButton,并且采 用<table>来布局。

 1: @model bool
 2: <table>
 3:     <tr>
 4:         <td>@Html.RadioButton("",true,Model)是</td>
 5:         <td>@Html.RadioButton("",false,!Model)否</td>
 6:     </tr>
 7: </table>

值得一提的是,我们没有指定RadioButton的名称,而是指定一个空字符串,Html本身会对其进行命名,而命名的依据就是本章介绍的核 心:Model元数据。Employee的IspartTime属性呈现在界面上对应的HTML如下所示,我们可以看到两个类型为radio 的<input>元素的name被自动赋上了对应的属性名称。美中不足的是它们具有相同的ID,如果希望让ID具有唯一性,可以对模板进行更 加细致的定制。

 1: <div class="editor-label"><label for="IsPartTime">是否兼职</label></div>
 2: <div class="editor-field">
 3:     <table>
 4:         <tr>
 5:             <td><input checked="checked" id="IsPartTime" name="IsPartTime" type="radio" value="True" .../></td>
 6:             <td><input id="IsPartTime" name="IsPartTime" type="radio" value="False" /></td>
 7:         </tr>
 8:     </table>
 9: </div>

 

二、预定义模板

上面我们介绍如何通过View的方式创建模板进而控制某种数据类型或者某个目标元素最终在UI界面上的HTML呈现方式,实际上在ASP.NET MVC的内部还定义了一系列的预定义模板。当我们调用HtmlHelper/HtmlHelper<TModel>的模板方法对Model或 者Model的某个成员进行呈现的时候,系统会根据当前的呈现模式(显示模式和编辑模式)和Model元数据获取一个具体的模板(自定义模版或者预定义模 版)。由于Model具有显示和编辑两种呈现模式,所以定义在ASP.NET MVC内部的默认模版分为这两种基本的类型。接下来我们就逐个介绍这些预定义模版以及最终的HTML呈现方式。

EmailAddress

该模板专门针对用于表示Email地址的字符串类型的数据成员,它将目标元素呈现为一个href属性具有“mailto:”前缀的链接(<a></a>)。由于该模板仅仅用于Email地址的显示,所以只在显示模式下有效, 或者说ASP.NET MVC仅仅定义了基于显示模式的EmailAddress模板。为了演示数据在不同模板下的呈现方式,我们定义了如下一个简单的数据类型Model,我们 通过在属性Foo上应用UIHintAttribute特性将模板名称设置为“EmailAddress”。

 1: public class Model
 2: {
 3:     [UIHint("EmailAddress")]
 4:     public string Foo { get; set; }
 5: }

然后在一个基于Model类型的强类型View中,我们通过调用HtmlHelper<TModel>的DisplayFor方法将一个具体的Model对象的Foo属性以显示模式呈现出来。

 1: @model Model
 2: @Html.DisplayFor(m=>m.Foo)

如下的代码片断表示Model的Foo属性对应的HTML,我们可以看到它就是一个针对Email地址的连接。当我们点击该链接的时候,相应的Email编辑软件(比如Outlook)会被开启用于针对目标Email地址的邮件编辑。

 1: <a href="mailto:foo@gmail.com">foo@gmail.com</a>

HiddenInput

关于默认模板HiddenInput我们不应该感到模式,前面介绍的HiddenInputAttribute特性就是将表示Model元数据的ModelMetadata对象的TemplateHint属性设置为HiddenInput。 如果目标元素采用HiddenInput模板,在显示模式下内容会以文本的形式显示;编辑模式下不仅会以文本的方式显示其内容,还会生成一个对应的 type属性为“hidden”的<input>元素。如果表示Model元数据的ModelMetadata对象的 HideSurroundingHtml属性为True(将应用在目标元素上的特性HiddenInputAttribute的DisplayValue 属性设置为False),不论是显示模式还是编辑模式下显示的文本都将消失。

同样以上面定义的数据类型Model为例,我们通过在Foo属性上应用UIHintAttribute特性将模板名称设置为“HiddenInput”。

 1: public class Model
 2: {
 3:     [UIHint("HiddenInput")]
 4:     public string Foo { get; set; }
 5:     public bool Bar { get; set; }
 6:     public decimal Baz { get; set; }
 7: }

然后在一个基于Model类型的强类型View中分别调用HtmlHelper<TModel>的DisplayFor和EditFor方法将一个具体的Model对象的Foo属性以显示和编辑模式呈现出来。

 1: @model Model
 2: @Html.DisplayFor(m=>m.Foo)
 3: @Html.EditorFor(m=>m.Foo)

分别以两种模式呈现出来的Foo属性对应的HTML如下(包含在花括号中的GUID表示属性值)。第一行是针对显示模式的,可以看出最终呈现出来仅 限于表示属性值得文本;而编辑模式对应的HTML中不仅包含属性值文本,还具有一个对应的类型为“hidden”的<input>元素。

 1: {42A1E9B7-2AED-4C8E-AB55-78813FC8C233}
 2: {42A1E9B7-2AED-4C8E-AB55-78813FC8C233}<input id="Foo" name="Foo" type="hidden" value="{42A1E9B7-2AED-4C8E-AB55-78813FC8C233}" />

现在我们对数据类型Model做一下简单修改,将应用在属性Foo上的UIHintAttribute特性替换成HiddenInputAttribute特性,并将其DisplayValue属性设置成False。

 1: public class Model
 2: {
 3:     [HiddenInput(DisplayValue = false)]
 4:     public string Foo { get; set; }
 5:     public bool Bar { get; set; }
 6:     public decimal Baz { get; set; }
 7: }

由于应用在目标元素上的HiddenInputAttribute特新的DisplayValue属性会最终控制对应ModelMetadata的 HideSurroundingHtml属性,而后者控制是否需要生成用于显示目标内容的HTML。所以针对针对的Model定义,最终会生成如下一段 HTML。

 1: <input id="Foo" name="Foo" type="hidden" value="{42A1E9B7-2AED-4C8E-AB55-78813FC8C233}" />

Html

如果目标对象的内容包含一些HTML,并需要在UI界面中原样呈现出来,我们可以采用Html模板。和EmailAddress模板一样,该模板仅限于显示模式。为了演示Html模板对目标内容的呈现方法与默认呈现方式之间的差异,我们定义了如下一个数据类型Model。该数据类型具有两个字符串类型的属性Foo和Bar,其中Foo上面应用UIHintAttribute特性将模板名称设置为“Html”。

 1: public class Model
 2: {
 3:     [UIHint("Html")]
 4:     public string Foo { get; set; }
 5:     public string Bar { get; set; }
 6: }

现在我们创建一个具体的Model对象,并将Foo和Bar设置为一段表示链接的文本(<a href=”www.google.com”>google.com</a>),最终在一个基于Model类型的强类型View中通过 调用HtmlHelper<TModel>的DisplayFor方法将这两个属性以显示模式呈现出来。

 1: @model Model
 2: @Html.DisplayFor(m=>m.Foo)
 3: @Html.DisplayFor(m => m.Bar)

从如下所示的表示Foo和Bar两属性的HTML中我们不难看出:采用Html模板的Foo属性的内容原样输出,而包含在属性Bar中的HTML都进行了相应的编码。

 1: <a href="www.google.com">google.com</a> &lt;a href=&quot;www.google.com&quot;&gt;google.com&lt;/a&gt;

Text与String

不论是在显示模式还是编辑模式,Text和String模板具有相同的HTML呈现方式(实际上在ASP.NET MVC内部,两种模版最终生成的HTML是通过相同的方法产生的)。对于这两种模版来说,目标内容在显示模式下直接以文本的形式输出;而在编辑模式下则对应着一个单行的文本框

为了演示两种模版的同一性,我们对上面定义数据类型Model略作修改,在属性Foo和Bar上应用UIHintAttribute特性并将模版名称分别设置为String和Text。

 1: public class Model
 2: {
 3:     [UIHint("String")]
 4:     public string Foo { get; set; }
 5:     [UIHint("Text")]
 6:     public string Bar { get; set; }
 7: }

然后我们创建一个具体的Model对象,并在一个基于该Model类型的强类型View中通过调用HtmlHelper<TModel>的DisplayFor和EditorFor将两个属性分别以显示和编辑模式呈现出来。

 1: @model Model
 2: @Html.DisplayFor(m=>m.Foo)
 3: @Html.DisplayFor(m => m.Bar)
 4: @Html.EditorFor(m=>m.Foo)
 5: @Html.EditorFor(m => m.Bar)

如下所示的代码片断体现了上述四个元素对应的HTML(“Dummy text …”是Foo和Bar的属性值),可以看到采用了Text和String模板的两个属性在显示和编辑模式下具有相同的呈现方式。编辑模式下输出的类型为 “text”的<input>元素表示CSS特性类型的class属性被设置为“text-box single-line”,意味着这是一个基于单行的文本框。

 1: Dummy text ...
 2: Dummy text ...
 3: <input class="text-box single-line" id="Foo" name="Foo" type="text" value="Dummy text ..." />
 4: <input class="text-box single-line" id="Bar" name="Bar" type="text" value="Dummy text ..." />

值得一提的是,ASP.NET MVC内部采用基于类型的模板匹配机制,对于字符串类型的数据成员,如果没有显式设置采用的模板名称,默认情况下会采用String模板。

Url

与EmailAddress和Html一样,模板Url也仅限于显示模式。对于某个表示为Url的字符串,如果我们希望它最终以一个连接的方式呈现在最终生成的HTML中,我们采用该模板。如下面的代码片断所示,我们通过应用UIHintAttribute特性将模板Url应用到属性Foo中。

 1: public class Model
 2: {
 3:     [UIHint("Url")]
 4:     public string Foo { get; set; }
 5: }

我们创建一个具体的Model对象,并将Foo属性设置为一个表示Url的字符串“http://www.asp.net”,最后通过如下的方式将该属性以显示模式呈现出来。

 1: @model Model
 2: @Html.DisplayFor(m=>m.Foo)

如下面的代码片断所示,该属性最终呈现为一个href属性和文本内容均属性值得连接(<a></a>)。

 1: <a href="http://www.asp.net">http://www.asp.net</a>

MultilineText

一般的字符串在编辑模式下会呈现为一个单行的文本框(类型为“text”的<input>元素),而MultilineText模板会将表示目标内容的字符串通过一个<textarea>元素,该模板仅限于编辑模式。如下面的的代码片断所示,我们通过在字符串类型的Foo属性上应用UIHintAttribute特性将应用的模板设置为MultilineText。

 1: public class Model
 2: {
 3:     [UIHint("MultilineText")]
 4:     public string Foo { get; set; }
 5: }

现在我们创建一个具体的Model对象并通过如下的形式将Foo属性以编辑模式呈现在某个基于Model类型的强类型View中。

 1: @model Model
 2: @Html.EditorFor(m=>m.Foo)

如下所示的代码片断表示Model的Foo属性呈现在UI界面中的HTML(“dummy text …”是是Foo的属性值),我们可以看到这是一个< textarea >元素。表示CSS样式类型的class属性被设置为“text-box multi-line”,意味着它是以多行的效果呈现。

 1: <textarea class="text-box multi-line" id="Foo" name="Foo">dummy text ...</textarea>

Password

对于表示密码字符串来说,在编辑模式下应该呈现为一个类型为“password”的<input>元素,使我们输入的内容以掩码的形式 显示出来以保护密码的安全性。在这种情况下我们可以采用Password模板,该模板和MultilineText一样也仅限于编辑模式。如下面的代码片 断所示,我们在Model的Foo属性上应用UIHintAttribute特性将模式名称设置为“Password”。

 1: public class Model
 2: {
 3:     [UIHint("Password")]
 4:     public string Foo { get; set; }
 5: }

我们创建一个具体的Model对象,并通过如下的形式将Foo属性以编辑模式呈现在某个基于Model的强类型View中。

 1: @model Model
 2: @Html.EditorFor(m=>m.Foo)

该Foo属性最终会以如下的形式通过一个类型为“Password”的<input>元素呈现出来,表示CSS样式类型的class属性被设置为“text-box single-line password”,意味着呈现效果为一个单行的文本框。

 1: <input class="text-box single-line password" id="Foo" name="Foo" type="password" value="" />

Decimal

如果采用Decimal模板,代表目标元素的数字不论其小数位数是多少,都会最终被格式化为两位小数。在显示模式下,被格式化的数字直接以文本的形 式呈现出来;在编辑模式下则对应着一个单行的文本框架。如下面的代码片断所示,我们在数据类型Model中定义了两个对象类型属性Foo和Bar,上面应 用了UIHintAttribute特性并将模板名称指定为“Decimal”。

 1: public class Model
 2: {
 3:     [UIHint("Decimal")]
 4:     public object Foo { get; set; }
 5:
 6:     [UIHint("Decimal")]
 7:     public object Bar { get; set; }
 8: }

我们创建一个具体的Model对象,将它的Foo和属性分别设置为整数123和浮点数3.1415(4位小数),最终通过如下的形式将它们以显示和编辑的模式呈现在一个基于Model类型的强类型View中。

 1: @model Model
 2: @Html.DisplayFor(m=>m.Foo)
 3: @Html.DisplayFor(m=>m.Bar)
 4: @Html.EditorFor(m=>m.Foo)
 5: @Html.EditorFor(m =>m.Bar)

上述四个元素在最终呈现的UI界面中对应着如下的HTML,我们可以看到最终显示的都是具有两位小数的数字。

 1: 123.00
 2: 3.14
 3: <input class="text-box single-line" id="Foo" name="Foo" type="text" value="123.00" />
 4: <input class="text-box single-line" id="Bar" name="Bar" type="text" value="3.14" />

Boolean

通过本章最开始的实例演示我们知道一个布尔类型的对象在编辑模式下会以一个类型为“checkbox”的<input>元素的形式呈 现,实际上在显示模式下它依然对应着这么一个元素,只是其disabled属性会被设置为True使之处于只读状态。布尔类型的这种默认呈现方式源自 “Boolean”模板默认被使用。

当布尔类的目标元素以编辑模式进行呈现的时候,除了生成一个一个类型为“checkbox”的<input>元素之外还会附加产生一个 类型为“hidden”的<input>元素。如下面的代码片断所示,这个hidden元素具有与CheckBox相同的名称,但是值为 False,它存在的目的在于当CheckBox没有被勾选的情况下通过对应的hidden元素向服务区提交相应的值(False),因为没有被勾选的 CheckBox的值是不会包含在请求中的。

 1: <input id="Foo" name="Foo" type="checkbox" value="true" />
 2: <input name="Foo" type="hidden" value="false" />

Boolean和String、Decimal以及后面我们介绍的Object一样属于是基于CLR类型的模板。由于ASP.NET在内部采用基于类型的模板匹配策略,如果没有显示设置采用的模板类型,相应类型的元素会默认采用与之匹配的模板。

Collection

顾名思义,Collection模板用于集合类型的目标元素的显示与编辑。对应采用该模板的类型为集合(实现了IEnumerable接口)的目标 元素,在调用HtmlHelper或者HtmlHelper<TModel>以显示或者编辑模式对其进行呈现的时候,会遍历其中的每个元素, 并根据基于集合元素的Model元数据决定对其的呈现方法。同样一我们定义的数据类型Model为例,我们按照如下的方式将它的Foo属性类型改为对象数 组,上面应用了UIHintAttribute特性并将模板名称 设置为“Collection”。

 1: public class Model
 2: {
 3:     [UIHint("Collection")]
 4:     public object[] Foo { get; set; }
 5: }

然后我们按照如下的方式创建一个包含三个对象的数组,作为数据元素的三个对象类型为别是数字、字符串和布尔,然后将该数组作为Foo属性创建一个具体的Model对象。

 1: object[] foo = new object[]
 2: {
 3:     123.00,
 4:     "dummy text ...",
 5:     true
 6: };
 7: Model model = new Model { Foo = foo };

在一个基于Model类型的强类型View中,我们分别调用HtmlHelper<TModel>的DisplayFor和EditorFor方法将上面创建的Model对象的Foo属性以显示和编辑模式呈现出来。

 1: @model Model
 2: @Html.DisplayFor(m=>m.Foo)
 3: @Html.EditorFor(m=>m.Foo)

Model对象的Foo属性最终呈现出来的HTML如下所示,我们可以看到不论是显示模式还是编辑模式,基本上就是对集合元素呈现的HTML的组合而已。

 1: 123dummy text ...<input checked="checked" class="check-box" disabled="disabled" type="checkbox" />
 2: <input class="text-box single-line" data-val="true" data-val-number="The field Double must be a number." data-val-required="The Double field is required." id="Foo_0_" name="Foo[0]" type="text" value="123" />
 3: <input class="text-box single-line" id="Foo_1_" name="Foo[1]" type="text" value="dummy text ..." />
 4: <input checked="checked" class="check-box" data-val="true" data-val-required="The Boolean field is required." id="Foo_2_" name="Foo[2]" type="checkbox" value="true" />
 5: <input name="Foo[2]" type="hidden" value="false" />

Object

我们说过,ASP.NET 内部采用基于类型的模板匹配策略,如果通过ModelMetadata对象表示的Model元数据不能找到一个具体的模板,最终都会落到Object模板上。 Object模板对目标对象的呈现方式很简单,它通过ModelMetadata的Proeprties属性得到所有基于属性的Model元数据。针对每 个表示属性Model元数据的ModelMetadata,它会根据DisplayName或者属性名称生成一个标签(实际上是一个内部文本为显示名称 的<div>元素),然后根据元数据将属性值以显示或者编辑的模式呈现出来。

 1: public class Address
 2: {
 3:     [DisplayName("省")]
 4:     public string Province { get; set; }
 5:     [DisplayName("市")]
 6:     public string City { get; set; }
 7:     [DisplayName("区")]
 8:     public string District { get; set; }
 9:     [DisplayName("街道")]
 10:     public string Street { get; set; }
 11: }

针对上面定义得这个表示地址的Address。现在我们创建一个具体的Address对象并通过如下的方式调用HtmlHelper<TModel>的DisplayForModel方法将它呈现在以此作为Model的View中。

 1: @model Address
 2: @Html.DisplayForModel()

从如下所示的HTML中我们可以看出作为Model的Address对象的所有属性都以显示模式呈现出来,而在前面还具有相应的标签。

 1: <div class="display-label"></div>
 2: <div class="display-field">江苏省</div>
 3: <div class="display-label"></div>
 4: <div class="display-field">苏州市</div>
 5: <div class="display-label"></div>
 6: <div class="display-field">工业园区</div>
 7: <div class="display-label">街道</div>
 8: <div class="display-field">星湖街328号</div>

值得一提的是,Object模板在对属性进行遍历的过程中,不论是显示模式还是编辑模式,只会处理非复杂类型。也就是如果属性成员是一个复杂类型(不能支持从字符串类型的转换),它不会出现在最终生成的HTML中。

 1: public class Contact
 2: {
 3:     [DisplayName("姓名")]
 4:     public string Name { get; set; }
 5:     [DisplayName("电话")]
 6:     public string PhoneNo { get; set; }
 7:     [DisplayName("Email地址")]
 8:     public string EmailAddress { get; set; }
 9:     [DisplayName("联系地址")]
 10:     public Address Address { get; set; }
 11: }

通过上面的代码片断,我们定义了一个表示联系人的数据类型Contact,它具有一个类型的Address的同名属性。现在我们创建一个具体的 Contact对象,并 对包括Address 属性在内的所有属性进行初始化,然后通过如下的方式通过调用HtmlHelper<TModel>的DisplayForModel方法将它 呈现在以此作为Model的View中。

 1: @model Contact
 2: @Html.DisplayForModel()

从如下所示的HTML可以看出,Contact的数据成员Address由于是复杂类型,其内容并不会呈现出来。

 1: <div class="display-label">姓名</div>
 2: <div class="display-field">张三</div>
 3: <div class="display-label">电话</div>
 4: <div class="display-field">1234567890</div>
 5: <div class="display-label">Email地址</div>
 6: <div class="display-field">zhangsan@gmail.com</div>

我们可以有两种方式解决这个问题,其实就是通过为Address类型定义各类专门的模板用于地址信息的显示和编辑,通过 UIHintAttribute特性将此模板名称应用到Contact的Address属性上,然后调用DisplayFor/EditorFor将该属 性呈现出来。另一种就是按照类似如如下的方式手工将负责类型属性成员呈现出来。

 1: @model Contact
 2: @Html.DisplayForModel()
 3: <div class="display-label">@Html.DisplayName("Address")</div>
 4: <div class="display-field">@(Model.Address.Province + Model.Address.City + Model.Address.District+ Model.Address.Street)</div>

 

ASP.NET MVC的Model元数据与Model模板:预定义模板
ASP.NET MVC的Model元数据与Model模板:模板的获取与执行策略
ASP.NET MVC的Model元数据与Model模板:将ListControl引入ASP.NET MVC

[转载]Sql Server参数化查询之where in和like实现详解(续)

mikel阅读(971)

[转载]Sql Server参数化查询之where in和like实现详解(续) – 懒惰的肥兔 – 博客园.

在上一篇Sql Server参数化查询之where in和like实现详解中介绍了在SQL Server使用参数化查询where in的几种实现方案,遗漏了xml和表值参数,这里做一个补充

文章导读

方案5使用xml参数

方案6 使用表值参数TVP

6种实现方案总结

方案5 使用xml参数

SQL server xml类型参数不熟悉的童鞋需要先了解下XQuery概念,这里简单提下XQuery 是用来从 XML 文档查找和提取元素及属性的语言,简单说就是用于查询xml的语言说到这就会牵着到XPath,其实XPath是XQuery的一个子集,XQuery 1.0 和 XPath 2.0 共享相同的数据模型,并支持相同的函数和运算符,XPath的方法均适用于XQuery,假如您已经学习了 XPath,那么学习 XQuery 也不会有问题。详见http://www.w3school.com.cn/xquery/xquery_intro.asp

XQuery概念了解后需要进一步了解下SQL Server对xml的支持函数,主要为query()nodes()exist()value()modify() ,详见http://msdn.microsoft.com/zh-cn/library/ms190798.aspx

使用xml方式实现where in时有两种实现方式,使用value和exist,在这里推荐使用exist方法,msdn是这样描述的:

D.使用 exist() 方法而不使用 value() 方法

由于性能原因,不在谓词中使用 value() 方法与关系值进行比较,而改用具有 sql:column() 的 exist()

http://msdn.microsoft.com/zh-cn/library/ms178030.aspx

使用xml的value方法实现(不推荐)

DataTable dt = new DataTable();
using (SqlConnection conn = new SqlConnection(connectionString))
{
string xml = @"

1
2
5
";
SqlCommand comm = conn.CreateCommand();
//不推荐使用value方法实现,性能相对exist要低
comm.CommandText = @"select * from Users
where exists
(
select 1 from @xml.nodes('/root/UserID') as T(c)
where T.c.value('text()[1]','int')= Users.UserID
)";

//也可以这样写,结果是一样的
//comm.CommandText = @"select * from Users
// where UserID in
// (
// select T.c.value('text()[1]','int') from @xml.nodes('/root/UserID') as T(c)
// )
comm.Parameters.Add(new SqlParameter("@xml", SqlDbType.Xml) { Value = xml });
using (SqlDataAdapter adapter = new SqlDataAdapter(comm))
{
adapter.SelectCommand = comm;
adapter.Fill(dt);
}
}

使用xml的exist方法实现(推荐)

DataTable dt = new DataTable();
using (SqlConnection conn = new SqlConnection(connectionString))
{
string xml = @"

1
2
5
";
SqlCommand comm = conn.CreateCommand();

//使用xml的exist方法实现这样能够获得较高的性能
comm.CommandText = @"select * from Users where @xml.exist('/root/UserID[text()=sql:column(""UserID"")]')=1";
comm.Parameters.Add(new SqlParameter("@xml", SqlDbType.Xml) { Value = xml });
using (SqlDataAdapter adapter = new SqlDataAdapter(comm))
{
adapter.SelectCommand = comm;
adapter.Fill(dt);
}
}

列举下不同xml结构的查询方法示例,在实际使用中经常因为不同的xml结构经常伤透了脑筋

DataTable dt = new DataTable();
using (SqlConnection conn = new SqlConnection(connectionString))
{
string xml = @"

1

2

5

";
SqlCommand comm = conn.CreateCommand();

//不推荐使用value方法实现,性能相对exist要低
comm.CommandText = @"select * from Users
where UserID in
(
select T.c.value('UserID[1]','int') from @xml.nodes('/root/User') as T(c)
)";
//也可以这样写,结果是一样的
//comm.CommandText = @"select * from Users
// where exists
// (
// select 1 from @xml.nodes('/root/User') as T(c)
// where T.c.value('UserID[1]','int') = Users.UserID
// )";
comm.Parameters.Add(new SqlParameter("@xml", SqlDbType.Xml) { Value = xml });
using (SqlDataAdapter adapter = new SqlDataAdapter(comm))
{
adapter.SelectCommand = comm;
adapter.Fill(dt);
}
}
DataTable dt = new DataTable();
using (SqlConnection conn = new SqlConnection(connectionString))
{
string xml = @"

1

2

5

";
SqlCommand comm = conn.CreateCommand();
//使用xml的exist方法实现这样能够获得较高的性能
comm.CommandText = @"select * from Users where @xml.exist('/root/User[UserID=sql:column(""UserID"")]')=1";

comm.Parameters.Add(new SqlParameter("@xml", SqlDbType.Xml) { Value = xml });
using (SqlDataAdapter adapter = new SqlDataAdapter(comm))
{
adapter.SelectCommand = comm;
adapter.Fill(dt);
}
}

使用xml参数时需要注意点:

1.不同于SQL语句默认不区分大小写,xml的XQuery表达式是严格区分大小写的,所以书写时一定注意大小写问题

2.使用exist时sql:column() 中的列名须使用双引号,如sql:column(“UserID”),若非要使用单引号需要连续输入两个单引号 sql:column(”UserID”)

3.不管是where in或是其他情况下使用xml查询时能用exist(看清楚了不是sql里的exists)方法就用exist方法,我们不去刻意追求性能的优化,但能顺手为之的话何乐而不为呢。

方案6 使用表值参数(Table-Valued Parameters 简称TVP Sql Server2008开始支持)

按照msdn描述TVP参数在数据量小于1000时有着很出色的性能,关于TVP可以参考 http://msdn.microsoft.com/en-us/library/bb510489.aspx

这里主要介绍如何使用TVP实现DataTable集合传参实现where in

1.使用表值参数,首先在数据库创建表值函数

create type IntCollectionTVP as Table(ID int)

2.表值函数创建好后进行C#调用,

注意点:

1.需要SqlParameter中的SqlDbType设置为SqlDbType.Structured然后需要设置TypeName为在数据库中创建的表值函数名,本示例中为IntCollectionTVP

2.构造的DataTabel列数必须和表值函数定义的一样,具体列名随意,无需和表值函数定义的列名一致,数据类型可以随意,但还是建议和表值类型定义的保持一致,一来省去隐式类型转换,二来可以在初始化DataTabel时就将不合法的参数过滤掉

3.建议定义tvp的时候最好查询条件里的类型和tvp对应字段类型保持一致,这样可以避免隐式类型转换带来的性能损失

DataTable resultDt = new DataTable();
using (SqlConnection conn = new SqlConnection(connectionString))
{
SqlCommand comm = conn.CreateCommand();
comm.CommandText = @"select * from Users(nolock)
where exists
(
select 1 from @MyTvp tvp
where tvp.ID=Users.UserID
)";

//构造需要传参的TVP DataTable
DataTable tvpDt = new DataTable();
//为表添加列,列数需要和表值函数IntCollectionTVP保值一致,列名可以不一样
tvpDt.Columns.Add("myid", typeof(int));
//添加数据
tvpDt.Rows.Add(1);
tvpDt.Rows.Add(2);
tvpDt.Rows.Add(3);
tvpDt.Rows.Add(4);
//这里的TypeName对应我们定义的表值函数名
comm.Parameters.Add(new SqlParameter("@MyTvp", SqlDbType.Structured) { Value = tvpDt, TypeName = "IntCollectionTVP" });
using (SqlDataAdapter adapter = new SqlDataAdapter(comm))
{
adapter.SelectCommand = comm;
adapter.Fill(resultDt);
}
}

总结:

至此,一共总结了6六种where参数化实现,分别如下

1.使用CHARINDEX或like实现where in 参数化

2.使用exec动态执行SQl实现where in 参数化

3.为每一个参数生成一个参数实现where in 参数化

4.使用临时表实现where in 参数化

5.使用xml参数实现where in 参数化

6.使用表值参数(TVP)实现where in 参数化

其中前4种在Sql Server参数化查询之where in和like实现详解 一文中进行了列举和示例

6种方法,6种思路,

其中方法1 等于完全弃用了索引,若无特殊需要不建议采用,

方法2 本质上合拼SQL没啥区别与其用方法2自欺其人还不如直接拼接SQL来的实惠

方法3 受参数个数(做多2100个参数)限制,而且若传的参数过多性能如何有待验证,可以酌情使用

方法4 示例中采用的临时表,其实可以换成表变量性能也许会更好些,不过写法上有些繁琐,可以具体的封装成一个函数会好些(推荐)

方法5 使用xml传参,既然有这种类型说明性能上应该还不错,其它会比拼接SQL好很多,使用上也还比较方便,不过需要开发人员对xml查询有一定了解才行(推荐)

方法6 tvp方式sql server2008以后才可以使用,很好很强大,若只为where in 的话可以定义几个tvp where in问题就很容易解决了,而且是强类型也更容易理解(推荐)

不好去评论具体那种方法最好,还是那句老话合适的最好。

注:此文章属懒惰的肥兔原创,版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,如有问题,可以通过lzrabbit@126.com联系我,非常感谢。