[转载]分享:Jquery Mobile开发PhoneGap访问手机

mikel阅读(1321)

[转载]分享:Jquery Mobile开发,PhoneGap访问手机app – dekins – 博客园.

JQuery Mobile:

1.官方简介

A unified, HTML5-based user interface system for all popular mobile device platforms, built on the rock-solid JQuery and jQuery UI foundation. Its lightweight code is built with progressive enhancement, and has a flexible, easily themeable design.

地址:http://jquerymobile.com

2.支持以下智能手机平台

image

3.Building PhoneGap apps with jQuery Mobile

PhoneGapis an HTML5 app platform that allows developers to author native applications with web technologies and get access to APIs and app stores. Applications are built as normal HTML pages and packaged up to run as a native application within a UIWebView or WebView (a chromeless browser, referred to hereafter as a webview). Since PhoneGap is frequently used in conjunction with jQuery Mobile, we wanted to offer a few tips and recommendations to help you get staretd.

The initial application document is loaded by the PhoneGap application by a local file:// URL. This means that if you want to pull in pages from your company’s remote server (phone home) you will have to refer to them with absolute URLs to your server. Because your document originates from a file:// URL, loading pages or assets from your remote server is considered a cross-domain request that can be blocked in certain scenarios.

Your ability to access cross-domain pages from within a Phone Gap jQuery Mobile application is controlled by two key things: $.support.cors and $.mobile.allowCrossDomainPages, and can also be influenced by the white list feature in later builds of PhoneGap.

地址:http://phonegap.com

[转载]C#委托delegate

mikel阅读(1100)

[转载]C# delegate – 《孙金棚的程序人生》 – 博客园.

 .Net 中的委托类似于 C 或 C++ 中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方 法。与 C 或 C++ 中的函数指针不同,委托是面向对象、类型安全的,并且是安全的。

委托声明定义一种类型,它用一组特定的参数以及返回类型封装方法。对于静态方法,委托对象封装要调用的方法。对于实例方法,委托对象同时封装一 个实例和该实例上的一个方法。如果您有一个委托对象和一组适当的参数,则可以用这些参数调用该委托。委托的一个有趣且有用的属性是,它不知道或不关心自己 引用的对象的类。任何对象都可以;只是方法的参数类型和返回类型必须与委托的参数类型和返回类型相匹配。这使得委托完全适合“匿名”调用。

在任何地方都可以使用Delegates
在这一点上,也许你只知道有这样一回事,但却没有察觉它在你的程序中的应用。虽然你没 有建立自己的delegates,但如果经常使用事件时你会使用到它们。一个极好的例子就是ASP.NET中使用的事件。在后台使用的Delegates 围绕着事件工作。事件使用一个标记,你也可以在一个线程结构中找到delegates。

Delegate 和 interface

你必须注意的另一个点是一个delegate和一个接口之间的相似性,因为它们都将规范和执行分离开来。它们允许开发人员建立与规范相符合的执行。除此之外,方法标记必须是被指定和相兼容。

所以,你什么时候使用接口?以及你什么时候使用delegates?微软提供了以下的指导思想。

Delegates:
调用一个单一方法。 一个类要进行方法规范(method specification)的多种执行。 使用一个静态方法来执行规范。 想获得类似事件设计的模式。 调用者没有必要知道或获得方法定义的对象。 执行的提供者想将规范的执行“分发”成一些选择性的部分。

interface:

规范指定一套即将被调用的方法。 特别的,一个类只执行规范一次。 接口的调用者想通过接口类型以获得其它接口或类。

 

下面我们用示例来理解一下吧!…………………O(∩_∩)O

示例1

BookDB是一个书籍数据库、他公开了ProcessPaperbackBooks 方法、并且每本平装书都调用一个委托ProcessBookDelegate、从而得到书籍标题和平均价格

Code:

using System;
namespace Bookstore
{
using System.Collections;
public struct Book
{
public string Title; //标题
public string Author; // 作者
public decimal Price; // 价格
public bool Paperback; // 是否是平装书籍
public Book(string title, string author, decimal price, bool paperBack)
{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
}
}
//声明委托 参数是book
public delegate void ProcessBookDelegate(Book book);

// 模拟书籍数据库
public class BookDB
{
ArrayList list = new ArrayList();
//添加
public void AddBook(string title, string author, decimal price, bool paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}
// 处理平装书籍
public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
{
foreach (Book b in list)
{
if (b.Paperback)//过滤平装书籍
processBook(b);
}
}
}
}
//书籍平均价格
namespace BookTestClient
{
using Bookstore;
class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m;
internal void AddBookToTotal(Book book)
{
countBooks += 1;
priceBooks += book.Price;
}
internal decimal AveragePrice()
{
return priceBooks / countBooks;
}
}
class Test
{
//打印评价书籍标题
static void PrintTitle(Book b)
{
Console.WriteLine(" {0}", b.Title);
}
static void Main()
{
Console.WriteLine("创建书籍数据库");
BookDB bookDB = new BookDB();
AddBooks(bookDB);
Console.WriteLine("增加书籍完毕");
Console.WriteLine("所有平装书名");
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
PriceTotaller totaller = new PriceTotaller();
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
Console.WriteLine("评价书籍平均价格是: ${0:#.##}",
totaller.AveragePrice());
}
static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("C# 设计模式","X.Y", 19.95m, true);
bookDB.AddBook("人月神话 ","X.Y", 39.95m, true);
bookDB.AddBook("大话设计模式", "X.Y", 129.95m, false);
bookDB.AddBook("精通C#", "X.Y", 12.00m, true);
}
}
}

效果图

 

示例2

委托是可以用”+”和”-“来添加和移除的、但是要满足两个条件。条件一:必须是相同类型的委托、条件二:必须都是void返回值

Code:

using System;
delegate void MyDelegate(string s);
class MyClass
{
public static void Hello(string s)
{
Console.WriteLine(" Hello, {0}!", s);
}
public static void Goodbye(string s)
{
Console.WriteLine(" Goodbye, {0}!", s);
}
public static void Main()
{
MyDelegate a, b, c, d;

a = new MyDelegate(Hello);

b = new MyDelegate(Goodbye);

c = a + b;

d = c - a;
Console.WriteLine("调用 delegate a:");
a("A");
Console.WriteLine("调用 delegate b:");
b("B");
Console.WriteLine("调用 delegate c:");
c("C");
Console.WriteLine("调用 delegate d:");
d("D");
}
}

效果图

[转载]深入浅出JSONP--解决ajax跨域问题

mikel阅读(1223)

[转载]深入浅出JSONP–解决ajax跨域问题 – 快乐乔巴 – 博客园.

取不到数据!

上周客户新买了服务器,原本在旧的服务器上放着客户的Web主页信息和一个后台程序(ASP.NET),在客户的主页中有一个动态显示最新消息 的处理,这个处理就是通过ajax异步从那个后台程序中取得的。由于又购买了新的服务器,客户想把web主页和那个后台程序分开来,后台程序被部署到了新 的服务器上。不过这个项目是我的同事小福同志开发的,也就由他来把程序分开部署,然后进行一些小改动。

“怎么最新消息取不到了,异步处理的url也已经添加上新服务器的地址(http://xxxx.com/…/news.ashx),奇怪 了…”小福在一边抱怨,我看了看IE7下还出了个脚本错误”アクセスが拒否されました”的错误(环境是日文的,意思是访问被拒绝了)。网上查了下中文 环境应该是”没有权限”吧。在Firefox和Chrome上是看不到任何脚本错误的,不过可以通过Firebug工具测出这个错误 (”Permission denied to call method XMLHttpRequest.open”)。

同源策略

为什么会出这样的错误呢?这是因为所有支持JavaScript的浏览器都会使用同源策略这个安全策略。看看百度的解释:

  同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当一个百度浏览器执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。

这就是引起为何取不到数据的原因了,那如何才能解决跨域的问题呢?没错,我们现在可以进入正题,来了解下什么是JSONP了。

JSON和JSONP

JSONP和JSON好像啊,他们之间有什么联系吗?

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。对于JSON大家应该是很了解了吧,不是很清楚的朋友可以去json.org上了解下,简单易懂。

JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。–来源百度

JSONP就像是JSON+Padding一样(Padding这里我们理解为填充), 我们先看下面的小例子然后再详细介绍。

跨域的简单原理

光看定义还不是很明白,那首先我们先来手动做个简单易懂的小测试。新建一个ASP.NET的web程序,添加sample.html网页和一个test.js文件,代码如下:

sample.html的代码:

1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" >
3 <head>
4     <title>test</title>
5     <script type="text/javascript" src="test.js"></script>
6 </head>
7 <body>
8 </body>
9 </html>

test.js的代码:

1 alert("success");

打开sample.html后会跳出”success”这样的这样的信息框,这似乎并不能说明什么, 跨域问题到底怎么解决呢?好,现在我们模 拟下非同源的环境,刚才我们不是已经用Visual Studio新建了一个Web程序吗(这里我们叫A程序),现在我们再打开一个新的Visual Studio再新建一个Web程序(B程序),将我们的之前的test.js文件从A程序中移除然后拷贝到B程序中。两个程序都运行起来后,Visual Studio会启动内置服务器,假设A程序是localhost:20001,B程序是localhost:20002,这就模拟了一个非同源的环境了 (虽然域名相同但端口号不同,所以是非同源的)。

OK,我们接下来应该改下sample.html里的代码,因为test.js文件在B程序上了,url也就变成了localhost:20002。

sample.html部分代码:

1 <script type="text/javascript" src="http://localhost:20002/test.js"></script>

请保持AB两个Web程序的运行状态,当你再次刷新localhost:20001/sample.html的时候,和原来一样跳出 了”success”的对话框,是的,成功访问到了非同源的localhost:20002/test.js这个所谓的远程服务了。到了这一步,相信大家 应该已经大概明白如何跨域访问了的原理了。

<script>标签的src属性并不被同源策略所约束,所以可以获取任何服务器上脚本并执行。

JSONP的实现模式–CallBack

刚才的小例子讲解了跨域的原理,我们回上去再看看JSONP的定义说明中讲到了javascript callback的形式。那我们就来修改下代码,如何实现JSONP的javascript callback的形式。

程序A中sample的部分代码:

1 <script type="text/javascript">
2 //回调函数
3 function callback(data) {
4  alert(data.message);
5 }
6 </script>
7 <script type="text/javascript" src="http://localhost:20002/test.js"></script>

程序B中test.js的代码:

1 //调用callback函数,并以json数据形式作为阐述传递,完成回调
2 callback({message:"success"});

这其实就是JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调

  将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义吧。

一般情况下,我们希望这个script标签能够动态的调用,而不是像上面因为固定在html里面所以没等页面显示就执行了,很不灵活。我们可以通过javascript动态的创建script标签,这样我们就可以灵活调用远程服务了。

程序A中sample的部分代码:

 1 <script type="text/javascript">
 2     function callback(data) {
 3  alert(data.message);
 4     }
 5     //添加<script>标签的方法
 6     function addScriptTag(src){
 7     var script = document.createElement('script');
 8  script.setAttribute("type","text/javascript");
 9  script.src = src;
10  document.body.appendChild(script);
11     }
12  
13  window.onload = function(){
14  addScriptTag("http://localhost:20002/test.js");
15  }
16 </script>

程序B的test.js代码不变,我们再执行下程序,是不是和原来的一样呢。如果我们再想调用一个远程服务的话,只要添加 addScriptTag方法,传入远程服务的src值就可以了。这里说明下为什么要将addScriptTag方法放入到window.onload的 方法里,原因是addScriptTag方法中有句document.body.appendChild(script);,这个script标签是被添 加到body里的,由于我们写的javascript代码是在head标签中,document.body还没有初始化完毕呢,所以我们要通过 window.onload方法先初始化页面,这样才不会出错。

上面的例子是最简单的JSONP的实现模型,不过它还算不上一个真正的JSONP服务。我们来看一下真正的JSONP服务是怎么样的,比如Google的ajax搜索接口:http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=?&callback=?

q=?这个问号是表示你要搜索的内容,最重要的是第二个callback=?这个是正如其名表示回调函数的名称,也就是将你自己在客户端定义的 回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调。有点罗嗦了,还是看看实现代码 吧:

 1 <script type="text/javascript">
 2     //添加<script>标签的方法
 3     function addScriptTag(src){
 4  var script = document.createElement('script');
 5  script.setAttribute("type","text/javascript");
 6  script.src = src;
 7  document.body.appendChild(script);
 8  }
 9  
10  window.onload = function(){
11         //搜索apple,将自定义的回调函数名result传入callback参数中
12  addScriptTag("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=apple&callback=result");
13  
14  }
15     //自定义的回调函数result
16     function result(data) {
17         //我们就简单的获取apple搜索结果的第一条记录中url数据
18  alert(data.responseData.results[0].unescapedUrl);
19  }
20 </script>

像这样的JSONP服务还有很多(以下信息来自使用 JSONP 实现跨域通信,第 1 部分: 结合 JSONP 和 jQuery 快速构建强大的 mashup):

Digg API:来自 Digg 的头条新闻:

http://services.digg.com/stories/top?appkey=http%3A%2F%2Fmashup.com&type=javascript&callback=?

Geonames API:邮编的位置信息:

http://www.geonames.org/postalCodeLookupJSON?postalcode=10504&country=US&callback=?

Flickr JSONP API:载入最新猫的图片:

http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?

Yahoo Local Search API:在邮编为 10504 的地区搜索比萨:

http://local.yahooapis.com/LocalSearchService/V3/localSearch?appid=YahooDemo&query=pizza&zip=10504&results=2&output=json&callback=?

接下来我们自己来创建一个简单的远程服务,实现和上面一样的JSONP服务。还是利用Web程序A和程序B来做演示,这次我们在程序B上创建一个MyService.ashx文件。

程序B的MyService.ashx代码:

 1     public class MyService : IHttpHandler
 2     {
 3         public void ProcessRequest(HttpContext context)
 4         {
 5             //获取回调函数名
 6             string callback = context.Request.QueryString["callback"];
 7             //json数据
 8             string json = "{\"name\":\"chopper\",\"sex\":\"man\"}";
 9 
10             context.Response.ContentType = "application/json";
11             //输出:回调函数名(json数据)
12             context.Response.Write(callback + "(" + json + ")");
13         }
14 
15         public bool IsReusable
16         {
17             get
18             {
19                 return false;
20             }
21         }
22     }

程序A的sample代码中的调用:

 1 <script type="text/javascript">
 2     function addScriptTag(src){
 3         var script = document.createElement('script');
 4  script.setAttribute("type","text/javascript");
 5  script.src = src;
 6  document.body.appendChild(script);
 7  }
 8  
 9  window.onload = function(){
10         //调用远程服务
11  addScriptTag("http://localhost:20002/MyService.ashx?callback=person");
12  
13  }
14     //回调函数person
15     function person(data) {
16  alert(data.name + " is a " + data.sex);
17  }
18 </script>

这就完成了一个最基本的JSONP服务调用了,是不是很简单,下面我们来了解下JQuery是如何调用JSONP的。

JQuery对JSONP的实现

jQuery框架也当然支持JSONP,可以使用$.getJSON(url,[data],[callback])方法(详细可以参考http://api.jquery.com/jQuery.getJSON/)。那我们就来修改下程序A的代码,改用jQuery的getJSON方法来实现(下面的例子没用用到向服务传参,所以只写了getJSON(url,[callback])):

<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
 $.getJSON("http://localhost:20002/MyService.ashx?callback=?",function(data){
 alert(data.name + " is a a" + data.sex);
 });
</script>

结果是一样的,要注意的是在url的后面必须添加一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服 务,callback后面的那个问号是内部自动生成的一个回调函数名。这个函数名大家可以Debug一下看看,比如 jQuery17207481773362960666_1332575486681。

当然,加入说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?我们可以使用$.ajax方法来实现(参数较多,详细可以参考http://api.jquery.com/jQuery.ajax)。先来看看如何实现吧:

<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
 $.ajax({
 url:"http://localhost:20002/MyService.ashx?callback=?", 
 dataType:"jsonp",
 jsonpCallback:"person",
 success:function(data){
 alert(data.name + " is a a" + data.sex);
 }
 });
</script>

没错,jsonpCallback就是可以指定我们自己的回调方法名person,远程服务接受callback参数的值就不再是自动生成的回调名,而是person。dataType是指定按照JSOPN方式访问远程服务。

利用jQuery可以很方便的实现JSONP来进行跨域访问。先暂时写到这吧。

[转载]页面JavaScript调试脚本远离alert

mikel阅读(1219)

[转载]从今天开始,调试脚本,远离alert – 两会 – 博客园.

不知有多少人像我一样,几年web开发,无数js代码,调试脚本却只会alert方法。

不知有多少人像我一样,一个简单方法,开发中却需不断alert以确保数值正确,一步一alert,处处都alert,每次都alert到浏览器看不下去,跳出来阻止。

如果你确实苦逼如我,今天看到这篇文章算你福气啦,哥现身说法,教你如何摆脱alert的噩梦。

调试利器–console.log

如今主流浏览器(Chrome,IE8及后续版本,FireFox,Opera等)都支持控制台功能。

Chrome:

image

IE9:

image

FireFox(需安装FireBug插件)

image

当在js代码中调用Console.log方法时,相应的信息就会在控制台中显示。相对于alert方法,有三个优点:

  1. 如参数为一个对象,则可在控制台查看所有属性信息。
  2. 类似C#中的string.Format功能,不需字符串拼接。
  3. 无弹窗,即使不删除,也不会影响页面体验。

调试实例:Google Calendar API

我最近在整合googleAPI的过程中碰到一个问题,google API提交信息需要两个条件:

  1. Https协议。
  2. 需提交JSON格式的数据。

我并不完全了解Https和Http的区别,通过操作才知道监听软件(如Fiddler)拦截不到Https请求的详细信息,而整个过程是Ajax的形式,结果就是–任何一个地方出错,点击按钮完全无响应,却不知错在哪里。

对于第二条,我是第一次碰到要提交JSON格式数据,以往碰到的都是返回JSON数据。

为了走通整个流程,我做了简单的测试代码,三个输入框分别表示标题,开始时间,结束时间和一个提交按钮:

image

js代码如下,手动构造JSON对象tempRes,再发送到google响应的地址:

function makeRestRequest() {
    var tempRes = {
        "summary": $("#summary").val(),
        "start": {
            "dateTime":$("#start").val() // "2012-03-21T10:00:00.000+08:00" },
        "end": {
            "dateTime":$("#end").val() //"2012-03-21T11:00:00.000+08:00" }
    };

    console.log(tempRes);

    gapi.client.request({
        'path': '/calendar/v3/calendars/primary/events',
        'method': 'POST',
        // 'body': resource, 'body': tempRes,
        'callback': writeResponse
    });
}

然而代码完成后,点击按钮毫无响应,于是我就用console.log查看tempRes对象。发现结果如下:

image

图中可以看出,end属性为“undefined”,为什么?在确定输入值格式无误后发现,两个输入框的id重复了,错误代码如下:

 <tr> <td>Start:</td> <td><input type="text" id="start" name="start"/></td> </tr> <tr> <td>End:</td> <td><input type="text" id="start" name="end"/></td> </tr>

于是,改之,走通,完成!

这样的小错,我在编码中经常碰到,而console.log能让我快速定位问题。这里JSON对象属性只有3个,用alert一个一个查能找到,但如果有一二十个,再用alert就会显的笨拙了。

通过浏览器添加断点调试JavaScript代码

调试代码离不开断点。在主流浏览器中,都可以对JavaScript代码添加断点进行调试,虽然功能相似,但相对于chrome和firefox,我更喜欢IE多一点,可能是因为用惯了VS的缘故。

IE的调试界面如下图,右下方的输入框可以方便快捷的查看当前上下文的数值信息。类似VS在调试过程中的“Immediate Window”。

image

小技巧-在VS中编写Js独立文件时添加智能提示

智能提示可以很大程度上减少拼写错误,以提高效率,所以我一旦碰到写代码没有智能提示就浑身不自在。但每当我创建独立的js文件又要用到 JQuery,Knockout之类的框架时,往往不敢下手,就因为没有智能提示。比如Knockout中的一个常用方法 dependentObservable:

this.total = ko.dependentObservable(function () {
            return this.unitPrice * parseInt(this.quantity());
        }, this);

这个方法名太长,不拼错才奇怪,而一旦拼错,往往很难定位。

因此这里的技巧就是:在js开头加入以下代码,让其支持相应框架的智能提示:

///<reference path="~/Scripts/knockout.js"/> ///<reference path="~/Scripts/jquery-1.6.4.js"/> ///<reference path="~/Scripts/jquery.linq.min.js"/> 

加入这些代码后,再编码,心里就有底气了:

image

这一段跟js调试无关,但是通过智能提示减少代码出错率,也算是有所帮助。

结语

以上是我的个人经验分享,记述着我摆脱alert的过程,我相信还有其他更好的js调试方法,如有不对的地方,欢迎批评指出。

[转载]小译 Asp.net MVC 3 Framework 第十七章 模型绑定

mikel阅读(998)

[转载]小译 Asp.net MVC 3 Framework 第十七章 模型绑定 – 喝高了点 – 博客园.

小译 Asp.net MVC 3 Framework 第十七章 模型绑定

有人已经在翻译这本书了,好像翻译到了第16章,等第17章没等到,自己译出来看看。只为自己学习使用。不对的地方大家多批评。谢谢。

第十七章 模型绑定

模型绑定是实用浏览器发送的数据请求创建.net对象的过程。每次我们定义了一个带有参数的action方法时我们都依赖了模型绑定过程——参数对象是被模型绑定创建的。在本章我们将给你展示模型绑定系统怎样工作,并展示要求自定义他的这些技术的高级应用。

理解模型绑定

设想我们在Controller中定义了像清单17-1展示的一个action方法。

清单17-1 A Simple Action Method

using System.Web;

using System.Web.Mvc;

using MvcApp.Models;

namespace MvcApp.Controllers

{

    public class HomeController : Controller

    {

       public ActionResult Person(int id)

        {

            //get a person record from the repository

            Person myPerson = null;//…获取逻辑在这里编写

            return View(myPerson);

        }

    }

}

我们的action方法定义在HomeController中,这意味着Virtual Studio为我们创建的默认路由将调用我们的action方法。提醒一下,下面是默认路由:

routes.MapRoute(

      “Default”, // 路由名称

      “{controller}/{action}/{id}”, // 带有参数的URL

       new { controller = “Home”, action = “Index”, id = UrlParameter.Optional } // 参数默认值

);

当我们接受到一个像 Home/Person/32这样的请求时,MVC框架必须以这样的方式映射请求细节:它能把期望的值或对象作为参数传递给我们的acton方法。

调用acton的组建,Action调用器,在调用action方法前,负责为参数获取值。默认的action调用 器,ControllerActonInvoker(在11章介绍过)依赖于模型绑定器,模型绑定器被接口IModelBinder定义,如清单 17-2.

清单17-2 IModelBinder接口

namespace System.Web.Mvc

{

        public interface IModelBinder

        {

            object BindModel(ControllerContext contrellerContext, ModelBindingContext bingContext);

        }

}

在MVC应用程序中可以有多个模型绑定器,每个绑定器能够负责一个或者多个模型类型。当action调用器需要调用一个action方法时,它(模 型绑定器——我)查看action定义的参数并且负责为每个参数类型找到负责的模型绑定器。在17-1的例子中,action调用器将找到带有一个int 类型的参数的action方法,因此它将找到负责绑定int值的绑定器并且调用它的BindModel方法。如果没有负责int类型数据的绑定器,那么将 使用默认的绑定器。

一个绑定器负责生成合适的action方法参数值,这通常意味着转换一些请求数据的元素(如表单或查询字符串值),但是MVC框架并不限制数据怎样被获 取。我们将在本章会面展示给你一些自定义绑定器的例子并展示一些ModelBindingContext类的特点,这个类被传递给 IModelBinder.BindModel方法(你可以在12章看到其他BindModel参数——ControllerContext类的细 节。)TODO:

使用默认的模型绑定器

尽管一个应用程序可以有多个绑定器,大多数只依赖一个内建的绑定器类,DefaultModelBinder。当应用程序不能找到自定义的绑定器绑定类型时,它将使用默认绑定器。

默认的,绑定器查找如表17-1所示的四个地方,根据参数的名字来匹配和绑定数据。

表17-1 DefaultModelBinder类查找参数数据的顺序

描述
Request.Form 用户的HTML表单元素提供的值
Route.Values 通过应用程序的路由获取到的值
Request.QueryString 请求Url的查询字符串包含的数据
Request.File 作为请求的一部分而被上传的文件(查看“使用模型绑定接受上传文件”一节了解文件上传细节)。

这些地方按顺序被查询,在清单17-1的例子中展示的action方法,DefaultModelBinder类检查我们的action方法并找到一个叫做id的参数,它然后找向下面的一个值:

  1. Request.Form[“id”]
  2. Route.Values[“id”]
  3. Requset.QueryString[“id”]
  4. Request.Files[“id”]
提示:当JSON数据被接受时,这也是一个可用的数据源。我们会在19章中解释更多JSON并展示他怎么工作。

当找到一个值后搜索停止,基于我们的例子,表单数据,路由数据值将被搜索。因为在第二个位置中会找到路由中id这一节,所以查询字符串和上传的文件根本不会被搜索。

提示:你会看到action方法中参数的名字是多么重要——参数的名字和请求数据项的名字必须匹配,以便DefaultModelBinder类找到并使用数据。

绑定简单类型

当处理简单数据类型时,DefaultModelBinder 尝试使用System.ComponentModel.TypeDescripyter类把从请求数据中获取的string值转换成参数的类型。

如果这个值不能被转换——例如,我们提供了一个apple给一个int类型的参数,DefaultModelBinder将不能绑定参数给模型。

如果我们想避免这个问题,我们可以修改我们的参数。我们可以用一个可用类型,像这样:

       public ActionResult Person(int? id) {

         如果我们使用了这个方法,从请求中找到的可转换的值,如果没有匹配,id参数的值将是空。对应的,当没有可成功转换的数据时,我们可以提供一个默认值,使我们的参数可选。像这样:

public ActionResult Person(int? id=23) {

文化敏感解释

DefaultModelBinder类根据不同地区的请求数据为平台使用不同的文化设置。从Url中获取的值被转换时使用了文化敏感解释,从数据中获取的值被转换时会考虑文化。

DateTime类型的值常常引起问题。我们期望文化敏感的日期使用通用的格式 yyyy-mm-dd。期望表单日期值的格式与服务器指定的格式一致。这意味着,设定英国文化的服务器期望日期是这种格式dd-mm-yyyy,设定美国 文化的服务器希望日期格式是mm-dd-yyyy,尽管yyyy-mm-dd这种格式的日期也可以被接受。

如果一个日期值得格式不正确,那么它将不能不转换。这意味着所有日期格式数据包括Url中的,必须使用通用格式。当我们处理用户提供的日期值时,我们也必须小心。默认的绑定器假定用户以服务器文化表示日期数据,MVC用户都是国际用户是不太可能发生的。

绑定复杂类型

当action方法参数是复杂类型时(换句话说,任何类型都不能使用TypeConverter类转换),DefaultModelbinder使用反射 获取公共属性集合,并按顺序绑定它们。清单17-3展示了我们在前面章节中使用的Person类。我们将再次使用它来演示复杂类型的模型绑定。

清单17-3 一个复杂模型类

public class Person

    {

        [HiddenInput(DisplayValue=false)]

        public int PersonId { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        [DataType(DataType.Date)]

        public DateTime Birthday { get; set; }

        public Address HomeAddress { get; set; }

        public bool IsApproved { get; set; }

        public Role Role { get; set; }

    }

         默认的模型绑定器检查这个类的属性是不是简单类型。如果是,绑定器从请求数据中查找与属性名相同的数据项。这就是说FirstName属性将使绑定器查找名称为FirstName的数据项。

如果属性是复杂类型,绑定器将为这个新类型重复上面的操作;公共属性的集合将被获取,然后绑定器尝试为他们找到对应的值。不同的地方是,属性名是嵌套的,例如Person类中的HomeAddress属性是Address类型的,如清单17-4所示

清单17-4 嵌套模型类

    public class Address

    {

        public string Line1 { get; set; }

        public string Line2 { get; set; }

        public string City { get; set; }

        public string PostalCode { get; set; }

        public string Country { get; set; }

    }

当为Line1属性查找值时,模型绑定器将查找HomeAddress.Line1的值——换句话说,模型对象的属性名与属性类型中的属性名组合了。

创建易邦定的HTML

遵循这种格式的最简单的创建HTML的方式是使用我们在第16章介绍的模板视图辅助器。当我们在一个带有Person类型的视图模型的视图中调用@Html.EditorFro(m=>m.FirstName)时,我们会的到如下的HTML:

<input class=”text-box sigle-line” id=”FirstName” name=”FirstName” type=”text” Value=”Joy”/>

当调用@Html.EditorFro(m=>m.HomeAddress.Line1)时 ,得到的如下

<input class=”text-box sigle-line” id=”HomeAddress_Line1” name=” HomeAddress.Line1” type=”text” Value=”123 North Street”/>

你能看到HTML的name属性被自动的设置了值,这个值是模型绑定器需要查询的。我们可以手动的创建HTML,但是我们喜欢由模型绑定器做这种事情带来的方便。

制定自定义前缀

当模型绑定器查询数据项的时候我们可以为它制定一个自定义的前缀。如果在发送给客户端的HTML中包含了额外的模型对象,那么这是十分有用的。作为一个例子, 考虑清单17-5展示的视图。

清单17-5 添加额外的视图模型对象数据给响应

@using MvcApp.Models;

@model MvcApp.Models.Person;

@{

    Person myPerson = new Person()

    {

        FirstName=”Jane”,LastName=”Doe”

    };

}

@using (Html.BeginForm()) {

    @Html.EditorFor(m=>myPerson)

    @Html.EditorForModel()

    <input type]=”submit” value=”Submit” />

}

虽然可以很容易的使用ViewBag传递视图,但在我们创建的视图中,我们创建了一个Person对象,并使用了EditorFor辅助器来为它生 成HTML。Lambda表达式的输入是模型对象(用m表示),但是我们忽略了它并范围我们的第二个作为渲染目标的Person对象。我们也调用了 EditerForModel辅助器以便发送给用户的HTML中的数据来自两个Person对象。

当我们像这样渲染视图时,模板视图辅助器给HTML元素的name属性申请一个前缀,这用来与主视图模型数据区分。前缀的名称来自变量名,myPerson。例如下面的HTML是视图中被渲染的FirstName属性

<input class=”text-box single-line” id=”myPerson_FirstName” name=“myPerson.FirstName” type=”text” value=”Jane” />

这个元素的name属性的值被创建时,通过变量名——myPerson.FirstName 给属性名赋值。模型绑定器期望这种方式,并且当查找数据的时候,它可能使用action方法的参数名作为前缀。如果我们提交表单的action方法具有如下的签名:

public ActionResult Index(Person firstPerson, Person myPerson) {

第一个参数对象将使用没有前缀的数据来绑定,第二个参数绑定是将查找以参数名开头 的数据,可以是myPerson.FirstName, myPerson.LastName等。

如果我们不想让我们的参数名与视图的内容以这种方式绑定,我们可以使用Bind特性

指定一个自定义的前缀,如清单17-6所示

清单17-6 使用Bind特性指定自定义数据前缀

public ActionResult Register(Person firstPerson, [Bind(Prefix = “myPerson”)]Person secondPerson) {

         我们把前缀设置为myPerson,它的意思是默认模型绑定器将使用myPerson作为数据项的前缀,即使参数的名称是secondPerson。

可选的绑定属性

设想一下,Person类的IsApproved属性是非常敏感的,我们可以组织模型中的属性被渲染成HTML时使用第16章的技术,但是一个恶意用户提 交表单时可以简单的在Url后添加?IsAdmin=true。如果他这样做了,模型绑定器会发现并在处理绑定的时候使用这些数据。

幸运的是,我们可以使用Bind特性在处理绑定时包含或排除模型的属性。要指定只有特定的属性被包含在内,我们可以Bind特性的Include属性赋值,如清单17-7所示

清单17-7 使用Bind特性来包含模型属性到绑定处理程序

public ActionResult Register([Bind(Include = “FirstName,LastName”)]Person person) {

这个清单指定了只有FirstName,LastName属性被包含在绑定程序中;Person类的其他属性将被忽略。相应的,我们可以指定被排除在外的属性,如清单17-8所示

清单17-8使用Bind特性从绑定处理程序排除模型属性

public ActionResult Register([Bind(Exclude = “IsApproved,Role”)]Person person) {

这个清单告诉模型绑定器,包含所有属性到绑定处理程序,除了IsApproved和Role。

当我们像这样使用Bind特性的时候,它只应用于单个的Action方法。如果我们想把我们的规则应用到所有Controller中的所有action方法,我们可以在模型类本身中使用Bind特性,如清单17-9。

清单17-9 使用Bind特性到模型类

[Bind(Exclude = “IsApproved”)]

    public class Person

    {

        [HiddenInput(DisplayValue=false)]

        public int PersonId { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        [DataType(DataType.Date)]

        public DateTime Birthday { get; set; }

        public Address HomeAddress { get; set; }

        public bool IsApproved { get; set; }

        public Role Role { get; set; }

    }

 这样使用特性与应用它到单个的action方法参数有相同的作用,但是任何使用模型绑定器绑定Person对象的时候它都会被应用。

提示:如果Bind特性被同时应用到了模型类和action方法参数,任何一个都不排除的属性才会被包含到绑定处理程序中。这意味着应用于模型类的规则不会被包含更少限制的应用于action方法参数的规则重写。

 

绑定数组和集合

默认模型绑定器的一个优雅特性是他如何处理具有相同名称的多个数据项。例如,考虑清单17-10展示的视图。

清单17-10 渲染具有相同名称name属性的HTML元素的视图

@{

    ViewBag.Title = “Movies”;

}

Enter your three favorite movies:

@using (Html.BeginForm())

{

    @Html.TextBox(“movies”)

    @Html.TextBox(“movies”)

    @Html.TextBox(“movies”)

    <input type=”submit” />

}

我们已经使用了Html.EditorFor辅助器创建了三个输入元素;这些元素将被创建并且name属性具有相同的值——movies。像这样:

<input id=”movies” name=”movies” type=”text” value=”” />

<input id=”movies” name=”movies” type=”text” value=”” />

<input id=”movies” name=”movies” type=”text” value=”” />

我们可以用如清单17-11所示的action方法接受用户输入的值。

清单17-11 在一个action方法中接受多个数据项

                 [HttpPost]

        public ActionResult Movies(List<string> movies){

                   … …

模型绑定器将找到所有用户提供的数据,并且把他们放到一个List<string>中传递给Movies动作方法。绑定器为不同的参数类型提供了灵敏的支持。我们可以选择用string[]或IList<string>接受数据。

为自定义类型绑定集合

多值绑定的技巧非常好,但是如果我们想让它应用于自定义类型,我们不得不编写特定格式的HTML。清单17-12展示了我们怎样为一个Person对象的数组做这件事。

清单17-12 为自定义对象集合生成HTML

… …

  <h4>Person number :0</h4>

    First Name:<input class=”text-box single-line” name=”[0].FirstName” type=”text” value=”Tom” />

    Last Name:<input class=”text-box single-line” name=”[0].LastName” type=”text” value=”Green” />

    <h4>Person number :1</h4>

    First Name:<input class=”text-box single-line” name=”[1].FirstName” type=”text” value=”Jane” />

    Last Name:<input class=”text-box single-line” name=”[1].LastName” type=”text” value=”Smith” />

… ….

为了绑定这些数据,我们只需要定义一个接收视图对象类型的集合参数的action方法,如清单17-13所示

清单17-13 绑定可索引的集合

[HttpPost]

public ActionResult Register(List<Person> people) {

因为我们要绑定集合,默认的模型绑定器将根据以索引下标为前缀的值为Person类查找值。当然我们不能用模板辅助器来生成HTML;我们会在视图中明确的指定它,如清单17-14演示的。

清单 17-14  创建绑定集合的HTML元素

… …

<h4>First Person</h4>

First Name:@Html.TextBox(“[0].FirstName”)

Last Name:@Html.TextBox(“[0].LastName”)

<h4>Scond Person</h4>

First Name:@Html.TextBox(“[1].FirstName”)

Last Name:@Html.TextBox(“[1].LastName”)

… …

只要我们确保索引值被适当的生成了,模型绑定器就能找到并绑定我们定义的数据元素。

绑定无序索引的集合

顺序的数字索引值的另一方面是,使用任意的字符型键来定义集合数据项。这在我们想使用客户端JavaScript来动态添加或移除控件并且不用担心维护索引序列时是十分有用的。使用这个功能,我们需要定义一个被称为索引的隐藏数据元素来指定数据项的键,如清单17-15。

清单17-15 为数据项指定任意的键

<h4>First Person</h4>

<input type=”hidden” name=”index” value=”firstPerson” />

First Name:@Html.TextBox(“[firstPerson].FirstName)

Last Name:@Html.TextBox(“[firstPerson].LastName)

<h4>Scond Person</h4>

<input type=”hidden” name=”index” value=”secondPerson” />

First Name:@Html.TextBox(“[secondPerson].FirstName)

Last Name:@Html.TextBox(“[secondPerson].LastName)

我们给输入元素的name值加上前缀使他能匹配隐藏索引元素。模型绑定器检测索引并在绑定处理过程中使用它来关联数据值。

绑定字典

在绑定字段时,默认模型绑定器也是可用的,但是我们只能遵循一个非常明确的命名序列。清单17-16提供了一个演示。

清单17-16 绑定字典

<h4>First Person</h4>

<input type=”hidden” name=”[0].key” value=”firstPerson” />

First Name:@Html.TextBox(“[0].value.FirstName”)

Last Name:@Html.TextBox(“[0].value.LastName”)

<h4>Scond Person</h4>

<input type=”hidden” name=“[1].key” value=”secondPerson” />

First Name:@Html.TextBox(“[1].value.FirstName”)

Last Name:@Html.TextBox(“[1].value.LastName”)

当绑定字段Dictionary<string,Person>或Dictionary<string,Person>时,字典将包含键为firstName和lastName的Person对象。我们可以用这样的action方法接收数据:

 [HttpPost]

public ActionResult Register(IDictionary<string,Person> people) {

… …

手动调用模型绑定

当action方法定义参数后,模型绑定处理程序会自动执行,但是我们也可以直接控制执行。这使我们对模型对象的实例化过程中数据值从哪里获取,数据解析错误如何被处理,有了更精确的控制。清单17-17演示了action方法中如何手动调用绑定处理程序。

清单17-17 手动调用模型绑定程序

[HttpPost]

        public ActionResult RegisterMember()

        {

            Person myPerson = new Person();

            UpdateModel(myPerson);

            return View(myPerson);

        }

UpdateModel方法用我们之前创建的模型对象作为参数并尝试使用标准绑定处理程序从这个对象的公共属性中获取值。使用手动模型绑定的原因之 一是为了在模型对象中支持依赖注入(DI)。例如,如果我们使用了应用程序范围的依赖分析器(我们在第10章中描述过),我们可以再创建Person模型 对象中添加DI,如清单17-18演示。

清单17-18 为模型对象创建添加依赖注入

        [HttpPost]

        public ActionResult RegisterMember()

        {

            Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));

            UpdateModel(myPerson);

            return View(myPerson);

        }

我们演示的不是唯一的在绑定处理程序中引入DI的方式,我们会在本章中展示其他的方式。

绑定特定的数据源

当我们手动调用绑定处理程序时,我们可以限制绑定处理程序到简单的数据源。默认地,绑定器查找四个地方:表单数据,路由数据,查询字符串和上传的文件。清单17-19展示了我们怎样限制绑定器只搜索单一的位置——在本例中是表单数据。

清单17-19 限制绑定器到表单数据

[HttpPost]

        public ActionResult RegisterMember()

        {

            Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));

            UpdateModel(myPerson,new FormValueProvider(ControllerContext));

            return View(myPerson);

        }

在这个版本的UpdateModel方法中,它接受一个实现了IValueProvider接口的实现类作为绑定程序获取数据值的唯一数据来源。这四个地方中的每个都有一个IValueProvider的实现,如表17-2所示

表17-2 内置的IValueProvider实现

IValueProvider实现
Request.Form FormValueProvider
Route.Values RouteDataValueProvider
Requset.QueryString QueryStringValueProvider
Request.Files HttpFileCollectionValueProvider

表17-2中列出的没有个类都接收一个ControllerContext的构造器参数,我们可以从Controller的同名属性中得到。属性名称如上表展示。

约束数据来源的最常用的方式是:只查看表单中的值。在使用时,有一个非常灵巧的技巧,我们不用创建一个FormValueProvider的实例,如清单17-20所示。

清单17-20 限制绑定器的数据来源

[HttpPost]

        public ActionResult RegisterMember(FormCollection formData)

        {

            Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));

            UpdateModel(myPerson, formData);

            return View(myPerson);

        }

FormCollection类实现了IValueProvider接口,并且如果我们定义了一个接收FormCollection类型参数的action方法,模型绑定器将为我们提供一个可以直接传递给UpdateModel方法的对象。

提示:UpdateModel的其他重载版本,允许我们为查询指定一个前缀,并且允许我们指定模型的那些属性被包含到绑定处理程序中。

处理绑定错误

用户提供的值不能被绑定到相应的模型属性,这是不可避免的,如不正确的日期值,文本对应数字值。当我们明确的调用模型绑定是,我们需要处理这样的错 误。模型绑定器抛出InvalidOperationException异常来提示错误。错误的细节可以在ModelState(我们在第18章中描述 过)中找到。当我们调用UpdateModel方法时,我们要准备好获取异常并使用ModelState给用户表示错误消息。如清单17-21。

清单17-21 处理模型绑定错误

        public ActionResult RegisterMember(FormCollection formData)

        {

            Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));

            try

            {

                UpdateModel(myPerson, formData);

            }

            catch (InvalidOperationException ex)

            {

                                     //provide UI feedback based on modelstate

            }

            return View(myPerson);

        }

作为二选一的方法,我们可以使用TryUpdateModel方法。当模型绑定成功时它返回true,否则返回false。如清单17-22。

清单17-22 使用TryUpdateModel方法

[HttpPost]

        public ActionResult RegisterMember(FormCollection formData)

        {

            Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));

            if (TryUpdateModel(myPerson,formData))

            {

                // process as normal

            }

            else

            {

                //provide UI feedback based on ModelState

            }

            return View(myPerson);

        }

我们更喜欢TryUpdateModel方法的唯一原因是我们不想捕获并处理异常。这两方法在绑定处理程序中并没有什么不同。

提示:当模型绑定自动被调用时,绑定错误并不抛出异常。我们必须检查ModelState.IsValid属性的结果。我们会在第18章中介绍。

使用模型绑定接收上传文件

对于接受上传文件,所有我们需要做的是定义一个参数类型为HttpPostedFileBase的action方法。模型绑定器将把数据作为一个上传的文件来处理。清单17-23展示了action方法接受上传文件。

清单17-23 action方法接受上传文件

[HttpPost]

       public ActionResult Upload(HttpPostedFileBase file)

        {

            //Save the file to disk on the server

            string fileName = “myFileName”;//pick a name

            file.SaveAs(fileName);

            //or work with the data directly

            byte[] uploadedBytes = new byte[file.ContentLength];

            file.InputStream.Read(uploadedBytes, 0, file.ContentLength);

            //now do something with uploadedBytes

        }

我们必须以特定的格式创建HTML表单来允许用户上传文件,如清单17-24所示。

清单17-24 允许用户上传文件的视图

@{

    ViewBag.Title = “Upload”;

}

<h2>Upload</h2>

<form action=”@Url.Action(“Upload”)” method=”post” enctype=”multipart/form-data”>

Upload a photo:<input type=”file” name=”photo” />

<input type=”submit” />

</form>

关键是我们要设置enctype=”multipart/form-data”,如果不这样做,浏览器只会提交文件的名字而不会提交文件本身(浏览器就是这样工作的,并不只准备MVC框架)。

在这个清单中我们使用了逐字的HTML来渲染表单控件,我们也可以用Html.BeginForm来生成,但是只能使用具有四个参数的重载版本,我们认为逐字的HTML更容易阅读。

自定义模型绑定系统

我们已经展示了默认模型绑定处理过程,正如你可能期望的,我们有一些不同的方式来自定义绑定系统。在下面的章节中我们会展示一些例子。

创建自定义值提供器

自定义值提供器,我们可以为模型绑定处理程序添加我们自己的数据源,值提供器实现了IValueProvider接口,如清单17-25所示:

清单17-25 IValueProvider接口

namespace System.Web.Mvc {

    using System;

    public interface IValueProvider {

        bool ContainsPrefix(string prefix);

        ValueProviderResult GetValue(string key);

    }

}

被模型绑定器调用的ContainsPrefix方法用来判断值提供器是否可以为给定的前缀解析数据。GetValue方法为给定的数据键返回值, 当提供器没有匹配的数据时会返回null,清单17-26展示了值提供器为CurrentTime属性绑定时间戳,我们在实际应用程序中很少这么做,但这 是一个很好的演示。

清单17-26  自定义IValueProvider实现

    public class CurrentTimeValueProvider : IValueProvider

    {

        public bool ContainsPrefix(string prefix)

        {

            return string.Compare(“CurrentTime”, prefix, true) == 0;

        }

        public ValueProviderResult GetValue(string key)

        {

            return ContainsPrefix(key) ? new ValueProviderResult(DateTime.Now, null, CultureInfo.InvariantCulture) : null;

        }

    }

我们想在请求CurrentTime是做出响应。当我们得到一个这样的请求,我们返回静态的DateTime.Now属性的值。对于所有其他的请求我们返回null,这表明我们不提供数据。

我们必须以ValueProviderResult的形式返回我们的数据值。这个类有三个构造器参数,第一个是我么想与请求的键关联的数据项,第二 个参数用于跟踪模型绑定错误,我们并不在我们的例子中使用。最后一个参数是与值相关的文化信息,我们制定了InvariantCulture。

为应用程序注册我们的值提供器,我们需要创建一个用于生成我们的提供器的工厂类,这个类派生自抽象类ValueProviderFactory。清单17-27展示了为CurrentTimeProvider创建 的工厂类。

清单17-27 自定义值提供器工厂类

    public class CurrentTimeValueProviderFactory : ValueProviderFactory

    {

        public override IValueProvider GetValueProvider(ControllerContext controllerContext)

        {

            return new CurrentTimeValueProvider();

        }

    }

当模型绑定器为绑定处理程序获取值得时候GetValueProvider方法被调用。在我们的实现中,我们只简单的创建并返回了CurrentTimeValueProvider的一个实例。

最有一步是为应用程序注册工厂类,我们在Global.asax文件的Application_Start方法中操作。如清单17-28。

清单17-28 注册值提供器工厂

        protected void Application_Start()

        {

            AreaRegistration.RegisterAllAreas();

            ValueProviderFactories.Factories.Insert(0, new CurrentTimeValueProviderFactory());

            RegisterGlobalFilters(GlobalFilters.Filters);

            RegisterRoutes(RouteTable.Routes);

        }

我们通过向静态的ValueProviderFactories.FactoriesCollection中 添加一个实例来注册我们的工厂类。正如我们之前解释的那样,模型绑定器按顺序查看值提供器。如果我们想让我们自定义的提供器优先于内建的提供器,我们必须 使用Insert方法把我们的提供器插入到集合的第一个位置,正如清单展示的那样。如果我们想在其他的提供器不能提供数据时使用我们的提供器,我们需要使 用Add方法把我们的提供器缀到集合的末尾,像这样:

ValueProviderFactories.Factories.Add(new CurrentTimeValueProviderFactory());

… …

我们可以定义一个带有DateTime类型的CurrentTime参数的action方法来测试我们的提供器。

如清单17-29。

清单17-29 使用自定义提供器的action方法

        public ActionResult Clock(DateTime currentTime)

        {

            return Content(“The Time is” + currentTime.ToLongTimeString());

        }

以为我们的提供器是模型绑定器请求数据的第一个提供器,我们能够为参数绑定一个值。

创建依赖敏感的模型绑定器(Creating a Dependency-Aware Model Binder)

我们展示过怎样为绑定处理程序引入依赖注入的手动模型绑定,但是更优雅的方式是创建一个源于DefaultModelBinder类并重载CreateModel方法的DI-Aware的绑定器,如清单17-30。

清单17-30 创建DI-Aware的模型绑定器

using System.Web;

using System.Web.Mvc;

namespace MvcApp.Infrastructure

{

    public class DIModelBinder:DefaultModelBinder

    {

        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)

        {

            return DependencyResolver.Current.GetService(modelType) ?? base.CreateModel(controllerContext, bindingContext, modelType);

        }

    }

}

这个类使用应用程序范围的依赖解析器创建模型对象,并且必要时调用基类的实现(使用System.Activator的默认构造器创建实例)。

提示:查看第十章了解NInject依赖解析器类了解详细。

我们必须把我们的绑定器作为默认绑定器注册到应用程序中,我们在Global.asax文件的Application_Start方法中操作。如清单17-31。

清单17-31 注册默认模型绑定器

        protected void Application_Start()

        {

            AreaRegistration.RegisterAllAreas();

            ModelBinders.Binders.DefaultBinder = new DIModelBinder();

            RegisterGlobalFilters(GlobalFilters.Filters);

            RegisterRoutes(RouteTable.Routes);

        }

模型绑定处理程序现在可以创建具有依赖性的模型对象了。

创建自定义模型绑定器

我们能通过为指定的类型创建自定义的模型绑定器来重载默认绑定器的行为。清单17-32提供了一个演示。

清单17-32 自定义模型绑定器

    public class PersonModelBinder:IModelBinder

    {

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

        {

            // see if there is a existing model to update and create one if not

            Person model = (Person)bindingContext.Model ?? (Person)DependencyResolver.Current.GetService(typeof(Person));

            //find out if the value provider has the required prefix

            bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);

            string searchPrefix = (hasPrefix) ? bindingContext.ModelName + “.” : “”;

            //populate the fields of the model object

            model.PersonId = int.Parse(GetValue(bindingContext, searchPrefix, “PersonId”));

            model.FirstName = GetValue(bindingContext, searchPrefix, “FirstName”);

            model.LastName = GetValue(bindingContext, searchPrefix, “LastName”);

            model.Birthday = DateTime.Parse(GetValue(bindingContext, searchPrefix, “Birthday”));

            model.IsApproved = GetCheckedValue(bindingContext, searchPrefix, “IsApproved”);

            model.Role = (Role)Enum.Parse(typeof(Role), GetValue(bindingContext, searchPrefix, “Role”));

            return model;

        }

        private string GetValue(ModelBindingContext context, string prefix, string key)

        {

            ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);

            return vpr == null ? null : vpr.AttemptedValue;

        }

        private bool GetCheckedValue(ModelBindingContext context, string prefix, string key)

        {

            bool result = false;

            ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);

            if (vpr!=null)

            {

                result = (bool)vpr.ConvertTo(typeof(bool));

            }

            return result;

        }

    }

这个类有很多内容,我们会打碎它并以我们的方式一步一步解析它。首先我们获取我们将要绑定的模型对象,如下

Person model = (Person)bindingContext.Model ?? (Person)DependencyResolver.Current.GetService(typeof(Person));

当我们手动调用模型绑定处理程序时,我们传递一个模型对象给UpdateModel方法,这个对象对于BindingContext方法的 Model属性是可用的。一个好的模型绑定器将检查是否有模型对象可用,如果有,把它用于绑定处理程序。否则我们用应用程序范围的依赖解析器创建一个模型 对象(第十章中讨论过)。

下一步我们看值提供器提供的请求数据值是否需要应用前缀:

bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);

            string searchPrefix = (hasPrefix) ? bindingContext.ModelName + “.” : “”;

bindingContext.ModelName属性返回我们正绑定的模型的名字,如果我们已经在视图中渲染了模型对象,我们生成的HTML将不带有前缀,但是不管怎样ModelName属性将返回action方法的参数名。我们可以检查值提供器看是否有前缀存在。

我们通过BindingContext.ValueProvider方法值提供器,这给了我们对于所有可用的值提供器访问的统一入口,并且请求将按照之前我们在本章中讨论的顺序传递给他们。如果数据值中存在前缀,我们将使用它。

下一步我们所有值提供器从Person模型对象中获取值,下面是例子:

model.PersonId = int.Parse(GetValue(bindingContext, searchPrefix, “PersonId”));

我们在模型绑定器中定义了一个GetValue方法,它用来从统一的值提供器中获取ValueProviderResult对象并且从AttempedValue属性中摘录一个string类型的值。

我们在第15章解释了,当我们渲染Checkbox时,HTML辅助器方法创建一个隐藏输入元素来确保当没有选项被选中 时我们能接收到一个值。这给我们带来了一个小问题,当模型绑定的时候,值提供器将以一个数组的形式给我们两个值,解决它,我们可以使用 ValueProviderResult.ConvertTo方法来判断并给我们一个正确的值:

result = (bool)vpr.ConvertTo(typeof(bool));

提示:在这个绑定器中,我们不进行输入验证,我们乐观的假定了用户为Person类的属性提供的数据都是有效的。我们在第18章中讨论验证,此刻我们更关注基本的模型绑定处理。

一但我们为我们感兴趣的属性设置了值(为了简化程序,我们省略了HomeAddress属性)我们将返回我们构建的模型对象。注册我们的模型绑定器,我们在Global.asax文件的Application_Start方法中操作。如清单17-33。

清单17-33 注册自定义模型绑定器

        protected void Application_Start()

        {

            AreaRegistration.RegisterAllAreas();

            ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder());

            RegisterGlobalFilters(GlobalFilters.Filters);

            RegisterRoutes(RouteTable.Routes);

        }

我们使用ModelBinders.Binders.Add方法注册我们的绑定器,传入我们绑定器支持的类型并实例化绑定器类。

创建模型绑定器提供器

另一个注册模型绑定器的方法是通过实现IModelBinderProvider接口创建一个模型绑定器提供器。如清单17-34所示。

清单17-34 自定义模型绑定器提供器

using System.Web.Mvc;

using MvcApp.Models;

namespace MvcApp.Infrastructure

{

    public class CustomModelBinderProvider:IModelBinderProvider

    {

        public IModelBinder GetBinder(Type modelType)

        {

            return modelType == typeof(Person) ? new PersonModelBinder() : null;

        }

    }

}

如果我们有多种类型的多个绑定器或者我们有多个提供器需要维护,这正方式更灵活。我们在Global.asax文件的Application_Start方法中注册我们的绑定器提供器,如清单17-35。

         protected void Application_Start()

        {

            AreaRegistration.RegisterAllAreas();

            ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());

            RegisterGlobalFilters(GlobalFilters.Filters);

            RegisterRoutes(RouteTable.Routes);

        }

使用模型绑定器特性

最后一种注册自定义的模型绑定器的方式是为模型类应用ModelBinder特性,如清单17-36所示。

清单17-36 使用ModelBinder特性指定自定义的模型绑定器

    [ModelBinder(typeof(PersonModelBinder))]

    public class Person

    {

        [HiddenInput(DisplayValue=false)]

        public int PersonId { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        [DataType(DataType.Date)]

        public DateTime Birthday { get; set; }

        public Address HomeAddress { get; set; }

        public bool IsApproved { get; set; }

        public Role Role { get; set; }

    }

ModelBinder特性的唯一参数是绑定某种类型对象的模型绑定器的类型。我们指定了PersonModelBinder。对于这三种方式,我 们倾向于使用实现IModelBinderProvider接口来处理复杂的需求。它感觉与MVC框架的其他部分更一致。或者简单的使用 [ModelBinder]特性来处理只关联一种特定模型类型的模型绑定器。不管怎样,所有这些方式产生相同的行为,使用哪一种方式并不十分在重要。

小结

在本章中,我们介绍了模型绑定处理程序的工作,展示了默认模型绑定器的操作和不同的自定义处理程序的方式。许多MVC应用程序只需要使用默认的模型 绑定器,它能很好的与处理辅助器生成的HTML结合,但是,对于许多高级的应用程序,创建自定义绑定器来以明确、高效的方式创建模型对象是非常有用的。下 一张我们将演示怎样校验模型对象和当接受到无效数据时怎样向用户程序更有意义的错误。

[转载]C#中的时间日期加减法运算

mikel阅读(1645)

[转载]C#中的时间日期加减法运算 – MrRock – 博客园.

方法是:
AddYears();//年份的加减
AddMonths();//月份的加减
AddDays();//日期的加减
AddHours();//小时的加减
AddMinutes();//分钟的加减
AddSeconds();//秒的加减
AddMilliseconds();//毫秒的加减用法:
DateTime dt = DateTime.Now; //首先我们需要DateTime的变量,你可以把你的符合格式的string直接Convert.ToDateTime(),这里我就用当前日期
dt.AddYears(1); //增加一年
dt.AddMonths(-1);//减少一个月
dt.AddDays(-20d);//减少20天
dt.AddHours(+20d);//增加20小时
dt.AddMinutes(10d);//增加10分钟 ……………….. 上面是一个日期自己的加减法,在附上,日期之间的加减法:
DateTime dt =……//减数
DateTime dt_n = DateTime.Now;//被减数 long x = dt .ToFileTime();//表示自协调世界时 (UTC) 公元 (C.E.) 1601 年 1 月 1 日午夜 12:00 以来已经过的是协调世界时 (UTC)。 long y = dt_n.ToFileTime();long z = x-y; //俩个时间相差的微秒数,然后你就可以自己转化成如时分秒等你想要的时间了
1秒=1000毫秒(ms), 1毫秒=1/1000秒(s); 
1秒=1000000 微秒(μs), 1微秒=1/1000000秒(s); 
1秒=1000000000 纳秒(ns),1纳秒=1/1000000000秒(s); 
1秒=1000000000000皮秒 1皮秒==1/1000000000000秒。
ASP.NET C#如何获取当月的第一天和最后一天
DateTime now = DateTime.Now; DateTime d1 = new DateTime(now.Year, now.Month, 1);
DateTime d2 = d1.AddMonths(1).AddDays(-1);
d1是本月的第一天,d2本月的最后一天, 最后一天的算法是:得到本月的第一天然后增加一月,再减去一天,

[转载]jquery星级插件、支持页面中多次使用

mikel阅读(841)

[转载]jquery星级插件、支持页面中多次使用 – 钊子 – 博客园.

我从网上收集一个JQuery星级插件的,它只支持一个页面中使用一次,多次使用的话会发生冲突,达不到我项目的需求,没办法,只能修改它,

效果图如下:

css所需背景图片:


二话不说,帖代码:

html代码

<script type="text/javascript" src="js/jQuery_1.42.js"></script>

<script type="text/javascript">

    $(function(){

      $(".rating-wrap a").mouseover(function(){

           $(this).parent().siblings().find("a").removeClass("active-star");

           $(this).addClass("active-star");

           $("."+$(this).parent().parent().attr("id")).html($(this).attr("data-hint"))

        }).mouseleave(function(){

           var selectID=$(this).parent().parent().attr("id")+"select";

           $(this).removeClass("active-star");

           if($("#"+selectID).length==0)

           { 

               $("."+$(this).parent().parent().attr("id")).removeClass("active-hint").html("点击星星开始打分");  

           }

           else

           { 

               $("."+$(this).parent().parent().attr("id")).html($("#"+selectID).attr("data-hint"));

               $("#"+selectID).addClass("active-star");

           } 

       }).click(function(){

           $(this).addClass("active-star").attr('id',$(this).parent().parent().attr("id")+"select");

           $(this).parent().siblings().find("a").attr("id","");

           $("."+$(this).parent().parent().attr("id")).html($(this).attr("data-hint")).addClass("active-hint");

       })

    })

</script>

css代码

<style>
.item-rank-rst, .user-rank-rst, .rating-wrap ul, .rating-wrap a:hover, .rating-wrap .active-star, .user-m-star, .urr-rank60, .breadcrumb .note {
    background-image: url(xing_bg.png);/**-----星级插件背景图片----**/
    background-repeat: no-repeat;
}
.rating-wrap {
    background: none repeat scroll 0 0 #FFF9F1;
    border: 1px solid #EFE0D7;
    display: inline-block;
    float: left;
    height: 20px;
    margin-right: 5px;
    padding: 4px 0 0 5px;
    position: relative;
    top: -2px;
    width: 89px;
    z-index: 0;
}
.rating-wrap ul {
    background-position: 0 -250px;
    height: 16px;
    position: relative;
    width: 85px;
    z-index: 10;
}
.rating-wrap li {
    display: inline;
}
.rating-wrap a {
    display: block;
    height: 16px;
    left: 0;
    position: absolute;
    top: 0;
}
.rating-wrap .five-stars {
    background-position: 0 -160px;
    width: 84px;
    z-index: 10;
}
.rating-wrap .four-stars {
    background-position: 0 -178px;
    width: 68px;
    z-index: 20;
}
.rating-wrap .three-stars {
    background-position: 0 -196px;
    width: 51px;
    z-index: 30;
}
.rating-wrap .two-stars {
    background-position: 0 -214px;
    width: 34px;
    z-index: 40;
}
.rating-wrap .one-star {
    background-position: 0 -232px;
    width: 17px;
    z-index: 50;
}
.rating-block .hint {
    color: #999999;
    float: left;
}
.active-hint {
    color: #CC0000;
}
.rating-block .err-hint {
    color: #EE0000;
    font-weight: bold;
}
</style>

[转载]工作流在多组织架构下费用报销的应用

mikel阅读(859)

[转载]工作流在多组织架构下费用报销的应用 – zdming – 博客园.

由于以前是各事业部有各自的报销流程,虽然整合在同一系统中,但其实是不同的流程,如集团报销流程、各事业部报销流程等,造成这一情况的原因是:公司分布 地域比较广泛,组织机构庞大,如:总公司下有集团财务,总公司除财务还有信息,公共事务等部门;各事业部有事业部财务,各事业部还有其他职能部门;各区域 有区域财务,另区域下设有区域办,同时管理所辖门店;另外加一个大型物流中心。所以,目前公司需要整合以前的费用报销流程,使之规范化。

按上述描述分析,以前在进行费用报销时,完全是按照行政组织进行路由。这种组织架构,在只有一个财务管理中心直管全司的情况下,完全没有问题。但是,根据公司目前实际情况,那么必须对组织构架模型进行扩展。

具体是想引入工作流引擎,在同一流程中实现不同组织下的费用报销。因为目前公司组织机构比较复杂,不同的组织,财务管理中心也不一样。各事业部财务管理下辖机构所有财务业务,最后信息汇报到集团财务归档。

所以在系统中,将会引入辅助组织架构(虚拟组织架构),即:财务线(财务类型组织)的财务组织架构、人事线(人力资源类型组织)的人力资源组织架构等。一 个辅助组织必须与一个主组织(行政组织架构)关联,一个主组织可关联多个辅助组织,但同一类型辅助组织,只允许关联一个。

部分表组织如下

完成组织架构模块改造,下一步就是改造工作流引擎。

工作流引擎相对来说变动会比较少,主要的变化点在人员路由模块。可查看此文章,1.2.3、设置处理者。

在此模块中,加入可按组织机构类型寻找用户。如组织类型设置为财务组织,处理者设置为报销会计,那么当流程行进到此节点,就会寻找提交人所处组织设置的财务组织,如果本级组织中没有进行设置,那么将到上级组织中寻找,直接找到为止。

这样,在不同的组织设置不同的财务组织,在用户进行费用报销时,系统就会自动找到财务中心。

优点:系统配置灵活,可随时更换不同财务中心。统一流程,便于管理,以往相同流程在系统中可能需要设置多个,除费用报销外,另一个常见的就是请假流程,需要不同的人力资源部进行备案归档,按此方案,只需要一个流程,不需要写任务代码就可以完成。

缺点:组织架构初始化工作量。

节点处理者部分结构

以上是我在多组织架构下费用报销流程大概的做法,目前流程运行比较顺畅。

[转载]ASP.NET MVC实用技术:自定义AuthorizeAttribute与ActionLink的隐藏

mikel阅读(979)

[转载]ASP.NET MVC实用技术:自定义AuthorizeAttribute与ActionLink的隐藏 – dax.net – 博客园.

在有些情况下,我们希望界面上的Action Link不仅仅是限制未授权用户的进一步访问,而是对于这些用户直接隐藏。比如,以普通用户登录时,只能在页面上看到一些常规的链接,而以管理员身份登录 时,除了能看到这些常规链接外,还能够看到网站管理的链接。本文将介绍如何使用自定义的AuthorizeAttribute来实现这样的功能。

为了方便介绍,在这里不打算使用那些复杂的权限管理子系统或者权限验证机制,我们就做一个非常简单的假设:如果输入的用户名是“daxnet”,则 表示这个账户是一个管理员账户,否则,它就是一个普通用户账户。在实际应用过程中,读者朋友可以采用自己的一套权限验证逻辑来判断某个账户是否为管理员。

WCF Service

在《ASP.NET MVC实用技术:开篇》 一文,我已经介绍过了三种不同的ASP.NET MVC应用模式,其中第二种“Data Transfer Object as View Model”是在企业级应用中最为常见的一种方式,本文(及以后的系列文章)都会使用这种应用模式进行介绍。所以,在这里我们也还是需要建立一个WCF Service,用来向客户端返回登录用户权限认证的结果。

新建一个WCF Service,在其中定义一个enum类型,用来表示登录用户的账户类型。在这里我们只讨论两种类型:RegularUser,表示普通用户账户;SiteAdmin,表示网站管理员账户。这个enum类型定义如下:

view plaincopy to clipboardprint?

///

<summary> /// Represents the type of the account.
/// </summary>

&nbsp;

[Flags]
public enum AccountType
{
///

<summary> /// Indicates that the account is a regular user.
/// </summary>

&nbsp;

[EnumMember]
RegularUser = 1,
///

<summary> /// Indicates that the account is the site administrator.
/// </summary>

&nbsp;

[EnumMember]
SiteAdmin = 2
}

使用FlagsAttribute来标记这个AccountType枚举,是为了今后能够更方便地处理更多类型的账户,事实上在我们这个案例中,并没有太大的实际意义。

然后,新建一个Service Contract,为了简化案例,这个Service Contract只包含一个操作,就是根据传入的用户账户名称,返回AccountType。

[ServiceContract(Namespace="http://aspnetmvcpractice.com")]
public interface IAccountService
{
[OperationContract]
AccountType GetAccountType(string userName);
}

之后在WCF Service中实现这个接口,根据我们上面的约定,当用户名为“daxnet”的时候,就返回SiteAdmin,否则就返回RegularUser,因此这个实现类还是非常简单的:

public class AccountService : IAccountService
{
#region IAccountService Members
public AccountType GetAccountType(string userName)
{
if (userName == "daxnet")
return AccountType.SiteAdmin;
else
return AccountType.RegularUser;
}
#endregion
}

至此,我们完成了WCF Service部分的开发,接下来,需要在ASP.NET MVC中使用这个WCF Service来完成用户的验证操作。在通常情况下,我们会在ASP.NET MVC的应用程序上直接添加WCF Service的引用,这样做其实也没有什么太大的问题,不过我还是比较习惯另外新建一个Class Library,然后将WCF Service Reference添加到这个Class Library上,这样做的好处是,可以把所有与ASP.NET MVC扩展相关的内容都集中起来,而且这种扩展相关的类型和方法都有可能需要用到WCF Service提供的服务,这样也不至于将ASP.NET MVC应用程序的结构弄得很乱。在这个案例中,我们新建一个名为WebExtensions的Class Library,在这个Library中使用刚刚创建好的WCF Service来实现我们的自定义授权特性。
Web Extensions
CustomAuthorizeAttribute

在新建的这个Class Library中直接添加WCF Service Reference,这将在这个Library中产生一系列的代理类型,以及一个app.config文件。不要去关注这个app.config文件,因为它在这个Class Library中并不起什么作用;但是也不要去删除这个文件,因为后面我们还是需要用到它里面的内容的。

在Class Library中,新建一个CustomAuthorizeAttribute类,使这个类继承于AuthorizeAttribute。我们会在后面将这个Attribute用在action上,以限制未授权用户对页面的访问。在这个类中,重载AuthorizeCore方法,它的处理逻辑如下:首先判断当前账户是否被认证,如果没有,则返回false;然后调用WCF Service来获取当前账户的类型,并跟给定的类型进行比较,如果类型相同,则返回true,否则返回false。假设这个给定的账户类型是通过CustomAuthorizeAttribute类的构造函数传入的,那么,当我们在某个action上应用[CustomAuthorizeAttribute(AccountType.SiteAdmin)]这个特性的时候,只要访问这个action的用户账户不是SiteAdmin,程序就会自动跳转到登录页面,请求用户以网站管理员的身份登录。CustomAuthorizeAttribute类的代码如下:

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly AccountType requiredType;

public CustomAuthorizeAttribute(AccountType comparedWithType)
{
this.requiredType = comparedWithType;
}

internal bool PerformAuthorizeCore(System.Web.HttpContextBase httpContext) { return this.AuthorizeCore(httpContext); }

protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");

if (!httpContext.User.Identity.IsAuthenticated)
return false;

if (this.requiredType == (AccountType.SiteAdmin | AccountType.RegularUser))
return true;

using (AccountServiceClient client = new AccountServiceClient())
{
var calculatedAccountType = client.GetAccountType(httpContext.User.Identity.Name);

switch(this.requiredType)
{
case AccountType.RegularUser:
if ((calculatedAccountType &amp; AccountType.RegularUser) == AccountType.RegularUser)
return true;
else
return false;
case AccountType.SiteAdmin:
if ((calculatedAccountType &amp; AccountType.SiteAdmin) == AccountType.SiteAdmin)
return true;
else
return false;
default:
return base.AuthorizeCore(httpContext);
}
}
}
}

在这个类中有一个internal的方法:PerformAuthorizeCore,它的作用就是向程序集的其它方法暴露AuthorizeCore的执行逻辑,以避免相同的逻辑需要在程序集内部的其它类型中重复实现。这个PerformAuthorizeCore的方法会在自定义的HtmlHelper扩展方法中使用,目的就是为了能够对未授权的账户隐藏Action Link。
HtmlHelper Extension

现在我们来扩展HtmlHelper类,使得其中的ActionLink方法能够支持对未授权账户的隐藏。同样也是在当前这个Class Library中,新建一个静态类,命名为MvcExtensions,然后使用下面的代码实现这个类:

public static class MvcExtensions
{
private static bool Visible(HtmlHelper helper, AccountType accountType)
{
return new CustomAuthorizeAttribute(accountType).PerformAuthorizeCore(helper.ViewContext.HttpContext);
}
///

<summary> /// Returns an anchor element (a element) that contains the virtual path of the specified action.
/// </summary>

&nbsp;

///The HTML helper instance that this method extends. ///The inner text of the anchor element. ///The name of the action. ///The name of the controller. ///The required account type. /// The anchor element (a element) that contains the virtual path of the specified action.
public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName,
AccountType accountTypeRequired)
{
MvcHtmlString link = MvcHtmlString.Empty;
if (Visible(htmlHelper, accountTypeRequired))
link = htmlHelper.ActionLink(linkText, actionName, controllerName);
return link;
}
}

这个ActionLink方法首先将link设置为MvcHtmlString.Empty,表示为一个空的string,然后调用私用静态方法 Visible,来判断当前用户是否应该看到这个ActionLink,如果Visible返回的是true,则直接调用HtmlHelper中已有的 ActionLink重载方法,否则直接返回MvcHtmlString.Empty。在Visible方法中,我们可以看到,所执行的逻辑正是 CustomAuthorizeAttribute中的AuthorizeCore方法。

接下来要做的,就是在ASP.NET MVC应用程序中使用这些扩展方法和自定义特性。

ASP.NET MVC应用程序

在ASP.NET MVC应用程序上添加对上述Class Library的引用,然后我们打开Views\Shared\_Layout.cshtml文件,在这个Razor View中添加对所需命名空间的引用:

image

然后,根据需要,我们向主菜单中添加两个ActionLink:Regular Users Only和Site Admins Only,前者仅允许普通用户访问,后者仅允许站点管理员访问。在此所使用的ActionLink就是在上文中我们自定义的那个重载:

image

 

接下来在HomeController中定义两个action:RegularUserVisible和SiteAdminVisible,并将 CustomAuthorizeAttribute应用在这两个action上。事实上这个步骤与隐藏Action Link并没有太大关系,只是确保用户无法通过在浏览器中输入URL而直接访问到这两个页面。

image

 

最后别忘了把Class Library下app.config中有关system.serviceModel的配置复制到ASP.NET MVC应用程序的web.config中。

运行程序

现在让我们来启动程序,看看会产生什么效果。首先启动WCF Service,然后直接运行ASP.NET MVC应用程序,得到如下界面:

image

 

现在点击“Log On”链接,以daxnet账户登录,我们得到了如下的效果,可以看到页面上显示了“Site Admins Only”的链接选项:

image

 

退出登录,再以“acqy”账户登录,我们又得到了如下效果,看到页面上显示了“Regular Users Only”的选项:

image

 

本文案例源代码下载

下载链接

单击此处下载本文案例源代码

有关数据库配置

本文使用的是SQL Server Enterprise Edition作为ASP.NET MVC的后台数据库,如果你打算选用SQL Server Express作为数据库,请修改本文案例中web.config里的连接字符串,并使用《ASP.NET MVC实用技术:开篇》 一文中所介绍的方法重建你的数据库结构。根据本文案例需要,你需要在ASP.NET MVC应用程序启动以后,新建两个用户账户:daxnet以及另一个任意名称的账户。当你正确地配置好了ASP.NET MVC的数据库以后,你可以在Solution Explorer中单击ASP.NET Configuration按钮来配置你的ASP.NET MVC站点,以添加所需的用户账户:

image

[转载]windows服务器安全之修改远程桌面连接端口

mikel阅读(836)

[转载]windows服务器安全之修改远程桌面连接端口 – xlw – 博客园.

一般Web服务器放在公网上以后,有些为了方便远程操作,所以打开了远程桌面连接。这样是很不安全的,黑客在扫描到服务器开启了3389端口以后很多都是利用这个端口发起攻击。为了降低风险而又想使用远程桌面连接,一个比较简单的方法就是修改远程桌面链接的3389端口。

远程桌面终端服务默认端口为”3389″,为防止他人进行恶意连接,就需要对默认端口进行更改。对此可打开注册表编辑器(运行regedit),依次展开”HKEY_LOCAL_MACHINE\SYSTEM\
CurrentControlSet\Control\Terminal Server\Wds\rdpwd\Tds\tcp”分支,其下的”PortNumber”键值所对应的就是端口号,将其修改即可。上面设置完成后,需要 再依次展开”HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control
\Terminal Server\WinStations\RDP-Tcp”分支,同样将其下的”PortNumber”键值进行更改。

比如我们可以将端口改为8080,或者改为443或者其他端口,这样可以误导黑客以为这是代理服务器的端口或HTTPS的端口。

那么修改了端口后我们怎么连接到远程桌面啦?其实很简单,在IP后面跟一个冒号再跟端口好就可以了。比如:192.168.2.131:443

http://www.renrenaj.com 人人安家网技术博客 老徐