[JQuery]JQuery上传插件Uploadify使用详解

mikel阅读(820)

转载:http://www.cnblogs.com/oec2003/archive/2010/01/06/1640027.html

Uploadify是JQuery的一个上传插件,实现的效果非常不错,带进度显示。不过官方提供的实例时php版本的,本文将详细介绍Uploadify在Aspnet中的使用,您也可以点击下面的链接进行演示或下载。

首先按下面的步骤来实现一个简单的上传功能。

1 创建Web项目,命名为JQueryUploadDemo,从官网上下载最新的版本解压后添加到项目中。

2 在项目中添加JQueryUploadHandler.ashx文件用来处理文件的上传。

3 在项目中添加UploadFile文件夹,用来存放上传的文件。

进行完上面三步后项目的基本结构如下图:

2010-01-05_094439

4 Default.aspx的html页的代码修改如下:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Uploadify</title>
<link href="JS/jquery.uploadify-v2.1.0/example/css/default.css"
rel="stylesheet" type="text/css" />
<link href="JS/jquery.uploadify-v2.1.0/uploadify.css"
rel="stylesheet" type="text/css" />
<script type="text/javascript"
src="JS/jquery.uploadify-v2.1.0/jquery-1.3.2.min.js"></script>
<script type="text/javascript"
src="JS/jquery.uploadify-v2.1.0/swfobject.js"></script>
<script type="text/javascript"
src="JS/jquery.uploadify-v2.1.0/jquery.uploadify.v2.1.0.min.js"></script>
<script type="text/javascript">
$(document).ready(function()
{
$("#uploadify").uploadify({
'uploader': 'JS/jquery.uploadify-v2.1.0/uploadify.swf',
'script': 'UploadHandler.ashx',
'cancelImg': 'JS/jquery.uploadify-v2.1.0/cancel.png',
'folder': 'UploadFile',
'queueID': 'fileQueue',
'auto': false,
'multi': true
});
});
</script>
</head>
<body>
<div id="fileQueue"></div>
<input type="file" name="uploadify" id="uploadify" />
<p>
<a href="javascript:$('#uploadify').uploadifyUpload()">上传</a>|
<a href="javascript:$('#uploadify').uploadifyClearQueue()">取消上传</a>
</p>
</body>
</html>

 

5  UploadHandler类的ProcessRequest方法代码如下:

public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Charset = "utf-8";
HttpPostedFile file = context.Request.Files["Filedata"];
string  uploadPath =
HttpContext.Current.Server.MapPath(@context.Request["folder"])+"\\";
if (file != null)
{
if (!Directory.Exists(uploadPath))
{
Directory.CreateDirectory(uploadPath);
}
file.SaveAs(uploadPath + file.FileName);
//下面这句代码缺少的话,上传成功后上传队列的显示不会自动消失
context.Response.Write("1");
}
else
{
context.Response.Write("0");
}
}

 

6 运行后效果如下图:

2010-01-05_095617

7 选择了两个文件后,点击上传,就可以看到UploadFile文件夹中会增加这两个文件。

上面简单地实现了一个上传的功能,依靠函数uploadify实现,uploadify函数的参数为json格式,可以对json对象的key值的 修改来进行自定义的设置,如multi设置为true或false来控制是否可以进行多文件上传,下面就来介绍下这些key值的意思:

uploader : uploadify.swf 文件的相对路径,该swf文件是一个带有文字BROWSE的按钮,点击后淡出打开文件对话框,默认值:uploadify.swf。
script :   后台处理程序的相对路径 。默认值:uploadify.php
checkScript :用来判断上传选择的文件在服务器是否存在的后台处理程序的相对路径
fileDataName :设置一个名字,在服务器处理程序中根据该名字来取上传文件的数据。默认为Filedata
method : 提交方式Post 或Get 默认为Post
scriptAccess :flash脚本文件的访问模式,如果在本地测试设置为always,默认值:sameDomain 
folder :  上传文件存放的目录 。
queueID : 文件队列的ID,该ID与存放文件队列的div的ID一致。
queueSizeLimit : 当允许多文件生成时,设置选择文件的个数,默认值:999 。
multi : 设置为true时可以上传多个文件。
auto : 设置为true当选择文件后就直接上传了,为false需要点击上传按钮才上传 。
fileDesc : 这个属性值必须设置fileExt属性后才有效,用来设置选择文件对话框中的提示文本,如设置fileDesc为“请选择rar doc pdf文件”,打开文件选择框效果如下图:

2010-01-05_220920

fileExt : 设置可以选择的文件的类型,格式如:'*.doc;*.pdf;*.rar' 。
sizeLimit : 上传文件的大小限制 。
simUploadLimit : 允许同时上传的个数 默认值:1 。
buttonText : 浏览按钮的文本,默认值:BROWSE 。
buttonImg : 浏览按钮的图片的路径 。
hideButton : 设置为true则隐藏浏览按钮的图片 。
rollover : 值为true和false,设置为true时当鼠标移到浏览按钮上时有反转效果。
width : 设置浏览按钮的宽度 ,默认值:110。
height : 设置浏览按钮的高度 ,默认值:30。
wmode : 设置该项为transparent 可以使浏览按钮的flash背景文件透明,并且flash文件会被置为页面的最高层。 默认值:opaque 。
cancelImg :选择文件到文件队列中后的每一个文件上的关闭按钮图标,如下图:

2010-01-05_220626

 

上面介绍的key值的value都为字符串或是布尔类型,比较简单,接下来要介绍的key值的value为一个函数,可以在选择文件、出错或其他一些操作的时候返回一些信息给用户。

onInit : 做一些初始化的工作

onSelect :选择文件时触发,该函数有三个参数

  • event:事件对象。
  • queueID:文件的唯一标识,由6为随机字符组成。
  • fileObj:选择的文件对象,有name、size、creationDate、modificationDate、type 5个属性。
  •  

代码如下:

$(document).ready(function()
{
$("#uploadify").uploadify({
'uploader': 'JS/jquery.uploadify-v2.1.0/uploadify.swf',
'script': 'UploadHandler.ashx',
'cancelImg': 'JS/jquery.uploadify-v2.1.0/cancel.png',
'folder': 'UploadFile',
'queueID': 'fileQueue',
'auto': false,
'multi': true,
'onInit':function(){alert("1");},
'onSelect': function(e, queueId, fileObj)
{
alert("唯一标识:" + queueId + "\r\n" +
"文件名:" + fileObj.name + "\r\n" +
"文件大小:" + fileObj.size + "\r\n" +
"创建时间:" + fileObj.creationDate + "\r\n" +
"最后修改时间:" + fileObj.modificationDate + "\r\n" +
"文件类型:" + fileObj.type
);
}
});
});  

当选择一个文件后弹出的消息如下图:

2010-01-05_225323

onSelectOnce :在单文件或多文件上传时,选择文件时触发。该函数有两个参数event,data,data对象有以下几个属性:

  • fileCount:选择文件的总数。
  • filesSelected:同时选择文件的个数,如果一次选择了3个文件该属性值为3。
  • filesReplaced:如果文件队列中已经存在A和B两个文件,再次选择文件时又选择了A和B,该属性值为2。
  • allBytesTotal:所有选择的文件的总大小。

 

onCancel : 当点击文件队列中文件的关闭按钮或点击取消上传时触发。该函数有event、queueId、fileObj、data四个参数,前三个参数同onSelect 中的三个参数,data对象有两个属性fileCount和allBytesTotal。

  • fileCount:取消一个文件后,文件队列中剩余文件的个数。
  • allBytesTotal:取消一个文件后,文件队列中剩余文件的大小。

 

onClearQueue 当调用函数fileUploadClearQueue时触发。有event和data两个参数,同onCancel 中的两个对应参数。

onQueueFull :当设置了queueSizeLimit并且选择的文件个数超出了queueSizeLimit的值时触发。该函数有两个参数event和queueSizeLimit。

onError :当上传过程中发生错误时触发。该函数有event、queueId、fileObj、errorObj四个参数,其中前三个参数同上,errorObj对象有type和info两个属性。

  • type:错误的类型,有三种‘HTTP’, ‘IO’, or ‘Security’
  • info:错误的描述

 

onOpen :点击上传时触发,如果auto设置为true则是选择文件时触发,如果有多个文件上传则遍历整个文件队列。该函数有event、queueId、fileObj三个参数,参数的解释同上。

onProgress :点击上传时触发,如果auto设置为true则是选择文件时触发,如果有多个文件上传则遍历整个文件队列,在onOpen之后触发。该函数有 event、queueId、fileObj、data四个参数,前三个参数的解释同上。data对象有四个属性percentage、 bytesLoaded、allBytesLoaded、speed:

  • percentage:当前完成的百分比
  • bytesLoaded:当前上传的大小
  • allBytesLoaded:文件队列中已经上传完的大小
  • speed:上传速率 kb/s

 

onComplete:文件上传完成后触发。该函数有四个参数event、queueId、fileObj、response、data五个参数,前三个参数同上。response为后台处理程序返回的值,在上面的例子中为1或0,data有两个属性fileCount和speed

  • fileCount:剩余没有上传完成的文件的个数。
  • speed:文件上传的平均速率 kb/s

 

onAllComplete:文件队列中所有的文件上传完成后触发。该函数有event和data两个参数,data有四个属性,分别为:

  • filesUploaded :上传的所有文件个数。
  • errors :出现错误的个数。
  • allBytesLoaded :所有上传文件的总大小。
  • speed :平均上传速率 kb/s

 

相关函数介绍

在上面的例子中已经用了uploadifyUpload和uploadifyClearQueue两个函数,除此之外还有几个函数:

uploadifySettings:可以动态修改上面介绍的那些key值,如下面代码

$('#uploadify').uploadifySettings('folder','JS');

如果上传按钮的事件写成下面这样,文件将会上传到uploadifySettings定义的目录中

<a href="javascript:$('#uploadify').uploadifySettings('folder','JS');$('#uploadify').uploadifyUpload()">上传</a>

uploadifyCancel:该函数接受一个queueID作为参数,可以取消文件队列中指定queueID的文件。

$('#uploadify').uploadifyCancel(id);

 

花了一个晚上,终于写完了,对jQuery这个上传插件也基本了解了,希望对大家有所帮助,不对之处还望大家指正。

作者:oec2003(水杯)
出处:http://oec2003.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[MVC]使用自定义ViewHelper来简化Asp.net MVC view的开发

mikel阅读(960)

转载:http://www.cnblogs.com/CareySon/archive/2010/01/05/1639825.html

      从开发者的角度来看,创建ASP.NET MVC的View是一件很爽的事,因为你可以精确控制最终生成的HTML。具有讽刺意味的是不得不写出每一行HTML代码同时也是ASP.NET MVC的View中让人不爽的地方。让我用我的一个经历来告诉我创建ASP.NET MVC view Helpers背后灵感的由来。由一小部分开发人员(包括我)和一个CSS设计人员(我们叫他Ricky)组成的小组,开始了一个新的ASP.NET MVC的项目,在项目开发过程中;我给页面添加了一些TextBox和一些其他元素,我check-in了我的代码,直到回家我也没再想起过这事。隔夜早 晨,刚上班时我就从CSS设计那里收到一封邮件来通知我我必须按照他的CSS指导方针来写HTML,比如说对于textbox,必须遵循以下规则:

  • 每个textbox必须内嵌在li标签中
  • 每一个textbox都必须有一个label标签的for属性与之对应
  • textbox必须使用input标签并设置type属性为text

      对于这些要求我一一照做并修改我的代码符合了后两条规则,但我忘了关于li的 指导方针,我很快更新了页面并提交了我的代码。几天后,项目又推进了很多,Ricky来到我的办公桌前并让我看看我所做的改变。打开页面,他开始一一列举 那些我不遵循它的UI规定的地方,有很多地方我都忽视了因为我甚至不知道这些指导方针的存在.在他指出这些后,我想:一定会有方法可以让我们两个人都如愿 以偿.对于我来说只是需要html标签的id,对于Ricky来说他需要我的HTML符合规范来让他的CSS文件能够选择到合适的html。所以我们引入 了view helper.

       在我用ASP.NET MVC时我注意到我自己写了很多纯Html,比如div和span,同时伴随使用了很多System.Web.Mvc.HtmlHelper来生成html,比如说一个输入名字的textbox:

<li>
<label for="FirstName">First name</label>
<%= Html.TextBox("FirstName") %>
<%= Html.ValidationMessage("FirstName", "*") %>
</li> 

   我就想,是不是能有一种方法来将上面的所有代码融合在一起呢。这样不仅让我编程更加轻松,而且再也不用担心Ricky给我设置的条条框框了。理想的情况下会满足以下标准:

  1. 容易执行
  2. 重用性好
  3. 强制执行某些标准(比如Ricky的)
  4. 和标准的HtmlHelper扩展方法用起来没太大区别
  5. 容易使用

    在我们进入执行这个的细节之前如果你感觉这听起来像又回到了Web Form时代,那就错了。view helper仅仅是在创建HTML的时候起辅助作用,而不是将HTML进行抽象。我关心的只是HTML在页面中的显示效果以及使用JavaScript的 行为更轻松.而不是textbox是否放入li中,当我需要创建一个textbox时,我只需在view中放入如下代码:

<% Html.NewText("FirstName", "First name"); %> 

     我想声明我仅仅是想将创建HTML延迟到另一个类中。使用View helper我可以轻松做到这一点。首先我们先来看标准的HtmlHelper扩展方法如何做到这一点.

     Html helper有两种实现用法,大多数的使用方法都会如下:

<%= Html.TextBox("FirstName") %> 

     而还有一种用法和声明一个form元素很相似:

<% using (Html.BeginForm()) { %>
<!--  Other elements here-->
<% } %> 

     上面两种方法的主要区别是Html.TextBox仅仅返回一个string来注入到view中。这也是为什么使用<%=而不是标准的的代码块。而另一种以对象作为返回类型的方法更老练许多,比如,System.Web.Mvc.Html.MvcForm, 这个对象放入using语句.对象被创建时一些HTML就会被注入到view中(严格说:并不是对象创建时,但很接近)还有一些事在对象被回收时将 html注入view(也就是碰到”}”符号时).使用这种方法的好处是可以在using语句之间插入代码。这使它的能力无疑比那些仅仅返回一个字符串注 入页面的方式要强大许多。

      所以,我选择第二种方法来实现我的View Helpers.所以HtmlHelper扩展方法会集成IViewObject接口并返回我创建的对象。类图如下:

     1

      可以看到,IViewObject实现了System.IDisposable接口。这使实现如前面所提到和Html.BeginForm的使用方法类似所必须的。IViewObject有两个方法,StartViewEndView.这两个方法分别在对象创建时和对象回收时被调用.为了让这些对象的创建更加容易我创建了一个抽象类来处理:执行方法,回收对象和在合适的时候调用EndView方法。类图如下:

   2

上图中的抽象类完整代码如下:

public abstract class AbstractHtmlViewObject : IViewObject
{
private bool mDisposed;
public AbstractHtmlViewObject(ViewRequestContext requestContext, string name)
{
if (requestContext == null)
{ throw new ArgumentNullException("requestContext"); }
ViewRequestContext = requestContext;
Name = name;
}
public IViewRequestContext RequestContext
{
get;
protected set;
}
#region IViewObject Members
public object Attributes { get; set; }
public string Name { get; set; }
public abstract void StartView();
public abstract void EndView();
#endregion
// based on System.Web.Mvc.HtmlHelper.GetModelStateValue 
public object GetModelStateValue(string key, Type destinationType)
{
object result = null;
ModelState modelState;
if (ViewRequestContext.HtmlHelper.ViewData.ModelState.TryGetValue(
key, out modelState))
{
result = modelState.Value.ConvertTo(destinationType, null);
}
return result;
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!mDisposed)
{
mDisposed = true;
EndView();
}
}
#endregion
} 

     如你所见上面AbstractHtmlViewObject对象不仅满足了最上面提到的列表(Ricky那段里),还包含了一些辅助类更容易扩展的东西。 也就是它包含的一个属性:RequestContext,这个属性可以帮助我们很容易创建HTML和扩展方法GetModelStateValue,我们 会在后面详细讲述GetModelStateValue的使用方法。我们会在后面讲述RequestContext的细节,这里我们先看看如何创建我们先 前讨论的那个textbox。

     我们已经知道需要创建的textbox有一个文本值与之对应:

  1. 文本值在label标签中
  2. 可选的值放在Textbox中
  3. 可选的验证信息(validation message)

      如果上面3个条件都能满足,肯定也能满足我们在part1里的那5个条件.还有一些锦上添花的是可以通过属性来指定textbox是否包裹在li标签内以 及textbox是否是readonly模式.这样我们便能更好的在view page中代码复用。下面的代码包含所有HtmlText(译者按:继承AbstractHtmlViewObject对象,在part1的类图中)对象所有的属性:

private readonly string mLabelText;
private readonly bool mCreateLabel;
private readonly object mValue;
private readonly string mValidationMessage;
private readonly bool mCreateValidationMessage;
private readonly bool mCreateLi;
private readonly bool mReadonly;
public HtmlText(
ViewRequestContext requestContext, string name, string labelText, objec
string validationMessage, bool @readonly, bool createLi, object attribu
: base(requestContext, name)
{
mLabelText = labelText;
mCreateLabel = !string.IsNullOrEmpty(mLabelText);
mValidationMessage = validationMessage;
mCreateValidationMessage = !string.IsNullOrEmpty(validationMessage);
mCreateLi = createLi;
mReadonly = @readonly;
Attributes = attributes;
object valueToAssign = value;
if (valueToAssign == null)
{
// see if the ModelState has a value for this 
valueToAssign = GetModelStateValue(name, typeof(string));
}
mValue = valueToAssign;
} 

     在构造函数中,我们我们存入一系列私有变量中并初始化了会在StartView方法内使用的一个bool类型,除此之外你可以发现这里开始使用 GetModelStateValue方法.目前为止我们先不过多讨论这个方法,这个方法会在后面提到。在参数传入构造器之前我们注意到:

  1. value参数的类型是object
  2. object类型的attributes参数被传入

    之所以把Value参数定义为object类型是因为这样可以使用户更容易使用并且和ASP.Net MVC Helpers的执行方式保持一致。attributes参数可以被调用者来扩展生成的HTML。比如说,你想将textbox的maxlength属性设置为5,你只需要传入匿名类型”new {maxlength=5}“.input标签会将这个匿名类型转换为HTML属性maxlength=5.这同时也符合Asp.net MVC中HTML Helper现有扩展方法的使用方式.每一个View helper对象都应该支持这种行为以便具有更大的灵活性.在这个类中剩下的两个方法就是从父类继承来的StartViewEndView方法了.

     StartViewEndView的定义如下:

public override void StartView()
{
HttpResponseBase httpResponse = RequestContext.HttpResponse;
TagBuilder htmlLiTagBuilder = new TagBuilder("li");
if (mCreateLi)
{
httpResponse.Write(htmlLiTagBuilder.ToString(TagRenderMode.StartTag));
}
// write out label if provided 
if (mCreateLabel)
{
TagBuilder labelTag = new TagBuilder("label");
labelTag.Attributes.Add("for", Name);
labelTag.SetInnerText(mLabelText);
httpResponse.Write(labelTag.ToString(TagRenderMode.Normal));
}
string stringValue = string.Empty;
if (this.mValue != null)
{
stringValue = Convert.ToString(this.mValue, CultureInfo.CurrentCulture);
}
if (this.mReadonly)
{
TagBuilder textTag = new TagBuilder("span");
textTag.AddCssClass("readonly-text");
textTag.SetInnerText(
Convert.ToString(this.mValue, CultureInfo.CurrentCulture));
httpResponse.Write(textTag.ToString(TagRenderMode.Normal));
}
else
{
// Use MVC helpers to create the actual text box 
httpResponse.Write(RequestContext.HtmlHelper.TextBox(
Name, this.mValue, Attributes));
}
if (this.mCreateLi)
{
httpResponse.Write(htmlLiTagBuilder.ToString(TagRenderMode.EndTag));
}
}
public override void EndView()
{
// Not needed for this element 
} 

     在StartView方法中有很多值得注意的地方,让我们逐个讨论。首先是我们使用System.Web.Mvc.TagBuilder来生成HTML, 而不是直接写HTML标签。TagBuilder只能在Asp.net MVC中使用并且我推荐在生成HTML中必须使用TagBuilder而不是直接写HTML标签,下面是TagBuilder的类图:

1

   

下表是TagBuilder中一些方法的说明:

名称 描述
AddCssClass 加入css的class名称,如果class已经存在,则后来加入的会和原来的class一起生效
MergeAttribute 这个方法用于添加或者更新tag的属性,这个方法有一个接受replaceExisting参数的重载,默认情况下已经定义的属性不会被重载。
MergeAttributes 同上,只是可以在一个方法内添加或更新所有属性.
SetInnerText 设置标签内的文本
ToString 被重载。用于生成相应的html代码,TagRenderMode枚举类型会控制如何生成HTML标签.

 

在上面表格的ToString那行,TagRenderMode枚举用于控制TagBuilder生成HTML标签的方式,TagRenderModel如下所示:

TagRenderModel 结果示例
Normal <div name=”Sample01”>Some content here</div>
StartTag <div name=”Sample01”>
EndTag </div>
SelfClosing <div name=”Sample01” />
   

 

    根据你想创建的HTML标签和你如何使用它,你会发现使用TagRenderModel可以创建出任何你想创建出的HTML.在前面提到的StartView方法内你会发现TagRenderModel被依据不同的条件设置成StartTag,Normal和EndTag等不同的的类型.如果你给InnerHTML属性赋值并用StartTagEndTag生成它你必须要记住InnerHtml不会被自动生成,你还必须显式的使用InnerHtml属性本身。下面我们来讨论如何创建HtmlHelper扩展方法。

   在前面我们说到了创建HtmlText类的方方面面。包括为HtmlText创建的扩展方法.这些扩展方法包括直接被View调用的那些扩展方法。下面代码展示了HtmlText的几种不同的构造函数:

public static class HtmlHelperExtensions
{
#region Textbox
public static IViewObject NewText(
this HtmlHelper htmlHelper, string name)
{
return NewText(htmlHelper, name, null);
}
public static IViewObject NewText(
this HtmlHelper htmlHelper, string name, string labelText)
{
return NewText(htmlHelper, name, labelText, null);
}
public static IViewObject NewText(
this HtmlHelper htmlHelper, string name, string labelText, object value)
{
return NewText(htmlHelper, name, labelText, value, null, false, true, null);
}
public static IViewObject NewText(
this HtmlHelper htmlHelper, string name, string labelText, object value,
string validationMessage, bool @readonly, bool createLi, object attributes)
{
IViewObject viewObject = new HtmlText(
new ViewRequestContext(htmlHelper), name, labelText, value,
validationMessage, @readonly, createLi, attributes);
viewObject.StartView();
return viewObject;
}
#endregion
//NOTE: SOME CONTENT OMITTED FROM THIS SNIPPET 
} 

   NewText方法有四个不同版本的重载,这些重载都属于 System.Web.Mvc.HtmlHelper的扩展方法,只有最后一个方法用于真正的起作用,而其他的方法都是这个方法的封装以便让用户使用起来 更简单.上面的代码中HtmlText对象通过传入适当的参数来初始化,而view是通过StartView方法来初始化,在StartView中被调用 的HtmlText会返回合适的对象动态的将Html注入View.现在让我们来看看如何在view中使用这些方法。

     前面我们已经创建了在View中可使用的HtmlText对象,现在就可以使用了。在前面我们提到,如果想要创建一个textbox来满足Ricky的标准,我必须写如下代码:

<li>
<label for="FirstName">First name</label>
<%= Html.TextBox("FirstName") %>
<%= Html.ValidationMessage("FirstName", "*") %>
</li> 

    现在通过使用HtmlHelper,我们可以把代码简化如下:

<% Html.NewText("FirstName", "First name"); %> 

    上面两种方法所生成的Html是完全相同的,我们实现了前面设定的目标。从今往后就可以使用这个Helper来简化Asp.net MVC view的开发了。上面代码中并没有用到EndView方法.下面我们来研究一个更复杂一些的HTML的构造—radio button,看是如何实现的

     使用Asp.net MVC来创建一组radio button,代码一般如下:

<li>
<div class="option-group" id="GenderContainer">
<label for="Gender">Gender</label>
<% foreach (SelectListItem item in Model.GenderList)
{ %>
<%= Html.RadioButton(item.Text, item.Value)%>
<span><%= item.Text%></span>
<% } %>
</div>
</li> 

    上面代码是从AddContactClass.aspx view中节选的,所有代码可以从这篇文章的网站下载,上面代码中ContactController通过Model.GenderList属性来集中返回代码:

public ActionResult AddContactClassic()
{
AddContactModel addModel = InternalGetInitialAddModel();
return View(addModel);
}
private AddContactModel InternalGetInitialAddModel()
{
string maleString = Gender.Male.ToString();
string femaleString = Gender.Female.ToString();
IList<SelectListItem> genderRadioButtons = new List<SelectListItem>()
{
new SelectListItem { Text = maleString, Value = maleString },
new SelectListItem { Text = femaleString, Value = femaleString }
};
AddContactModel model = new AddContactModel { GenderList = genderRadioButtons };
return model;
} 

生成的HTML效果图如下:

1

在上面创建radio button的代码中有很多掩盖了元素真实意图(译者按:比如说为什么我们这么写HTML,是为了满足Ricky的标准吗?)的部分,比如说:外层的 div和内层的span是为了label而包裹文本.而如果我们需要一组radio button时只需要声明一下并指定相关的值那不是更爽吗?下面我们创建HtmlRadioButtonGroup view helper,它可以满足我们只声明并指定相关值就能创建出相应的html,使用HtmlRadioButtonGroup,我们可以将前面的radio button精简如下:

<% Html.NewRadioButtonGroup("Gender", Model.GenderList); %> 

上面代码中,我们可以从更高的视角来创建Html,清楚的这段代码的作用而不是关注Html的细节。下面来创建一个替我们生成HTML的helper,也就是为:HtmlRadioButtonGroup类,下面代码展示了这个类唯一的构造函数和它的字段:

private readonly List<SelectListItem> mSelectList;
private readonly bool mCreateLi;
public HtmlRadioButtonGroup(
ViewRequestContext requestContext, string name,
IEnumerable<SelectListItem> selectList, bool createLi, object attributes)
: base(requestContext, name)
{
mSelectList = new List<SelectListItem>();
if (selectList != null)
{
mSelectList.AddRange(selectList);
}
mCreateLi = createLi;
Attributes = attributes;
}

看上去是不是和我们先前的HtmlText对象的构造器很像?它的构造函数为通过传参的方式将RequestContext变得可用。并且通过构造 函数为所有的字段进行初始化,这也意味着这个类是在StartView方法中(译者按:因为RequestContext方法在StartView中可以 传入)的,下面代码是StartView的完全版本:

public override void StartView()
{
HttpResponseBase httpResponse = RequestContext.HttpResponse;
TagBuilder liTagBuilder = new TagBuilder("li");
if (mCreateLi)
{
httpResponse.Write(liTagBuilder.ToString(TagRenderMode.StartTag));
}
TagBuilder divTag = new TagBuilder("div");
divTag.AddCssClass("option-group");
divTag.MergeAttribute("name", Name);
if (Attributes != null)
{
divTag.MergeAttributes(new RouteValueDictionary(Attributes));
}
TagBuilder labelTag = new TagBuilder("label");
labelTag.MergeAttribute("for", Name);
labelTag.SetInnerText(Name);
httpResponse.Write(labelTag.ToString(TagRenderMode.Normal));
httpResponse.Write(divTag.ToString(TagRenderMode.StartTag));
// Write out the radio buttons, let the MVC Helper do the hard work here 
foreach (SelectListItem item in this.mSelectList)
{
string text = !string.IsNullOrEmpty(item.Text)
? item.Text
: item.Value;
httpResponse.Write(RequestContext.HtmlHelper.RadioButton(
Name, item.Value, item.Selected));
// Note: Because we are using HtmlHelper.RadioButton the <input>  
//       elements will have duplicate ids 
//       See: http://forums.asp.net/t/1363177.aspx 
//       In order to avoid this we could do this ourselves here 
TagBuilder spanTag = new TagBuilder("span");
spanTag.SetInnerText(text);
httpResponse.Write(spanTag.ToString(TagRenderMode.Normal));
}
httpResponse.Write(divTag.ToString(TagRenderMode.EndTag));
if (this.mCreateLi)
{
httpResponse.Write(liTagBuilder.ToString(TagRenderMode.EndTag));
}
} 

这里的想法和HtmlText类如初一撤,那就是:所有的HTML代码都在StartView方法中生成。因此这里StartView方法创建了一 些HTML tag,并遍历mSelectList中的元素并通过Asp.net MVC自带的RadioButton扩展方法为每一个元素生成一个RadioButton。在重用这些方法时最好先重写这些方法(译者按:看上面代码注 释)。

从上面代码中的注释可以看出,使用HtmlHelper.RadioButton扩展方法有一个明显的bug,就是id和name用的是同一个值, 这里因为name属性本来就应该为RadioButton设置成相同的这样他们便可以逻辑上连成一组,但是id属性是每个元素唯一拥有,这里解决这个 bug的方法是不用这个方法,但在这里为了简单起见我们先使用这个方法.上面创建的两个Html helper对象都没有用到EndView方法,你可以已经开怀疑这个方法为什么存在,在接下来的HtmlFieldSet的Helper我会给你展示 EndView的用途

    接上文..前面我们已经创建好了HtmlFieldSet,现在,为了让HtmlHelper的扩展方法可以使用这个类,还需要创建一个方法:NewHtmlFieldSet

public static IViewObject NewHtmlFieldSet(
this HtmlHelper htmlhelper, string name, string title, object attributes)
{
IViewObject viewObject = new HtmlFieldSet(
new ViewRequestContext(htmlhelper), name, title, attributes);
viewObject.StartView();
return viewObject;
} 

    这个方法的实现和前面所提到的那些没有上面不同,都是传入相应参数并返回view object,在View被初始化时返回这个对象,View首先在初始化时使用返回的View object,更确切点说,返回的IViewObject会在using语句中被view使用,例子如下:

<% using (Html.NewHtmlFieldset("FieldsetName", "My Fieldset", null))
{ %>
<li>
<label for="FirstName">FirstName</label>
<span id="FirstName"><%= Html.Encode(Model.FirstName) %></span>
</li>
<% } %> 

    对应生成的HTML代码如下:

<fieldset name="FieldsetName">
<legend>My Fieldset</legend>
<ol>
<li>
<label for="FirstName">FirstName</label>
<span id="FirstName">Sayed</span>
</li>
</ol>
</fieldset> 

EndView方法输出了最后的三个结尾标签(</li>,</ol>,</fieldset>),达到了我 们的预期,现在就可以使用view helper来创建fieldset以及包含在内的legend,以便达到更好的可理解和可维护性。下面来看view helper是如何简化view的开发的。

这篇文章中附带的示例代码时全功能版本,每一个页面都有两个版本-使用view helper和不使用view helper.不适用view helper的版本全部手动创建HTML,而使用view helper的版本包括了我们先前创建的3个view helper,让我们来进行简单的比较,从源码中找到AddContactClassic.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<Sedodream.Web.ViewHelper.Models.AddContactModel>"
%>
<%@ Import Namespace="Sedodream.Web.Common.Contact" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Add Contact Classic
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Add Contact Classic</h2>
<%= Html.ValidationSummary("Errors exist") %>
<ol>     <li>
<span class="success-message"><%= ViewData["SuccessMessage"]%></span>
</li>
</ol>
<% using (Html.BeginForm())
{ %>
<fieldset>
<legend>Account Information</legend>
<ol>
<li>
<label for="FirstName">First name</label>
<%= Html.TextBox("FirstName") %>
<%= Html.ValidationMessage("FirstName", "*") %>
</li>
<li>
<label for="LastName">Last name</label>
<%= Html.TextBox("LastName") %>
<%= Html.ValidationMessage("LastName", "*") %>
</li>
<li>
<label for="Email">Email</label>
<%= Html.TextBox("Email")%>
<%= Html.ValidationMessage("Email", "*")%>
</li>
<li>
<label for="Phone">Phone</label>
<%= Html.TextBox("Phone")%>
<%= Html.ValidationMessage("Phone", "*")%>
</li>
<li>
<div class="option-group" id="GenderContainer">
<label for="Gender">Gender</label>
<% foreach (SelectListItem item in Model.GenderList)
{ %>
<%= Html.RadioButton(item.Text, item.Value)%>
<span><%= item.Text%></span>
<% } %>
</div>
</li>
<li>
<input type="submit" value="Add contact" />
</li>
</ol>
</fieldset>
<% } %>
</asp:Content> 

上面代码尽管简单,但仍然包含多达59行代码,而且看起来十分丑陋,下面的版本是使用我们自定义的view helper,让我们来看看包含在AddContactNew.aspx内的新版本:

<%@ Page
Inherits="System.Web.Mvc.ViewPage<Sedodream.Web.ViewHelper.Models.AddContactModel>"
Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Title="" %>
<%@ Import Namespace="Sedodream.Web.Common.View" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Add Contact New
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Add Contact New</h2>
<%= Html.ValidationSummary("Errors exist") %>
<ol>
<li>
<span class="success-message"><%= Model.SuccessMessage %></span>
</li>
</ol>
<% using (Html.BeginForm())
{ %>
<fieldset>
<legend>Account Information</legend>
<ol>
<% Html.NewText("FirstName", "First name"); %>
<% Html.NewText("LastName", "Last name"); %>
<% Html.NewText("Email", "Email"); %>
<% Html.NewText("Phone", "Phone"); %>
<% Html.NewRadioButtonGroup("Gender", Model.GenderList); %>
<li>
<input type="submit" value="Add contact" />
</li>
</ol>
</fieldset>
<% } %>
</asp:Content> 

 

使用view helper的版本html大大减少(只有39行)而且更容易理解,这里需要注意view引入了Sedodream.Web.Common.View命名 空间,这使view helper扩展方法所必须的.Sedodream.Web.Common.View命名空间包含在另一个程序集中,这样更方便你在整个小组内进行分发, 使用View helper所带来的可理解性只是使用它所带来好处的其中之一,它还会带来以下好处:

  1. View更清爽,更容易理解
  2. 小组内遵循某些标准更容易
  3. 在修改时需要改变的地方更少
  4. 可利用回传的model state辅助生成代码

在前面我们提到了GetModelStateValue方法的使用。这个方法用于给HTML元素赋上它自己从View里回传的值,而在view helper内可以给生成的html元素赋值.下面代码片段是System.Web.Mvc.Html.InputExtensions源文件中的一部 分,这里用来展示GetModelStateValue的用法:

case InputType.Radio:
if (!usedModelState) {
string modelStateValue = htmlHelper.GetModelStateValue(
name, typeof(string)) as string;
if (modelStateValue != null) {
isChecked = String.Equals(
modelStateValue, valueParameter, StringComparison.Ordinal);
usedModelState = true;
}
} 

上面代码先检查model state来看radio button是否被创建,如果radio button已经存在就可以查看radio button是否已经被选中,当你创建自定义view helper时,你最好也在合适的地方支持类似(可以获取当前html的元素)的功能。前面的HtmlText view helper已经说明了这一点。

 

文章到此已经将创建自定义view helper的方方面面都讲到了。

原文链接:http://mvcviewhelpers.codeplex.com/

translated by CareySon

[Memcached]利用memcached构建高性能的Web应用程序

mikel阅读(1141)


转载:http://it.dianping.com/use-memcached-to-build-high-performance-web-application.htm

利用memcached构建高性能的Web应用程序

面临的问题

对于高并发高访问的Web应用程序来说,数据库存取瓶颈一直是个令人头疼的问题。特别当你的程序架构还是建立在单数据库模式,而一个数据池连接数峰值已经达到500的时候,那你的程序运行离崩溃的边缘也不远了。很多小网站的开发人员一开始都将注意力放在了产品需求设计上,缺忽视了程序整体性能,可扩展性等方面的考虑,结果眼看着访问量一天天网上爬,可突然发现有一天网站因为访问量过大而崩溃了,到时候哭都来不及。所以我们一定要未雨绸缪,在数据库还没罢工前,想方设法给它减负,这也是这篇文章的主要议题。

大家都知道,当有一个request过来后,web服务器交给app服务器,app处理并从db中存取相关数据,但db存取的花费是相当高昂的。特别是每次都取相同的数据,等于是让数据库每次都在做高耗费的无用功,数据库如果会说话,肯定会发牢骚,你都问了这么多遍了,难道还记不住吗?是啊,如果app拿到第一次数据并存到内存里,下次读取时直接从内存里读取,而不用麻烦数据库,这样不就给数据库减负了?而且从内存取数据必然要比从数据库媒介取快很多倍,反而提升了应用程序的性能。

因此,我们可以在web/app层与db层之间加一层cache层,主要目的:1. 减少数据库读取负担;2. 提高数据读取速度。而且,cache存取的媒介是内存,而一台服务器的内存容量一般都是有限制的,不像硬盘容量可以做到TB级别。所以,可以考虑采用分布式的cache层,这样更易于破除内存容量的限制,同时又增加了灵活性。

Memcached 介绍

Memcached是开源的分布式cache系统,现在很多的大型web应用程序包括facebook,youtube,wikipedia,yahoo等等都在使用memcached来支持他们每天数亿级的页面访问。通过把cache层与他们的web架构集成,他们的应用程序在提高了性能的同时,还大大降低了数据库的负载。
具体的memcached资料大家可以直接从它的官方网站[1]上得到。这里我就简单给大家介绍一下memcached的工作原理:

Memcached处理的原子是每一个(key,value)对(以下简称kv对),key会通过一个hash算法转化成hash-key,便于查找、对比以及做到尽可能的散列。同时,memcached用的是一个二级散列,通过一张大hash表来维护。

Memcached有两个核心组件组成:服务端(ms)和客户端(mc),在一个memcached的查询中,mc先通过计算key的hash值来确定kv对所处在的ms位置。当ms确定后,客户端就会发送一个查询请求给对应的ms,让它来查找确切的数据。因为这之间没有交互以及多播协议,所以memcached交互带给网络的影响是最小化的。

举例说明:考虑以下这个场景,有三个mc分别是X,Y,Z,还有三个ms分别是A,B,C:

设置kv对
X想设置key=”foo”,value=”seattle”
X拿到ms列表,并对key做hash转化,根据hash值确定kv对所存的ms位置
B被选中了
X连接上B,B收到请求,把(key=”foo”,value=”seattle”)存了起来

获取kv对
Z想得到key=”foo”的value
Z用相同的hash算法算出hash值,并确定key=”foo”的值存在B上
Z连接上B,并从B那边得到value=”seattle”
其他任何从X,Y,Z的想得到key=”foo”的值的请求都会发向B

Memcached服务器(ms)

内存分配

默认情况下,ms是用一个内置的叫“块分配器”的组件来分配内存的。舍弃c++标准的malloc/free的内存分配,而采用块分配器的主要目的是为了避免内存碎片,否则操作系统要花费更多时间来查找这些逻辑上连续的内存块(实际上是断开的)。用了块分配器,ms会轮流的对内存进行大块的分配,并不断重用。当然由于块的大小各不相同,当数据大小和块大小不太相符的情况下,还是有可能导致内存的浪费。

同时,ms对key和data都有相应的限制,key的长度不能超过250字节,data也不能超过块大小的限制 — 1MB。
因为mc所使用的hash算法,并不会考虑到每个ms的内存大小。理论上mc会分配概率上等量的kv对给每个ms,这样如果每个ms的内存都不太一样,那可能会导致内存使用率的降低。所以一种替代的解决方案是,根据每个ms的内存大小,找出他们的最大公约数,然后在每个ms上开n个容量=最大公约数的instance,这样就等于拥有了多个容量大小一样的子ms,从而提供整体的内存使用率。

缓存策略

当ms的hash表满了之后,新的插入数据会替代老的数据,更新的策略是LRU(最近最少使用),以及每个kv对的有效时限。Kv对存储有效时限是在mc端由app设置并作为参数传给ms的。

同时ms采用是偷懒替代法,ms不会开额外的进程来实时监测过时的kv对并删除,而是当且仅当,新来一个插入的数据,而此时又没有多余的空间放了,才会进行清除动作。

缓存数据库查询
现在memcached最流行的一种使用方式是缓存数据库查询,下面举一个简单例子说明:

App需要得到userid=xxx的用户信息,对应的查询语句类似:

“Select * FROM users Where userid = xxx”

App先去问cache,有没有“user:userid”(key定义可预先定义约束好)的数据,如果有,返回数据;如果没有,App会从数据库中读取数据,并调用cache的add函数,把数据加入cache中。

当取的数据需要更新,app会调用cache的update函数,来保持数据库与cache的数据同步。

从上面的例子我们也可以发现,一旦数据库的数据发现变化,我们一定要及时更新cache中的数据,来保证app读到的是同步的正确数据。当然我们可以通过定时器方式记录下cache中数据的失效时间,时间一过就会激发事件对cache进行更新,但这之间总会有时间上的延迟,导致app可能从cache读到脏数据,这也被称为狗洞问题。(以后我会专门描述研究这个问题)

数据冗余与故障预防

从设计角度上,memcached是没有数据冗余环节的,它本身就是一个大规模的高性能cache层,加入数据冗余所能带来的只有设计的复杂性和提高系统的开支。

当一个ms上丢失了数据之后,app还是可以从数据库中取得数据。不过更谨慎的做法是在某些ms不能正常工作时,提供额外的ms来支持cache,这样就不会因为app从cache中取不到数据而一下子给数据库带来过大的负载。

同时为了减少某台ms故障所带来的影响,可以使用“热备份”方案,就是用一台新的ms来取代有问题的ms,当然新的ms还是要用原来ms的IP地址,大不了数据重新装载一遍。

另外一种方式,就是提高你ms的节点数,然后mc会实时侦查每个节点的状态,如果发现某个节点长时间没有响应,就会从mc的可用server列表里删除,并对server节点进行重新hash定位。当然这样也会造成的问题是,原本key存储在B上,变成存储在C上了。所以此方案本身也有其弱点,最好能和“热备份”方案结合使用,就可以使故障造成的影响最小化。

Memcached客户端(mc)

Memcached客户端有各种语言的版本供大家使用,包括java,c,php,.net等等,具体可参见memcached api page[2]。
大家可以根据自己项目的需要,选择合适的客户端来集成。

缓存式的Web应用程序架构
有了缓存的支持,我们可以在传统的app层和db层之间加入cache层,每个app服务器都可以绑定一个mc,每次数据的读取都可以从ms中取得,如果没有,再从db层读取。而当数据要进行更新时,除了要发送update的SQL给db层,同时也要将更新的数据发给mc,让mc去更新ms中的数据。

假设今后我们的数据库可以和ms进行通讯了,那可以将更新的任务统一交给db层,每次数据库更新数据的同时会自动去更新ms中的数据,这样就可以进一步减少app层的逻辑复杂度。如下图:

不过每次我们如果没有从cache读到数据,都不得不麻烦数据库。为了最小化数据库的负载压力,我们可以部署数据库复写,用slave数据库来完成读取操作,而master数据库永远只负责三件事:1.更新数据;2.同步slave数据库;3.更新cache。如下图:

以上这些缓存式web架构在实际应用中被证明是能有效并能极大地降低数据库的负载同时又能提高web的运行性能。当然这些架构还可以根据具体的应用环境进行变种,以达到不同硬件条件下性能的最优化。

未来的憧憬
Memcached的出现可以说是革命性的,第一次让我们意识到可以用内存作为存储媒介来大规模的缓存数据以提高程序的性能。不过它毕竟还是比较新的东西,还需要很多有待优化和改进的地方,例如:
如何利用memcached实现cache数据库,让数据库跑在内存上。这方面,tangent software 开发的memcached_engine[3]已经做了不少工作,不过现在的版本还只是处于实验室阶段。
如何能方便有效的进行批量key清理。因为现在key是散列在不同的server上的,所以对某类key进行大批量清理是很麻烦的。因为memcached本身是一个大hash表,是不具备key的检索功能的。所以memcached是压根不知道某一类的key到底存了多少个,都存在哪些server上。而这类功能在实际应用中却是经常用到。

交流
作者也是刚接触memcached方面的内容,所以严格来说还只是个新手,班门弄斧地说了一大通,如果有不对的地方,还请各位大侠多多指正。当然,如果有什么和memcached方面有关的问题或建议,也欢迎和我联系。
联系Email: rongwei.yang@dianping.com

参考
[1]. Memcached website: http://danga.com/memcached/
[2]. Memcached API Page: http://danga.com/memcached/apis.bml
[3]. memcached_engine: http://tangent.org/506/memcache_engine.html

[C#].NET 4.0新特性-- Corrupted State Exceptions

mikel阅读(1453)

转载:http://www.cnblogs.com/jujusharp/archive/2010/01/05/handling-corrupted-state-exceptions-in-dot-net-4-0.html

作为程序员,我想很多人应该都有过跟异常打交道的经历。而且相信也有很多人也都写过catch(Exception e){//blabla}这种把所有未知异常一股脑儿捕获并处理掉的代码吧。不管是为敷衍客户也好,让程序继续运行以避免糟糕的用户体验也罢,在微软眼 中,这种处理方式都是不对滴,特别是当你的程序是作为一个插件寄存在别的程序如VS,Offcie中时,这种情况下对有些严重的异常如访问冲突我们更应当 是让程序结束而不是继续运行。然而很多时候,我们并不清楚哪些异常是严重的,哪些是可以让程序继续运行的,因为在.NET 4.0以前,CLR会很忠实的把所有大大小小的异常一股脑儿的都抛给程序员处理。不过这个问题在4.0以后会得到很好的解决了。因为对有些严重的会引起进 程崩溃的异常的处理以后会由CLR来统一处理而不再交给我们可怜的程序员了。下面我将对这种异样处理做一些简单的介绍。

为什么需要Corrupted State Exceptions

异常有大有小,小的如字符串为空,这些一般是用户输入问题,它不会引起整个程序或者系统中相关进程出现崩溃的情况;大的如访问冲突异常,这可能是你 的程序在做一些可能会引起操作系统崩溃的事情,这种异常一般都比较严重,一般如果出现这种异常,通常程序应该做的是结束当前进程,然后老老实实向用户报告 你犯傻了并提示他重启程序。不过在.NET 4.0以前,CLR是很相信程序员不会搞出一些诸如catch(Exception e){return;}这种不负责任的代码的,因此它不分轻重缓急,只要是异常,它统统都会抛出来,这里面不仅仅有托管代码的异常,也有一些.NET程序 员不太好看懂的COM和WIN32异常。CLR相信程序员在捕获异常的时候会只处理他们清楚的异常,但很多时候,作为开发人员,由于上面有老板,下面有客 户,我们真的很难做人,想想如果老板动不动就听又客户抱怨他们只不过点了两下按钮程序就报错然后结束了,他还能给你加薪么?虽然很多时候我们清楚我们的代 码不会出问题,但我们很难保证天时地利人和样样俱全,为了给老板和客户一个交代,这时候很多人都会选择去捕获所有的异常,然后记录下异常信息,然后程序继 续彪悍的跑下去。

看似一些都很完美,客户不会再像以前那么频繁的抱怨程序down掉,老板也就高兴了。但有人不高兴。小的未知异常当然不会捅大的篓子,但对有些可能 导致程序甚至操作系统崩溃的异常如果不中断程序的话可能影响的就是一大片了。这个时候客户可能不会抱怨你,但他会抱怨微软出了个烂操作系统,一天到晚蓝 屏,或者他会抱怨微软的Office或者IE太烂,他只不过加载了一个插件,结果整个Outlook就报错崩掉了。你是省事了,但微软得来被黑锅,而且他 还不知道这个黑锅里面到底是咋回事。

当然上面是玩笑,不过不管怎样,从程序安全和稳定的角度来看catch(Exception e)确实不是一个好的编程习惯,然而木已成舟,既然无法避免程序员偷懒,微软只能采取一些补救措施了,这里他们在CLR 4中添加了新的异常处理机制,自4.0以后,CLR不会主动给你抛出所有异常了,对于那些它认为是危险的,可能导致进程崩溃的异常它会标记为 Corrupted State Exception并自己处理掉而不是抛给程序员来做,如AccessViolationException这种继承自SystemException的 异常就会被当做Corrupted State Exception来处理。不过这里要注意的是,仅仅异常类型是可能会危险级别的异常还不够,CLR还会判断抛出异常的所有者,如果它发现是由操作系统抛 出的访问冲突则会认为这是状态崩溃异常,但如果异常是由用户代码抛出,则CLR不会对其做特殊处理,它仍然会像以前一样将其正常抛出。

如何继续捕获Corrupted State Exceptions

那么CLR包了这块的异常处理是不是意味着以后我们程序员就没得选只能老老实实向用户报告我们的产品不行,然后让老板炒我们鱿鱼了呢?那些.NET 4.0以前发布的,处处是漏洞的产品我们怎么处理?

虽然微软不再那么相信程序员是负责人的人,但它也做那么绝。虽然默认.NET 4.0以后CLR会处理这些异常,程序员也不用再操心这些危险的异常了。但你仍然可以继续你以往敷衍上司的做法。并且微软还提供了两种方式。

首先对于以往的程序,微软提供了两种选择:

1. 如果你想把以往旧的代码在.NET Framework 4.0下编译但又不想改代码的话,你可以在你的程序的配置文件中添加一个新的节 点:legacyCorruptedState­­ExceptionsPolicy=true,它使得你的代码仍能按照以前处理异常的方式来继续运行。

2. 如果你不想有任何改变,直接把以前已经编译好的程序在.NET Framework 4.0下运行则不需要任何改变,CLR会保证所有的异常仍然按照以往的方式处理。

其次,对于那些使用了.NET Framework 4.0 但又想自己处理这些导致程序状态崩溃的异常,微软同样提供了选择,他们在.NET 4.0中增加了一个新的命名空间:System.Runtime.ExceptionServices,这里面有个特性类叫做HandleProcessCorruptedStateExceptionsAttribute,你只需要在相应方法上添加这个属性,CLR就会把所有的异常处理交给你做,就像以前一样。e.g.

01 // This program runs as part of an automated test system so you need
02 // to prevent the normal Unhandled Exception behavior (Watson dialog).
03 // Instead, print out any exceptions and exit with an error code.
04 [HandledProcessCorruptedStateExceptions]
05 public static int Main()
06 {
07     try
08     {
09         // Catch any exceptions leaking out of the program
10         CallMainProgramLoop();
11     }
12     catch (Exception e) // We could be catching anything here
13     {
14         // The exception we caught could have been a program error
15         // or something much more serious. Regardless, we know that
16         // something is not right. We'll just output the exception
17         // and exit with an error. We won't try to do any work when
18         // the program or process is in an unknown state!
19         System.Console.WriteLine(e.Message);
20         return 1;
21     }
22     return 0;
23   }
24 当然要注意的是这个特性只能应用在方法上。

总结

异常处理常常是程序员心中的一块心病,尽管微软认为自己得为纵容程序员滥用异常捕获负责然后添加了这个新的异常处理机制,不过在他们看来,那种 catch(Exception e)的行为仍然是不对的。他们认为异常的出现表明当前程序的状态出现了问题,而程序员应当清楚这些错误的状态所造成的后果,所以程序员应当捕获具体的异常 并作出正确的处理,而不是因为偷懒或者省事去简单处理所有异常。

参考资料:

Handling Corrupted State Exceptions        作者:Andrew Pardoe     

原文地址:http://msdn.microsoft.com/en-us/magazine/dd419661.aspx

作者:Sean Zhu
出处:http://jujusharp.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[C#]一款基于C#的网络爬虫程序

mikel阅读(1288)

这是一款用 C# 编写的网络爬虫, 发布在:http://nwebcrawler.codeplex.com/

主要特性有:

  • 可配置:线程数、线程等待时间,连接超时时间,可爬取文件类型和优先级、下载目录等。
  • 状态栏显示统计信息:排入队列URL数,已下载文件数,已下载总字节数,CPU使用率和可用内存等。
  • 有偏好的爬虫:可针对爬取的资源类型设置不同的优先级。
  • 健壮性:十几项URL正规化策略以排除冗余下载、爬虫陷阱避免策略的使用等、多种策略以解析相对路径等。
  • 较好的性能:基于正则表达式的页面解析、适度加锁、维持HTTP连接等。

参见下面的截图:

image

今后有空可能加入的特性:

新特性 介绍
爬取文件用Berkeley DB存储 提高性能: 常用操作系统不善于处理大量小文件
基于URL Ranking的优先级队列 主题爬虫: 机器学习算法对链接与主题相关度进行评估,并按照得出的优先级顺序进行爬取
爬虫礼仪 遵循爬虫禁止协议、以及避免对服务器资源的过度使用等
性能优化 用UDP取代封装好的HttpWebRequest/Response
DNS缓存
异步的DNS地址解析
硬盘缓存或内存数据库以避免频繁的磁盘寻道
分布式爬虫以扩展单机能力(CPU、内存和硬盘访问)

[Java]Play!Framework 简介

mikel阅读(1074)

转载:http://www.playframework.org/documentation/1.0/overview

Play framework overview

The Play framework is a clean alternative to bloated Enterprise Java stacks. It focuses on developers productivity and targets RESTful architectures. Play is a perfect companion for agile software development.

The Play framework’s goal is to ease web applications development while sticking with Java. Let’s see how this is possible.

Wanna see some code?
Check Five cool things you can do with play, or start directly with hello world tutorial.

A Java framework without the pain

Play is a pure Java framework and allows you to keep your preferred development tools and libraries. If you already use Java as a development platform you don’t need to switch to another language, another IDE and other libraries. Just switch to a more productive Java environment!

Fix the bug and hit Reload

The Java platform is infamous for its low productivity, mainly due to repeated and tedious compile-package-deploy cycles.

That’s why we rethought the development cycle to make developing with play an efficient process.

The framework compiles your Java sources directly and hot-reloads them into the JVM without the need to restart the server. You can then edit, reload and see your modifications immediately, just as in a LAMP or Rails environment.

It’s so fun that you can even work with a simple text editor and skip a full-featured Java IDE if you want.

Whenever an error occurs, the framework makes its best effort to identify and show you the problem.

Even the stacktraces are stripped down and optimized to make it easier to solve problems. Look how templates execution is nicely integrated to the Java stacktrace.

Simple stateless MVC architecture

You’ve got a database on one side and a web browser on the other. Why should you have a state in between?

Stateful and component based Java Web framework make it easy to automatically save page state, but brings a lot of other problems: what happens if the user opens a second window? What if the user hits the browser back button?

The Share Nothing architecture is promoted by many Web application frameworks from PHP to Ruby on Rails or Django. As the browser is becoming more and more powerful, it is now easy to use Ajax or offline storage to solve the state problems client-side.

We don’t need to hack the HTTP model anymore to rebuild a fake state over the Web. Another aspect of the “share nothing” is to make it easier to render portions of the page in parallel, and to do partial page updates (or progressive enhancements).

HTTP-to-code mapping

If you’re already using another Java Web framework like the Servlet API or the Struts framework, you have already used an abstract view of the HTTP protocol with strange Java APIs and concepts. We think differently. A Web application framework should give you full, straightforward access to HTTP and its concepts. This is a fundamental difference between Play and other Java web application frameworks.

HTTP, the Request/Response pattern, the REST architectural style, content-type negotiation, URI are all major concepts for the play framework.

For instance, binding a URI pattern to a Java call is just one line:

GET    /clients/{id}        Clients.show

If AJAX, REST and managing back/forward movement between web pages are some of the issues you face in your day-to-day web development, just give play a try.

Efficient templating engine

We like the idea behind JSP & Expression Language. But why do we need so many configuration files to create a Tag library? Why can’t we have full access to the underlying object model? JSP has a lot of limitations and this is indeed frustrating. That’s why we’ve created a custom template system, inspired by JSP but without its constraints!

You and anyone else should be tired of writing things like this:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<c:choose>
<c:when test="${emails.unread != null && fn:size(emails.unread)}">
You have ${fn:size(emails.unread)} unread email(s)!
</c:when>
<c:otherwise>
You have no unread emails!
</c:otherwise>
</c:choose>

We think you would certainly prefer to write:

You have ${emails.unread ?: 'no'} ${emails.unread?.pluralize('email')} !

The expression language used by the play template engine is Groovy, which provides a syntax consistent with Java. While play mainly uses the templating system to render HTML responses, you are free to use it to generate any other documents such as email messages, json responses, etc.

JPA on steroids

JPA is the cleanest orM API available for Java. If you already know it you will be amazed how simpler it becomes with play. Nothing to configure, play will automatically start the JPA Entity Manager and magically synchronize it while the code is reloaded.

Moreover if you use the provided play.db.jpa.Model superclass it will help make your code prettier. Have a look:

public void messages(int page) {
User connectedUser = User.find("byEmail", connected());
List<Message> messages = Message.find(
"user = ? and read = false order by date desc",
connectedUser
).from(page * 10).fetch(10);
render(connectedUser, messages);
}

Test driven development (if you like it)

The integrated test runner makes it easy to work in a TDD mode. You can write all kind of tests, from simple unit tests to full acceptance tests, and run them directly in a browser using Selenium. Code coverage is also measured.

Full-stack application framework

The play framework was initially inspired by our own Java applications. It has all the tools needed to create a modern web application:

  • Relational Database support through JDBC.
  • Object-Relational Mapping using Hibernate (with the JPA API).
  • Integrated Cache support, with easy use of the distributed memcached system if needed.
  • Straightforward Web services consumption either in JSON or XML (we talk real web services here; not the SOAP stuff).
  • OpenID support for distributed authentication.
  • Image manipulation API.

The modular architecture lets you combine a web application with many others. Thanks to application modules, you can reuse your java code, templates and static resources (such as JavaScript and CSS files) in a simple way.

Give it a try
Install the play framework and start developing your first application.

[JQuery]jQuery Tips(5)----关于伪类选择符

mikel阅读(858)

转载:http://www.cnblogs.com/CareySon/archive/2010/01/03/1638439.html
   JQuery选择器的强大不仅在于选择器支持基本的css选择符,还支持很多CSS的伪类选择符,甚至可以自定义选择符,下面让我们来看看一些伪类选择符

  :nth-child的用法

         nth-child是一个css3伪类选择符,在JQuery中被实现了,在JQuery API中对nth-child的定义是:”匹配其父元素下的第N个子或奇偶元素“。读着感觉有点绕口,下面让我们通过例子来说明:

    <div>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
<li>five</li>
<li>six</li>
<li>seven</li>
<li>eight</li>
<li>nine</li>
</ul>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
</ul>
</div>
<script type="text/javascript">
$("li:nth-child(2)").css("background-color", "blue");
</script>

运行效果如下:

  1    

API定义中的匹配其父辈指的是所选元素的父元素不同,则分开选择。在上面例子中虽然一共选择18个<li>但是这 18<li>分属于2个不同的<ul>,所以会选择两个.如果将其放入同一个<ul>中,如果放入同一 个<ul>执行上面代码,则:

2

理解了上面匹配父辈元素,下面来说说这个选择符参数的用法.

  1. 向上面那样直接给出选择的位置,但是这里注意,这个位置是以1为开始的,而不是0
  2. n个倍数选择法,比如可以使3n+1,-3n+1,4n,等,匹配所有页面上存在的n的倍数

例子:

    <div>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
<li>five</li>
<li>six</li>
<li>seven</li>
<li>eight</li>
<li>nine</li>
</ul>
</div>
<script type="text/javascript">
$("li:nth-child(3n-1)").css("background-color", "blue");
</script>

效果:

3

可见相对应的元素都被匹配

   3.还有一种用法是我们熟知的odd和even,就是奇数和偶数,如下:

    <script type="text/javascript">
$("li:nth-child(odd)").css("background-color", "blue");
</script>

效果:

4

 

:first-child&last-child

   从上面的nth-child可以看到”匹配父类下的“含义,first-child和last-child也同样是这样.它们可以看做nth-child的封装:

first-child和nth-child(1)等价,这里就不多说了.

而first-child目前我还找不到等价的nth-child表达式,匹配父类下的最后一个子元素:

    <ul>
<li>1</li>
<li>2</li>
</ul>
<ul>
<li>1</li>
<li>2</li>
</ul>
<script type="text/javascript">
$("li:last-child").css("background-color", "blue");
</script>

效果:

5

:input并不只是匹配input

   个选择符我想大家都比较熟悉,但是要注意,input伪类选择符不只是匹配<input>标签,还会匹配<select>和<textarea>:

第一个:<input type="input" />
第二个:<select id="select">
</select>
第三个:<textarea></textarea>
<script type="text/javascript">
alert($(":input").length);//alert 3
</script>

    可以看到,不光<input>被选择,<select>和<textarea>也被选择了

 

伪类选择符可以嵌套

   通常情况下,我们可以通过嵌套伪类选择符来达到我们需要的效果,伪类选择符,如下:

    <ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
</ul>
<script type="text/javascript">
$("li:not(:first):not(:last)").css("background-color", "blue");
</script>

   效果:

   z11111111111111111111111

   可见,除了第一个和最后一个li,其它都被选择.当然,嵌套是有层数限制的,具体的次数我就不太清了(各位高手记得麻烦告诉我下),反正够你进行不是变态的使用:-)

自定义伪类选择符

   jQuery还提供给我们扩展原有选择符的方式,可以让我们根据自己的需要自定义选择符,下面通过一个有实际意义的例子看如何做到:

   在我们使用jQuery的serialize方法将当前表单中的元素提交到服务器时,总是会选上ASP.NET的ViewState(<input type=”hidden” />)这无疑浪费了好多资源,我们通过一个扩展的伪类选择符看如何不选择它:

<form name="form1" method="post" action="default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNzgzNDMwNTMzZGRWxo4mg/noF3+7k/L7nyw13HVnLQ==" />
</div>
<script type="text/javascript">
$.expr[":"].noViewState = function(element) {
return !$(element).attr("id") === "_VIEWSTATE";
}
alert($(":input:noViewState").size());//alert 0 ViewState has not been choosen
</script>
</form>

通过$.expr的方式对伪类选择符进行扩展,可以看出,上面的选择符使用:noViewState后,viewState没有被选择. 

 

 

小结:

    jQuery的伪类选择符是很强大的一项功能,它内置了很多种方便我们选择的选择符,我们可以嵌套甚至扩展这些伪类选择符.这让我们的js编程更加愉悦了许多.

 

By CareySon

[Linq]LINQ2Douban Demo Code(豆瓣Linq实例)

mikel阅读(801)

转载:http://www.cnblogs.com/chwkai/archive/2009/12/31/1636873.html

LINQToolKit是基于.Net的Web API调用框架。通过定义的XML Mapping,可以实现XML和单个实体对象和关系之间的映射,并可以在运行时对需要访问的属性进行延迟加载。LINQToolKit封装了LINQ style的查询和数据处理方式,基于LINQToolKit你可以用from/where/select来直接进行查询,这些关键字的语义可以通过定义 的QueryRule来生成相应的查询uri(用于invoke web api),并且可在上面进行任何Enumerable和Querable允许的的数据操作。

LINQToolKit可以通过扩展支持任何规范的web api,自带了Google Data API的支持

LINQ2Douban是基于LINQToolKit的douban网的api的全功能调用框架。通过LINQ2Douban,你可以用LINQ-Style的方式对douban的数据进行CRUD操作。

声明:LINQ2Douban不是官方框架,K.K只是douban的忠实用户

相关信息:

douban api讨论组:http://www.douban.com/group/dbapi/

google data protocol :http://code.google.com/apis/gdata/docs/2.0/reference.html

你可以通过google code获取LINQToolKit和LINQ2Douban的所有代码和Example

http://code.google.com/p/linqtodouban/

demo code源码下载:http://linqtodouban.googlecode.com/files/LINQ2Douban%20Demo.rar

所有疑问和错误请和K.K邮件联系chwkai@gmail.com

关于LINQToolKit稍后会有文档介绍

 

在运行Demo Code之前,请先修改context.config文件中的access token,如何获取access token请访问douban api组

1 <request type="LINQToolKit.Douban.DoubanRequest, LINQToolKit.Douban">
2     <!--
3     <apiKey></apiKey>
4     <apiKeySecret></apiKeySecret>
5     <accessToken></accessToken>
6     <accessTokenSecret></accessTokenSecret>
7     -->
8   </request>

Demo Code:

001 /// <summary>
002     /// Demos for LINQToolKit.Douban
003     /// </summary>
004     /// <remarks>
005     /// This framework is under the GNU licence and Creative Commons 3.0.
006     /// You can also get a copy of the source code from google code
007     /// <see cref="http://linqtodouban.googlecode.com/svn/trunk/"/>.
008     /// For more information and further support, please contace with kevin (chwkai@gmail.com)
009     /// </remarks>
010     class Program
011     {
012         static void Main(string[] args)
013         {
014             var context = new DoubanContext();
015             // 用于输出查询的url
016             context.Log = Console.Out;
017             context.Me.Title.Dump();
018  
019             // 回复广播(需要授权)
020             // context.CommentMiniBlog("miniBlogId", "content");
021             // 参与指定活动(需要授权)
022             //context.ParticipateEvent("eventId");
023             // 对活动感兴趣(需要授权)
024             // context.FollowEvent("eventId");
025             // 退出活动(需要授权)
026             // context.QuitEvent("eventId");
027             // 删除活动(需要授权)
028             // context.DeleteEvent("eventId", "reason");
029             // 回复推荐
030             // context.CommentOnReco("recommendId", "content");
031             // 删除推荐的回复
032             // context.DeleteCommentOnReco("recommendId", commentId);
033             // 批量设置豆油已读
034             // context.SetMailsRead(string[] mails);
035             // 批量删除豆油
036             // context.DeleteMails(string[] mails);
037             // 验证access token是否可用
038             // context.IsTokenAvailabe(token);
039             // 注销token
040             // context.CancelToken(token);
041         }
042  
043         /// <summary>
044         /// 获取用户豆油(需要授权,只能看Me的)
045         /// </summary>
046         /// <param name="context"></param>
047         private static void GetUserMails(DoubanContext context)
048         {
049             // 未读邮件
050             // context.Me.UnReadMails;
051             // 已发邮件
052             // context.Me.OutboxMails;
053             foreach (var item in context.Me.Mails)
054             {
055                 item.Title.Dump();
056             }
057         }
058  
059         /// <summary>
060         /// 获取用户所有推荐
061         /// </summary>
062         /// <param name="context"></param>
063         private static void GetUserRecos(DoubanContext context)
064         {
065             foreach (var item in context.Me.Recommends)
066             {
067                 item.Title.Dump();
068  
069                 //获取推荐回复
070                 foreach (var cc in item.Comments)
071                 {
072                     cc.Title.Dump();
073                 }
074             }
075         }
076  
077         /// <summary>
078         /// 获取指定城市id的活动
079         /// </summary>
080         /// <param name="context"></param>
081         private static void GetLocationEvents(DoubanContext context)
082         {
083             var events =
084                 (from e in context.Events
085                  where e.Location.ID == "beijing"
086                  select e).Take(10);
087  
088             foreach (var item in events)
089             {
090                 item.Title.Dump();
091             }
092         }
093  
094         /// <summary>
095         /// 获取指定用户所有活动
096         /// </summary>
097         /// <param name="context"></param>
098         private static void GetUserEvents(DoubanContext context)
099         {
100             // 用户参与的活动
101             //context.Me.ParticipateEvents
102             // 用户感兴趣的活动
103             //context.Me.WishEvents
104             // 用户发起的活动
105             //context.Me.InitiateEvents
106             foreach (var item in context.Me.Events)
107             {
108                 item.Title.Dump();
109             }
110         }
111  
112         /// <summary>
113         /// 获取用户所有日记(延迟加载)
114         /// </summary>
115         /// <param name="context"></param>
116         private static void GetUserNotes(DoubanContext context)
117         {
118             foreach (var item in context.Me.Notes)
119             {
120                 item.Title.Dump();
121             }
122         }
123  
124         /// <summary>
125         /// 获取指定用户广播
126         /// </summary>
127         /// <param name="context"></param>
128         private static void GetUserMiniBlogs(DoubanContext context)
129         {
130             var user = context.Peoples.GetByID("ahbei");
131  
132             foreach (var item in user.MiniBlogs)
133             {
134                 item.Title.Dump();
135  
136                 // 获取广播回复
137                 foreach (var c in item.Comments)
138                 {
139                     c.Title.Dump();
140                 }
141             }
142         }
143  
144         /// <summary>
145         /// 获取友邻广播
146         /// </summary>
147         /// <param name="context"></param>
148         private static void GetContactMiniBlogs(DoubanContext context)
149         {
150             foreach (var item in context.Me.ContactMiniBlogs)
151             {
152                 item.Title.Dump();
153             }
154         }
155  
156         /// <summary>
157         /// 获取指定用户收藏
158         /// </summary>
159         /// <remarks>
160         /// 此处以Me为例,也可以换成查询得来的People。当访问这些属性时,会自动加载数据
161         /// </remarks>
162         /// <param name="context"></param>
163         private static void GetPeopleCollections(DoubanContext context)
164         {
165             // 获取音乐收藏
166             // context.Me.Musics
167             // 获取电影收藏
168             // context.Me.Movies
169             // 获取书籍搜藏
170             foreach (var item in context.Me.Books)
171             {
172                 item.Title.Dump();
173             }
174         }
175  
176         /// <summary>
177         /// 删除指定id的评论
178         /// </summary>
179         /// <param name="context"></param>
180         private static void DeleteReview(DoubanContext context)
181         {
182             // 代码中的id更换为有效id
183             var review = context.Reviews.GetByID("2902263");
184             context.Reviews.Delete(review.ID);
185         }
186  
187         /// <summary>
188         /// 更新指定的id的评论
189         /// </summary>
190         /// <param name="context"></param>
191         private static void UpdateReview(DoubanContext context)
192         {
193             // 代码中的id更换为有效id
194             var review = context.Reviews.GetByID("2902263");
195             review.Content = "Udpat content test for linq2douban";
196             context.Reviews.Update(review.ID, review);
197         }
198  
199         /// <summary>
200         /// 发表评论
201         /// </summary>
202         /// <remarks>
203         /// 发表短的Content内容,douban api会报失败
204         /// </remarks>
205         /// <param name="context"></param>
206         private static void AddReview(DoubanContext context)
207         {
208             var review = new Review
209             {
210                 // 如果Subject是从API读回来的,Source属性会自动赋值
211                 Subject = new Movie { Source = "http://api.douban.com/movie/subject/1424406" },
212                 Rating = new Rating { Value = 4 },
213                 Content = "this is the test for linq2doubanddddddddddddddddddddddddddddddddddddddddd",
214                 Title = "this is the test for linq2douban"
215             };
216  
217             context.Reviews.Insert(review);
218         }
219  
220         /// <summary>
221         /// 获取指定书籍/电影/音乐的评论
222         /// </summary>
223         /// <param name="context"></param>
224         private static void GetSubjectReviews(DoubanContext context)
225         {
226             var movie = context.Movies.GetByID("1424406");
227             movie.Reviews.Count().Dump();
228         }
229  
230         /// <summary>
231         /// 获取用户所有评论
232         /// </summary>
233         /// <remarks>
234         /// 此例用me做示例,获取指定的用户的评论,访问People.Reviews属性即可,框架会延迟加载
235         /// </remarks>
236         /// <param name="context"></param>
237         private static void GetReviews(DoubanContext context)
238         {
239             foreach (var item in context.Me.Reviews)
240             {
241                 item.Title.Dump();
242             }
243         }
244  
245         /// <summary>
246         /// 示例复合查询(关键字,startindex,maxtresult)
247         /// </summary>
248         /// <remarks>
249         /// douban的startindex, maxresult的含义与常识理解不通,
250         /// douban的maxresult表示取回数据的最大下标,所以Take(20).Skip(10)返回下标为10-20共11条数据
251         /// </remarks>
252         /// <param name="context"></param>
253         private static void GetBooks(DoubanContext context)
254         {
255             var query = context.Books.Has("新东方").Take(20).Skip(10);
256  
257             foreach (var item in query)
258             {
259                 item.Title.Dump();
260             }
261         }
262  
263         /// <summary>
264         /// 用多个条件查询书籍(电影、音乐等,查询同此例)
265         /// </summary>
266         /// <remarks>
267         /// LINQToolKit会根据queries.config里定义的query rules转换成相应的url进行查询,
268         /// 你可尝试用不同关键字匹配,如果有问题请发chwkai@gmail.com
269         /// </remarks>
270         /// <param name="context"></param>
271         private static void GetBooksByQuery(DoubanContext context)
272         {
273             var query =
274                 from b in context.Books
275                 where b.LongIsbn == "9787543639133" || b.ID == "2023013"
276                 select b;
277  
278             foreach (var item in query)
279             {
280                 item.Title.Dump();
281             }
282         }
283  
284         /// <summary>
285         /// 获取指定ID的书籍
286         /// </summary>
287         /// <remarks>
288         /// 获取指定ID的douban信息与此例相同
289         /// </remarks>
290         /// <param name="context"></param>
291         private static void GetBookByID(DoubanContext context)
292         {
293             var query =
294                 from b in context.Books
295                 where b.ID == "2023013"
296                 select b;
297             query.Single().Dump();
298         }
299  
300         /// <summary>
301         /// 获取用户关注的人(延迟加载)
302         /// </summary>
303         /// <param name="context"></param>
304         private static void GetContacts(DoubanContext context)
305         {
306             // context.Me.Contacts 获取当前用户关注的人
307  
308             var query =
309                 from p in context.Peoples
310                 where p.ID == "chwkai"
311                 select p;
312  
313             // People.Contacts 当需要访问时会延迟加载
314             query.Single().Contacts.Count().Dump();
315         }
316  
317         /// <summary>
318         /// 获取用户朋友(延迟加载)
319         /// </summary>
320         /// <param name="context"></param>
321         private static void GetFriends(DoubanContext context)
322         {
323             // context.Me.Friends 获取当前用户的朋友
324  
325             var query =
326                 from p in context.Peoples
327                 where p.ID == "chwkai"
328                 select p;
329  
330             // People.Friends 当需要访问时会延迟加载
331             query.Single().Friends.Count().Dump();
332         }
333  
334         /// <summary>
335         /// 关键字搜索用户
336         /// </summary>
337         /// <remarks>
338         /// 不是ID的相等比较条件都会转换为关键字查询
339         /// </remarks>
340         /// <param name="context"></param>
341         private static void GetUserByKey2(DoubanContext context)
342         {
343             var query =
344                 from p in context.Peoples
345                 where p.Title == "net"
346                 select p;
347             query.Count().Dump();
348         }
349  
350         /// <summary>
351         /// 关键字搜索用户
352         /// </summary>
353         /// <param name="context"></param>
354         private static void GetUserByKey(DoubanContext context)
355         {
356             var query = context.Peoples.Has("net");
357             query.Count().Dump();
358         }
359  
360         /// <summary>
361         /// 获取当前用户(需要授权)
362         /// </summary>
363         /// <param name="context"></param>
364         private static void GetCurrentUser(DoubanContext context)
365         {
366             context.Me.Content.Dump();
367         }
368  
369         /// <summary>
370         /// 获取单个用户信息
371         /// </summary>
372         /// <param name="context"></param>
373         private static void GetOneUserLinqStyle(DoubanContext context)
374         {
375             var user =
376                 from u in context.Peoples
377                 where u.ID == "chwkai"
378                 select u;
379             user.Single().Content.Dump();
380         }
381  
382         /// <summary>
383         /// 获取单个用户信息
384         /// </summary>
385         /// <param name="context"></param>
386         private static void GetOneUser(DoubanContext context)
387         {
388             var user = context.Peoples.GetByID("chwkai");
389             user.Content.Dump();
390         }

[MVC] asp.net mvc中“Action”的创建

mikel阅读(1129)

转载:http://www.cnblogs.com/niuchenglei/archive/2009/12/29/1635482.html

在上一篇《ControllerActionInvoker——Action的导火索》中,我介绍了ControllerAcionInvoker类,那么接下来就到了Action的创建了,继续我们的ASP.NET mvc源代码之旅。

内容概览Top

本篇主要探讨“Action”的创建过程,为什么要加引号呢?因为我们创建的不是真正的Action,方法是没法创建的,它是指 ActionDescriptor对象,是对Action方法描述的一个对象,在mvc中,方法的调用是利用反射来实现的。下面我们就具体讨论一下这个过 程。

为什么要创建Action?Top

在一个请求到达时,必然最终会由一个Action去执行,那么这个Action是怎么执行的呢?答案是利用反射得到 Action的描述,然后再调用Action的。为什么要这么大费周折呢?因为在Action上还有好多Filter,我们要在执行的时候考虑到AOP的 影响,并把二者无缝的结合起来。所以在执行Action上,我们要得到一个ActionDescriptor对象,这个对象用以描述Action方法的一 些特性。

ControllerDescriptor与ActionDescriptorTop

ControllerDescriptor是描述Controller的类,ActionDescriptor是描述Action的类,而Action是 Controller的方法,那么在ControllerDescriptor和ActionControllerDescriptor两者之间就必然存 在着某种关联,下面我们看看到底他们是一种什么关系:

在ControllerActionInvoker类中,我们发现了两个类直接的一次协作,代码是这样的:

ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);

我们看到,ActionDescriptor是调用ControllerDescriptor的FindAction方法得到的。我们猜想它们可能是一对 多关系,一个ControllerDescriptor对应多个ActionDescriptor,下面就一步一步来验证我们的猜想,首先我们先从 ReflectedControllerDescriptor类入手,因为这个类是ControllerDescriptor类的惟一继承者。

ReflectedControllerDescriptor类有几个比较重要的字段:

private ActionDescriptor[] _canonicalActionsCache;
private readonly Type _controllerType;
private readonly ActionMethodSelector _selector;

从上面我们看到,ReflectedControllerDescriptor类有一个 ActionDescriptor[]类型的字段,这就证明了我们的猜想是正确的。还有一个ActionMethodSelector类型的字段,这个类 我们暂且不去管它。接着,我发现ReflectedControllerDescriptor类的构造函数接受一个Type类型的参数,这个Type就是 Controller的类型,然后new ActionMethodSelector(Type)一个ActionMethodSelector类型的对象,把它赋值给_selector字段。然 后,我们回到FindAction方法上,ControllerDescriptor就是通过这个方法得到ActionDescriptor对象的。该方 法代码如下:

public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName) {
    if (controllerContext == null) {
        throw new ArgumentNullException("controllerContext");
    }
    if (String.IsNullOrEmpty(actionName)) {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
    }
    MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName);
    if (matched == null) {
        return null;
    }
    return new ReflectedActionDescriptor(matched, actionName, this);
}

从上面我们看到,首先利用ActionMethodSelector获得MethodInfo对象,然后把它作为参数new ReflectedActionDescriptor对象并返回。

总结一下,ControllerDescriptor通过ActionMethodSelector得到ActionDescriptor对 象,ReflectedActionDescriptor对象的构造操作只需要一个MethodInfo对象。具体的 ActionMethodSelector类的机制下面介绍。

ActionMethodSelector是什么?Top

从上一节中我们了解到,ActonMethodSelector是一个工具,是被ControllerDescriptor利用来获取 ActionDescriptor对象的工具。那么我们就有必要来了解一下这个工具了。从上一节中,我们得知FindActionMethod是一个突破 口,但是这次我们要先从构造函数入手,因为这个类在构造函数里面完成了一些初始化的操作,而这些初始化的操作是非常重要的。下面是它的构造函数:

//构造函数
public ActionMethodSelector(Type controllerType) {
    ControllerType = controllerType;
    PopulateLookupTables();
}
//构造函数调用的方法,用来获取属于某个Controller的所有Action方法的MethodInfo对象
private void PopulateLookupTables() {
    MethodInfo[] allMethods = ControllerType.GetMethods(
                BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
    MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);
    AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
    NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(
                method => method.Name, StringComparer.OrdinalIgnoreCase);
}
//判断MethodInfo是否为合法的Action
private static bool IsValidActionMethod(MethodInfo methodInfo) {
    return !(methodInfo.IsSpecialName ||
            methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(Controller)));
}
//判断MethodInfo是否为被ActionNameSelectorAttribute所修饰
private static bool IsMethodDecoratedWithAliasingAttribute(MethodInfo methodInfo) {
    return methodInfo.IsDefined(typeof(ActionNameSelectorAttribute), true /* inherit */);
}

在上面的代码中,初始化的操作做了一些工作,这些工作是获取一个Controller的合法的所有Action,并存放在ActionMethodSelector的两个字段中,下面是这两个字段的定义:

public MethodInfo[] AliasedMethods
public ILookup<string, MethodInfo> NonAliasedMethods

这两个属性是存放Action方法对应MethodInfo的,AliasedMethods存放那些使用 ActionNameSelectorAttribute属性标注的Action,也就是我们告诉mvc这是一个Action。 NonAliasedMethods用来存放我们没有明确指出这是一个Action,但的确它是一个Action的Action方法。

ActionMethodSelector的初始化操作已经完成了,下面我们从FindActionMethod方法入手,继续探究是如何获取一个Action的MethodInfo的。下面是该方法的代码:

public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) {
    List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
    methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
    List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);
    switch (finalMethods.Count) {
        //匹配到0个
        case 0:
            return null;
        //匹配到1个
        case 1:
            return finalMethods[0];
        //匹配到多个,抛出异常
        default:
            throw CreateAmbiguousMatchException(finalMethods, actionName);
    }
}

在上面的一段代码中,还涉及到了GetMatchAliasedMethods、RunSelectionFilters方法,这两个方法的代码就不再做详细分析了,相信大家看看mvc的源代码就很容易明白了。步骤是这样的:

  • 该类在初始化操作中已经把“明确标注为Action”和“没有明确标注为Action”的所有Action填充到了自己的两个字段中。即我们通常使用的[ActionName("name")]特性。
  • 建立一个最终查询的列表,并把“明确标注为Action”的所有Action都加入其中。
  • 从“没有明确标注为Action”(即不使用ActionName进行标注)的Action列表中,即该类的NonAliasedMethods属性中找到名为action参数的一个MethodInfo,并把它加入到最终查询列表。
  • 从最终查询列表中查找名为action参数的MethodInfo,如果匹配0个表示没有该Action,匹配1个就正确,匹配多个就抛出异常。

好了,到此我好了,到此我们便获取到了一个Action的MethodInfo,即反射信息。下一步就是利用这个MethodInfo构建一个描述Action的ReflectedActionDescriptor对象了,这点我们在上一节已经说过了,的确很简单。

获取全部合法的ActionTop

在ControllerControllerDescriptor类中,我们发现有一个 GetCanonicalActions的抽象方法。我们转到ReflectedActionDescriptor类,看看这个方法的实现。它的返回值是 ActionDescriptor[]类型的,它返回一个Controller所有的合法的Action。下面我们具体分析一下,它与 FindActionMethod有什么不同。下面是该方法的代码:

public override ActionDescriptor[] GetCanonicalActions() {
    ActionDescriptor[] actions = LazilyFetchCanonicalActionsCollection();
    // need to clone array so that user modifications aren't accidentally stored
    return (ActionDescriptor[])actions.Clone();
}
private ActionDescriptor[] LazilyFetchCanonicalActionsCollection() {
    return DescriptorUtil.LazilyFetchOrCreateDescriptors<MethodInfo, ActionDescriptor>(
        /* cacheLocation */
        ref _canonicalActionsCache ,
        /* initializer */
        GetAllActionMethodsFromSelector ,
        /* converter */
        methodInfo => ReflectedActionDescriptor.TryCreateDescriptor(methodInfo,
                                                                    methodInfo.Name,
                                                                    this));
}

在上面的代码中,我们看到GetCanonicalActions方法需要一个DescriptorUtil类来辅助得到 ActionDescriptor[]类型列表。这个类的LazilyFetchOrCreateDescriptors方法需要三个参数,他们分别是 /*cacheLocation*/就是ControllerDescriptor存放ActionDescriptor的列表, /*initializer*/获取所有Action的一个委托,/*converter*/一个把MethodInfo对象包装成 ActionDescriptor对象的委托。第一个参数值为ControllerDescriptor类的一个字段,第二个参数值为一个方法的委托,下 面为这个委托:

private MethodInfo[] GetAllActionMethodsFromSelector() {
    List<MethodInfo> allValidMethods = new List<MethodInfo>();
    allValidMethods.AddRange(_selector.AliasedMethods);
    allValidMethods.AddRange(_selector.NonAliasedMethods.SelectMany(g => g));
    return allValidMethods.ToArray();
}

我们看到这个方法就是要把Controller的所有的Action都返回。第三个参数是一个lambda形式的表 达式,它直接调用ReflectedActionDescriptor的方法TryCreateDescriptor来包装MethodInfo对象成一 个ActionDescriptor对象。接下来我们看看DescriptorUtil类是如何设计的,这是一个静态类,只有一个静态的泛型方法:

public static TDescriptor[] LazilyFetchOrCreateDescriptors<TReflection, TDescriptor>(
                                            ref TDescriptor[] cacheLocation,
                                            Func<TReflection[]> initializer,
                                            Func<TReflection, TDescriptor> converter) {
    // did we already calculate this once?
    TDescriptor[] existingCache = Interlocked.CompareExchange(ref cacheLocation, null, null);
    if (existingCache != null) {
        return existingCache;
    }
    TReflection[] memberInfos = initializer();
    TDescriptor[] descriptors = memberInfos.Select(converter).Where(
                            descriptor => descriptor != null).ToArray();
    TDescriptor[] updatedCache = Interlocked.CompareExchange(ref cacheLocation,descriptors,null);
    return updatedCache ?? descriptors;
}

这段代码看起来很吓人,因为它不但使用了泛型方法,还使用了linq查询,已经lambda表达式。其实并没有那么 复杂,他要先判断传入的ControllerDescriptor的ActionDescriptor列表是否为空,是则把原来列表中相同的部分删掉一 个,然后返回(为什么会有相同的两个元素在节点中呢?请看下面的小注)。如果为空,则获取所有合法Action,然后把两个相同中的一个删除,然后返回 (怎么又出现两个元素相同的现象呢?请看下面的小注)。看,并不是那么的难理解,泛型只是对类型抽象了一下而已,而lambda表达式只不过是传递一个方 法,不要畏惧这些小把戏,有了这些小把戏编程才更有趣。

为什么会出现有两个相同元素相同的情况?还记得ActionMethodSelector的两个属性 吗,AliasedMethods和NonAliasedMethods,我们使用[ActionName("actionname")]标注的 Action是“非常合法”的Action,这些Action将被划入AliasedMethods行列,我们没有使用这一特性标注的Action是“合 法”的Action,这些Action将被划入NonAliasedMethods行列。如果我们有如下代码的两个Action时会出现什么情况呢?很明 显,在获取Controller的全部Action时这两个就都获取到了,就会有两个Action相同。

public ActionResult Index(){……}
[ActionName("Index")]
public ActionResult Default(){……}

总结Top

在这一篇中,我们围绕ActionDescriptor讨论了一些,讨论了ActionDescriptor的创 建,与ControllerDescriptor的关系,以及怎么去获取相应的Action等,其实这个过程十分的简单,概括一句话就是 ControllerDescriptor利用ActionMethod获取到ActionDescriptor,而且这个过程在mvc中也很微小,但是 的确它是一个不可或缺的部分,而且是相当重要的一部分。谢谢大家的阅读,也许你对mvc源代码不是太感兴趣或对这个过程不太清楚,但我还是要告诉你,学习 mvc源代码有很多用处,只有坚持一下,一切就都会明白的。希望朋友们多批评指正,相互学习。

作者:Creason New(Creason's Blog)
出处: http://www.cnblogs.com/niuchenglei
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[MVC]超级简单:一步一步教你创建一小型的asp.net mvc 应用程序

mikel阅读(847)

z转载:http://www.cnblogs.com/zhuqil/archive/2009/12/27/1633353.html

   这本教程中将帮助你创建一个小型的ASP.NET mvc示例。

    在本教程中,我们将创建自己的 Model , View 和Controller ,让他们同心协力运行起来。在这里,使用Visual Studio创建一个新的ASP.NET MVC应用程序。本教程的目标是使用mvc原理在web页面上输出的信息。简单地理解一下我们要打算做的,然后我们将演示每个步骤来建立我们的网站。对于 我们的目标,我们需要一个model 来保存信息。该model 将是一个具具有必要属性的类。然后,我们将创建一个View,在一个表格形式中显示信息。最后,我们需要一个Controller来接受的网页请求,并对 此作出反应。那就让我们开始吧。

创建Model

在Models文件夹右击并通过Add -> Class,创建一个新类,见图:

将类命名为 PersonalInformation.cs 。在Model中添加如下的属性:

创建这些属性使用C#3.0规范,我们不需要为他们创建私有字段。这就完成了我们的Model。现在让我们来创建我们的View,我们将使用它在浏览器中显示信息。

创建View:

    在views文件夹右击,创建一个新的文件夹。命名为PersonalInformation。右击PersonalInformation 文件夹,然后选择Add -> View ,看下图:

    将View命名为Index,选择“Create a strongly-typed view”,然后再选择class ,它是作用于这个View的Model。在View data class下拉框中,你可能看不到任何视图类。编译应用程序,然后添加一个View。 你就会看到在下拉框中看到PersonalInformation类。然后为你的View选择一个母板页。一旦这些步骤完成后,单击Add 和创建的View 。请参考下图。

创建Controller:

    现在我们已经完成了Model 和View。让我们来创建我们的Controller。右击Controllers 然后选择Add -> Controller

    将Controller命名为PersonalInformation。请确认Controller 的名字和我们创建的在Views文件夹下面的名字相同。

    你将在Controller类中看到一个名字为Index的默认的方法。

    这种方法具有返回类型ActionResult,它将返回一个视图。在未来教程中,我们将深入探讨的返回类型和其的细节。现在让我们做一些编码,传递Model给View,以便它可以在网页上显示信息。在下面的代码输入到您的Controller 类中。

    此代码创建了一个Model和填充数据给它,现在剩下的最后一步,是使用的视图显示的信息。对于我们创建的开放的View,我们输入下面代码:

    就是这样。我们完成了我们的Model,View和Cintroller。现在让我们编译和运行我们的应用程序。下面是最后的输出,我们将在浏览器中看到。请注意仔细观察浏览器地址栏中的URL。