[转载]ASP.NET MVC 3 开发的20个秘诀(六)[20 Recipes for Programming MVC 3]:找回忘记的密码

mikel阅读(1141)

[转载][翻译]ASP.NET MVC 3 开发的20个秘诀(六)[20 Recipes for Programming MVC 3]:找回忘记的密码 – O2DS – 博客园.

议题

您或者在您网站的注册的某个用户,无法想起自己的密码了,需要有一种方法能找回它。

解决方案

在AccountController控制器中添加一个新的动作和视图,以便用户可以找回他们的密码。利用Membership类搜索和匹配用户,并发送一个包含密码的电子邮件。

讨论

默认情况下,MVC Internet应用程序使用的是无法逆向转换的单向哈希算法。在下面的例子中,默认是双向散列加密方式。这种方式并不是很安全,但是在用户忘记密码时可以避免强迫用户修改密码。

先在Web.config文件中调整Membership节点的设置:

<?xml version="1.0"?>
<configuration>
    ...
    <system.web>
        ...
        <membership>
            <providers>
                <clear />
                <add name="AspNetSqlMembershipProvider" type=
"System.Web.Security.SqlMembershipProvider"
                connectionStringName="ApplicationServices"
                enablePasswordRetrieval="true" enablePasswordReset=
"false" requiresQuestionAndAnswer="false"
                requiresUniqueEmail="false" passwordFormat=
"Encrypted" maxInvalidPasswordAttempts="5"
                minRequiredPasswordLength="6"
                minRequiredNonalphanumericCharacters="0"
                passwordAttemptWindow="10" applicationName="/" />
            </providers>
        </membership>
        <machineKey
        validationKey=
"2CF9FF841A23366CFA5D655790D9308656B1F7532C0B95B5C067F80C45E59875
E2F3D68DAC63B5024C31D974D4BE151341FB8A31FC4BC3705DF5398B553FC3C3"
        decryptionKey="8E71407B62F47CCA3AAA6546B3880E1A0EF9833700
E0A0C511710F537E64B8B6" validation="SHA1" decryption="AES" />
        ...
    </system.web>
    ...
</configuration>

针对上面的设置代码,我们四个更改:

  1. 将enablePasswordRetrieval更改为True;
  2. 将enablePasswordReset更改为false;
  3. 添加passwordFormat=”Encrypted”;
  4. 生成加密的machineKey;

配置信息修改完毕后,在AccountModels.cs类中添加一个新的动作并创建忘记密码的视图:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Web.Mvc;
using System.Web.Security;

namespace MvcApplication4.Models
{
    public class ChangePasswordModel
    {
        ...
    }
    public class LogOnModel
    {
        ...
    }
    public class RegisterModel
    {
        ...
    }
    public class ForgotPasswordModel
    {
        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email address")]
        public string Email { get; set; }
    }
}

在创建新视图之前,应用程序必须先编译。单击“生成”->“生成解决方案”或按F6。编译完成之后,展开Views文件夹,右键单击 “Account”文件夹,选择“添加”->“视图”(如图1-4)。视图名称更改为“ForgotPassword”,选中“创建强类型视图”选 项,从“模型类”下拉框中选择先前创建的“ForgotPasswordModel”,然后点击“添加”。

视图创建后,添加一个基本的表单,表单接受用户输入注册时填写的电子邮件地址:

@model MvcApplication4.Models.ForgotPasswordModel
@{

    ViewBag.Title = "ForgotPassword";
}

<h2>ForgotPassword</h2>
<p>
    Use the form below to retrieve your password.
</p>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm()) { 

    @Html.ValidationSummary(true, "Password retrieval was unsuccessful. Please correct the errors and try again.")

    <div>
        <fieldset>
            <legend>Account Information</legend>
            <div class="editor-label">
                @Html.LabelFor(m => m.Email)
            </div>

            <div class="editor-field">
                @Html.TextBoxFor(m => m.Email)
                @Html.ValidationMessageFor(m => m.Email)
            </div>

            <p>
                <input type="submit" value="Retrieve Password" />
            </p>
        </fieldset>
    </div>
}

下面将在之前创建的MailClient类中添加一个新方法,以实现发送用户忘记的密码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Mail;
using System.Net;
using System.Configuration;

namespace MvcApplication4.Utils
{
    public class MailClient
    {
        private static readonly SmtpClient Client;
        static MailClient()
        {
            ...
        }

        private static bool SendMessage(string from, string to,
        string subject, string body)
        {
            ...
        }

        public static bool SendWelcome(string email)
        {
            ...
        }

        public static bool SendLostPassword(string email,
        string password)
        {
            string body = "Your password is: " + password;
            return SendMessage("no-reply@no-reply.com", email,
            "Lost Password", body);
        }
    }
}

这个方法与上一个方法(*译者注:发送欢迎邮件的方法)十分相似,只是又添加了一个参数 — 用户的密码。这个密码将被添加到电子邮件的内容中并发送给用户。

最后在AccountController中添加两个ForgotPassword方法,第一个方法是默认载入时动作,第二是在用户填写完电子邮件地址后,接受回发数据的动作,他会从数据库中通过搜索并匹配电子邮件地址找到用户,然后将用户密码通过这个地址发给用户。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using MvcApplication4.Models;
using MvcApplication4.Utils;

namespace MvcApplication4.Controllers
{
    public class AccountController : Controller
    {
        ...
        //
        // Get: /Account/ForgotPassword
        public ActionResult ForgotPassword()
        {
            return View();
        }

        //
        // Post: /Account/ForgotPassword
        [HttpPost]
        public ActionResult ForgotPassword(
        ForgotPasswordModel model)
        {
            if (ModelState.IsValid)
            {
                MembershipUserCollection users =
                Membership.FindUsersByEmail(model.Email);
                if (users.Count > 0)
                {
                    foreach (MembershipUser user in users)
                    {
                        MailClient.SendLostPassword(model.Email,
                        user.GetPassword());
                    }
                    return RedirectToAction("LogOn");
                }
            }
            // If we got this far, something failed,
            // redisplay form
            return View(model);
        }
        ...
    }

}

在之前的两个秘诀中,只是给用户发送了一些基本信息。将MailMessage的isBodyHtml设置为True,通过简单的扩展增强就可以发送HTML或者更复杂的内容给用户。

参考

Membership.Providers Property 原书地址 书籍源代码

[琐事]博客准备推出源码下载栏目

mikel阅读(1739)

最近准备推出源码分类,把自己这些年积累下来的源码共享出来供大家下载,

主要是网站源码居多,大部分是从网上收集来的,也有朋友送的

希望对各位有所帮助,就算“取之于民,用之于民”吧!

库存不多,希望大家海涵,如果你有好的源码也可以分享出来,发布到下载栏目

图书下载栏目也会陆续更新,我的藏书,主要以开发技术为主,涉及方面比较多,呵呵…毕竟我学得比较杂,什么Delphi、Java、.NET、C#、Python、JavaScriptAndroid、Flash、游戏开发

软件工程类的很多

敬请期待!

[转载]VisualStudio一些可能没用过的调试窗口

mikel阅读(1221)

[转载]一些可能没用过的调试窗口 – LoveJenny – 博客园.

首先说明:如果没有进入调试模式的话,默认的调试窗口如下:

image

开始前的准备:

新建控制台程序DebugWindowDemo:

修改Program.cs 的代码为:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

class S
    {
       public static void Main()
        {
            pcount = Environment.ProcessorCount;
            Console.WriteLine("Proc count = " + pcount);
            ThreadPool.SetMinThreads(4, -1);
            ThreadPool.SetMaxThreads(4, -1);

            t1 = new Task(A, 1);
            t2 = new Task(A, 2);
            t3 = new Task(A, 3);
            t4 = new Task(A, 4);
            Console.WriteLine("Starting t1 " + t1.Id.ToString());
            t1.Start();
            Console.WriteLine("Starting t2 " + t2.Id.ToString());
            t2.Start();
            Console.WriteLine("Starting t3 " + t3.Id.ToString());
            t3.Start();
            Console.WriteLine("Starting t4 " + t4.Id.ToString());
            t4.Start();

            Console.ReadLine();
        }

        static void A(object o)
        {
            B(o);
        }
        static void B(object o)
        {
            C(o);
        }
        static void C(object o)
        {
            int temp = (int)o;

            Interlocked.Increment(ref aa);
            while (aa < 4)
            {
                ;
            }

            if (temp == 1)
            {
                // BP1 - all tasks in C

                Debugger.Break();
                waitFor1 = false;
            }
            else
            {
                while (waitFor1)
                {
                    ;
                }
            }
            switch (temp)
            {
                case 1:
                    D(o);
                    break;
                case 2:
                    F(o);
                    break;
                case 3:
                case 4:
                    I(o);
                    break;
                default:
                    Debug.Assert(false, "fool");
                    break;
            }
        }
        static void D(object o)
        {
            E(o);
        }
        static void E(object o)
        {
            // break here at the same time as H and K

            while (bb < 2)
            {
                ;
            }
            //BP2 - 1 in E, 2 in H, 3 in J, 4 in K

            Debugger.Break();
            Interlocked.Increment(ref bb);

            //after

            L(o);
        }
        static void F(object o)
        {
            G(o);
        }
        static void G(object o)
        {
            H(o);
        }
        static void H(object o)
        {
            // break here at the same time as E and K

            Interlocked.Increment(ref bb);
            Monitor.Enter(mylock);
            while (bb < 3)
            {
                ;
            }
            Monitor.Exit(mylock);

            //after

            L(o);
        }
        static void I(object o)
        {
            J(o);
        }
        static void J(object o)
        {
            int temp2 = (int)o;

            switch (temp2)
            {
                case 3:
                    t4.Wait();
                    break;
                case 4:
                    K(o);
                    break;
                default:
                    Debug.Assert(false, "fool2");
                    break;
            }
        }
        static void K(object o)
        {
            // break here at the same time as E and H

            Interlocked.Increment(ref bb);
            Monitor.Enter(mylock);
            while (bb < 3)
            {
                ;
            }
            Monitor.Exit(mylock);

            //after

            L(o);
        }
        static void L(object oo)
        {
            int temp3 = (int)oo;

            switch (temp3)
            {
                case 1:
                    M(oo);
                    break;
                case 2:
                    N(oo);
                    break;
                case 4:
                    O(oo);
                    break;
                default:
                    Debug.Assert(false, "fool3");
                    break;
            }
        }
        static void M(object o)
        {
            // breaks here at the same time as N and Q

            Interlocked.Increment(ref cc);
            while (cc < 3)
            {
                ;
            }
            //BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q

            Debugger.Break();
            Interlocked.Increment(ref cc);
            while (true)
                Thread.Sleep(500); // for ever

        }
        static void N(object o)
        {
            // breaks here at the same time as M and Q

            Interlocked.Increment(ref cc);
            while (cc < 4)
            {
                ;
            }
            R(o);
        }
        static void O(object o)
        {
            Task t5 = Task.Factory.StartNew(P, TaskCreationOptions.AttachedToParent);
            t5.Wait();
            R(o);
        }
        static void P()
        {
            Console.WriteLine("t5 runs " + Task.CurrentId.ToString());
            Q();
        }
        static void Q()
        {
            // breaks here at the same time as N and M

            Interlocked.Increment(ref cc);
            while (cc < 4)
            {
                ;
            }
            // task 5 dies here freeing task 4 (its parent)

            Console.WriteLine("t5 dies " + Task.CurrentId.ToString());
            waitFor5 = false;
        }
        static void R(object o)
        {
            if ((int)o == 2)
            {
                //wait for task5 to die

                while (waitFor5) { ;}

                int i;
                //spin up all procs

                for (i = 0; i < pcount - 4; i++)
                {
                    Task t = Task.Factory.StartNew(() => { while (true);});
                    Console.WriteLine("Started task " + t.Id.ToString());
                }

                Task.Factory.StartNew(T, i + 1 + 5, TaskCreationOptions.AttachedToParent); //scheduled

                Task.Factory.StartNew(T, i + 2 + 5, TaskCreationOptions.AttachedToParent); //scheduled

                Task.Factory.StartNew(T, i + 3 + 5, TaskCreationOptions.AttachedToParent); //scheduled

                Task.Factory.StartNew(T, i + 4 + 5, TaskCreationOptions.AttachedToParent); //scheduled

                Task.Factory.StartNew(T, (i + 5 + 5).ToString(), TaskCreationOptions.AttachedToParent); //scheduled

                //BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died

                Debugger.Break();
            }
            else
            {
                Debug.Assert((int)o == 4);
                t3.Wait();
            }
        }
        static void T(object o)
        {
            Console.WriteLine("Scheduled run " + Task.CurrentId.ToString());
        }
        static Task t1, t2, t3, t4;
        static int aa = 0;
        static int bb = 0;
        static int cc = 0;
        static bool waitFor1 = true;
        static bool waitFor5 = true;
        static int pcount;
        static S mylock = new S();
    }

F5,开始运行:

由于Debugger.Break();

所以当执行到这里的时候,Debugger会中断。

这个时候再看看调试窗口会发现多了几个窗口:

image

点击调试->窗口->并行任务,界面如下:

image

按下F5,继续运行:

image

双击查看相应等待的任务,就可以知道这个工作线程为什么等待了。

例如:

image

继续按F5,运行,你应该会看到:

image

总之关于当前运行的并行任务你都可以看的到。

关闭程序,重新F5,进入调试模式

选择调试->窗口->并行堆栈

可以看到:

image

其中蓝线代表当前正在执行的线程。

如果右键点击方法视图的S.C ,可以看到右键菜单如下:image

点击切换方法视图

image

可以看到:

image

关闭程序,重新F5,进入调试模式

点击调试->窗口->线程:

可以看到:

image

当前控制台的所有线程都在这里。

在线程上点击右键可以冻结线程:

image

冻结线程也就是Pause线程,

冻结的线程可以被解冻,也就是Resume。

其他的窗口:

调试->窗口->模块:可以看到当前程序加载的所有模块。

image

调试->窗口->进程:

image

调试->窗口->反汇编:

image

调试->窗口->寄存器:

image

调试->窗口->调用堆栈:

调用堆栈窗口是比较常用的窗口:

image

上图表示先调用A方法,接着B方法,接着C方法。

也可以认为是C方法是B调用,而B方法是A调用的。

其他窗口比较常用,就不介绍了,是不是有一些你没用到的窗口呢?

[转载]博客园人必备工具:消除行号软件

mikel阅读(865)

[转载]博客园人必备工具:消除行号软件 – ma6174 – 博客园.

博客园里都是牛人,写的代码都是神笔,共享一下倒是好事,满屏行号情何以堪!

问题来源很简单,大牛们在博客园里面贴代码时,里面有一个“显示行号”的选项,大牛们轻轻一点,然后行号就显示出来了,代码好看了,我们可愁了,本来想研究一下代码,然后复制到编辑器,就发现每行都有行号。不信大家可以试一下,这里有一个带行号的代码http://www.cnblogs.com/ma6174/archive/2011/12/07/2279614.html。不把行号删除,代码怎么运行?然后就一行一行得删行号,代码短了还好说,可博客园里都是编程神牛啊,代码一写就是几千上万行,考验我们的耐力吗?哈,开个玩笑,为了解决这个问题,小弟花几分钟写了一个小软件,瞬间帮您把行号消除。

源码如下:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void welcome()
{
printf(\t\t\t博客园人必备工具——消除行号\n);
printf(\t\t\t\t  BY ma6174\n);
printf(\t\t\t       ma6174@163.com);
printf(\n使用方法:\n\n);
printf(先将你要消除行号的完整代码复制到这里,\n\n按一下回车键,);
printf(然后按“Ctrl + Z”组合键,再按一下回车,烦人的行号就全没啦\n);
for(int i = 0; i < 80; i++)
printf(=);
printf(\n\n);
}
int main()
{
welcome();
char s[1000];
int i,len,tag;
FILE *fp;
fp=fopen(temp.txt,w);
if(fp==NULL){
printf(文件打开失败!);
exit(1);
}
while(gets(s))
{
len=strlen(s);
i=0;
while(s[i]== )i++;
while(s[i]>=0&&s[i]<=9)i++;
while(i<len-1)
fprintf(fp,%c,s[++i]);
fprintf(fp,\n);
}
fclose(fp);
system(start temp.txt);
}

如果大家懒得自己编译么可以到我的网盘下载可直接运行的软件:http://ma6174.ys168.com/

[转载]ASP.NET MVC 3 开发的20个秘诀(五)[20 Recipes for Programming MVC 3]:发送欢迎邮件

mikel阅读(996)

[转载][翻译]ASP.NET MVC 3 开发的20个秘诀(五)[20 Recipes for Programming MVC 3]:发送欢迎邮件 – O2DS – 博客园.

议题

许多网站在访问内容或者发表评论的时候 要求用户注册或登录。这样的网站越来越多,用户非常难以记住每个他注册过的站点以及信息。而在用户提交注册信息的时候,网站可以发送邮件,提醒用户他们刚刚签署过注册信息,方便他们稍后访问时查询。

解决方案

实现SmtpClient类和MailMessage类在用户注册后发送欢迎邮件。

讨论

发送一份电子邮件,你需要配置SMTP服务器地址、端口、用户名和密码。为了让配置更加简单,建议将这些信息存储到Web.config的AppSettings节点里。

    <appSettings>
 
        <add key="webpages:Version" value="1.0.0.0" />
        <add key="ClientValidationEnabled" value="true" />
        <add key="UnobtrusiveJavaScriptEnabled" value="true" />
        <add key="smtpServer" value="localhost" />
        <add key="smtpPort" value="25" />
        <add key="smtpUser" value="" />
        <add key="smtpPass" value="" />
        <add key="adminEmail" value="no-reply@no-reply.com" />

    </appSettings>

在smtpServer、smtpPort、smtpUser和smtpPass的Value中输入可用的参数。

为了更好的组织项目,我们将创建一个新的文件夹来包含这些发送邮件的类。右键单击项目选择“添加”->“新建文件夹”并将其命名为 “Utils”。现在,右键单击新创建的“Utils”文件夹,选择“添加”->”类”并命名为“MailClient.cs”。

为了方便访问,MailClient类被定义为静态类,未来在与其他系统集成使用时,不需要实例化新的对象,直接使用即可。下面是MailClient类的完整代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Mail;
using System.Net;
using System.Configuration;

namespace MvcApplication4.Utils
{
    public static class MailClient
    {
        private static readonly SmtpClient Client;

        static MailClient()
        {
            Client = new SmtpClient
            {
                Host =
                ConfigurationManager.AppSettings["SmtpServer"],
                Port =
                Convert.ToInt32(
                ConfigurationManager.AppSettings["SmtpPort"]),
                DeliveryMethod = SmtpDeliveryMethod.Network
            };

            Client.UseDefaultCredentials = false;
            Client.Credentials = new NetworkCredential(
            ConfigurationManager.AppSettings["SmtpUser"],
            ConfigurationManager.AppSettings["SmtpPass"]);
        }

        private static bool SendMessage(string from, string to,
        string subject, string body)
        {
            MailMessage mm = null;
            bool isSent = false;

            try
            {
                // Create our message
                mm = new MailMessage(from, to, subject, body);
                mm.DeliveryNotificationOptions =
                DeliveryNotificationOptions.OnFailure;
                // Send it
                Client.Send(mm);
                isSent = true;
            }
            // Catch any errors, these should be logged and
            // dealt with later
            catch (Exception ex)
            {
                // If you wish to log email errors,
                // add it here...
                var exMsg = ex.Message;
            }
            finally
            {
                mm.Dispose();
            }

            return isSent;
        }

        public static bool SendWelcome(string email)
        {
            string body = "Put welcome email content here...";

            return SendMessage(
            ConfigurationManager.AppSettings["adminEmail"],
            email, "Welcome message", body);
        }
    }
}

在类开始的时候,从Web.Config中引用值并创建SmtpClient类实例。下面定义的SendMessage 方法是私有方法,不能从类外部直接调用,此方法的是实际执行发送功能的方法。这个方法创建一个新的MailMessage实例,并通过先前创建 SmtpClient对象发送。最后,创建了一个SendWelcome功能来接受用户的电子邮件地址参数,通过调用SendMessage方法来发送欢 迎邮件。

在Account控制器中,用户通过Register方法注册或更新之后立即调用SendWelcome方法发送电子邮件:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using MvcApplication4.Models;
using MvcApplication4.Utils;

namespace MvcApplication4.Controllers
{
    public class AccountController : Controller
    {
            …
        //
        // POST: /Account/Register
        [HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus;
                Membership.CreateUser(model.UserName,
                model.Password, model.Email, null, null,
                true, null, out createStatus);
                if (createStatus ==
                MembershipCreateStatus.Success)
                {
                    // Send welcome email
                    MailClient.SendWelcome(model.Email);
                    FormsAuthentication.SetAuthCookie(
                    model.UserName,
                    false /* createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError("",
                    ErrorCodeToString(createStatus));
                }
            }
            // If we got this far, something failed,
            // redisplay form
            return View(model);
        }

    }
}

上面的代码是一个基本的例子,实现了用户在注册过程中向用户发送一封欢迎邮件。在现代社会中,自动处理表单是非常棒的点子,在以后的使用,我们可以在邮件中加入“确认您的电子邮件地址”的链接消息,并要求用户必须通过点击欢迎电子邮件中的链接激活账户之后,才可以登录。

参考

StmpClient MailMessage 原书地址 书籍源代码

[转载]ASP.NET MVC 3实现类似于Server.Transfer的效果

mikel阅读(973)

[转载]ASP.NET MVC 3实现类似于Server.Transfer的效果 – L.Qiu – 博客园.

ASP.NET MVC的页面跳转

当我们在使用ASP.NET MVC实现页面跳转的时候,常用的应该是:

  • Redirect
  • RedirectToAction
  • RedirectToRoute
  • 或者在前台使用脚本跳转。

但这几种跳转方式都是基于Get请求的,在某些特定场景下可能并不适用。例如需要传递大数据量参数、或者复杂对象类型参数的场景,get方式肯定是有限制的。

在webform里面,有一种服务器端跳转方式:Server.Transfer,相信大家一定都还记得。这种方式是中止当前页面执行,并将执行流程转入一个新的页面,并使用上一个页面创建的应答流。 这种方式具有如下的特点:
1,地址栏URL不会发生变化。
2,上一个页面后台产生的参数和对象可以直接传递到新的页面。
3,减少客户端对服务器的请求。

我们知道,ASP.NET MVC有一个核心思想,就是“约定胜于配置” ,例如在执行完一个action后,会到view目录下根据controller名称查找对应的view来进行渲染,但是 约定的做法并不意味着不能改变。

对于ASP.NET MVC而言,可以通过动态改变当前Action所渲染的view路径,来实现类似的效果。

渲染非常规路径的View

第一步,先实现一个自定义的ViewEngine:
public class ChangeViewEngine : System.Web.Mvc.RazorViewEngine
    {
        public ChangeViewEngine(string controllerPathName,string viewName)
        {
            this.ViewLocationFormats = new[] {"~/Views/" + controllerPathName + "/" + viewName  + ".cshtml" };
            
        }
    }
第二步,实现一个ActionAttribute
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public class ChangeViewPathAttribute : ActionFilterAttribute
    {
        private string _controllerPath;
        private string _viewName;
        public ChangeViewPathAttribute(string controllerPath,string viewName)
        {
            this._controllerPath = controllerPath;
            this._viewName = viewName;
        }
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            //base.OnResultExecuting(filterContext);
            //ViewEngines.Engines.Clear();
            
            ViewEngines.Engines.Add(new ChangeViewEngine(_controllerPath,_viewName));
        }
    }

在该段代码里面,ChangeViewPathAttribute类继承于ActionFilter,并重写其中的OnResultExecuting方法,将自定义的ViewEngine加入到全局ViewEngine集合里面来。

第三步,在需要渲染不同路径的action加上Attribute
     [HttpPost]
        [Filter.ChangeViewPath("Invoice","Create")]
        public ActionResult PreInvoice(string strIds,bool flag)

在做完以上步骤后,我们就可以随意指定action所要渲染的view,在服务器端进行跳转,实现类似Server.Transfer的效果。 当然,以上只是一个简单的示例,你完全可以做的更优雅一点,实现更灵活的路径配置:)

[转载]Jquery easyui 界面设置存档edit.js

mikel阅读(1126)

[转载]Jquery easyui 界面设置存档edit.js_小豆号_百度空间.

$(function() {
    // 数值录入
    $("#salary").numberbox({
        min: 100,
        max: 1000.5,
        precision: 2
    });


    // 日期处理
    // FIXME: 日历控件不支持时分秒, 这在项目中的确是需要的
    // FIXME: 日历控件每周是从星期日开始, 而我们习惯是星期一, 最好能够设置
    $("#birthday").datebox({
        required: true,
        formatter: dateFormatter,
        parser: dateParser
    });


    // 下拉控件 combobox
    // FIXME: 不支持汉字的拼音检索, 最好提供回调接口, 我们来实现
    // 为了支持多音字, 我们会返回数组, 比如: 银行["YH","YX"]
    // FIXME: 空项目显示高度太窄, 需要添加"[请选择]"这样的占位项目(感觉不太好)
    // FIXME: 下拉弹出内容被屏幕遮挡时, 应该改为向上弹出
    // FIXME: 支持多选(每个项目加一个checkbox选项用于多选,用于提交的input里存的是id列表:01,02,03)
    $("#gender").combobox({
        url: '../data/gender.json',
        valueField: 'id',
        textField: 'text',
        width: 155,
        listWidth: 200,
        editable: true,
        checkbox: true // FIXME: 不支持checkbox多选?
    });

    // FIXME: 下拉弹出内容被屏幕遮挡时, 应该改为向上弹出
    // FIXME: 支持多选(每个项目加一个checkbox选项用于多选,用于提交的input里存的是id列表:01,02,03)
    $("#station").combotree({
        url: 'treeDate.aspx?flag=left',
        width: 155,
        checkbox: true, // FIXME: 不支持checkbox多选?
        treeWidth: 200
    });

    $('#save').click(function() {
        alert($('#parentid').text());
        $('#form').submit();
    });

    //提交弹出添加职位框
    $('#form').form({
        url: "Default.aspx?flag=adduser&pid=" + $('#parentid').text(),
        onSubmit: function() {
            return $(this).form('validate');
        },
        success: function(data) {
            alert(data);
            if (data == "yes") {
                $('#w').window('close');
                $('#jobText').val() = "";
                //                $('#w').tree('append', {
                //                    parent: pid,
                //                    data: nodes
                //                });
                $.messager.show({
                    title: "提示",
                    msg: "保存成功",
                    timeout: 3000
                })
            }

        }
    });


    // 自定义录入验证
    $.extend($.fn.validatebox.defaults.rules, {
        minLength: {
            validator: function(value, param) {
                return value.length >= param[0];
            },
            message: '至少输入 {0} 个字符.'
        }
    });


    // 只读测试
    var readonly = false;
    $('#readonly').click(function() {
        // 有这样的方法就好了
        //$('#form').readonly();

        if (readonly == false) {
            $('form input,form textarea').attr({ readonly: "false" });

            // FIXME: 应该支持readonly, disabled对用户不够友好: 不能提交到后台, 不能拷贝值出来
            $("form .easyui-validatebox").validatebox("disable"); // FIXME: 无此方法? 只读状态也提示非空等,不太好
            $("form .easyui-numberbox").numberbox("disable");
            $("form .easyui-datebox").datebox("disable");
            $("form .easyui-combobox").combobox("disable");
            $("form .easyui-combotree").combotree("disable");

        } else {
            $('form input,form textarea').removeAttr("readonly");

            $("form .easyui-validatebox").validatebox("enable"); // FIXME: 无此方法?
            $("form .easyui-numberbox").numberbox("enable");
            $("form .easyui-datebox").datebox("enable");
            $("form .easyui-combobox").combobox("enable");
            $("form .easyui-combotree").combotree("enable");
        }
        readonly = !readonly;
    });

    // TIP: 配合body解决页面跳动和闪烁问题
    $("body").css({ visibility: "visible" });
});

[转载]组织好你的Asp.Net MVC解决方案

mikel阅读(1114)

[转载]【译】组织好你的Asp.Net MVC解决方案 – CareySon – 博客园.

最近,Twitter上发起了一个一个关于“你最爱的ASP.NET MVC项目组织方式”,我自己研究了一些组织项目文件的方法。而我现在一直喜欢用的方式是一个几句灵活性的方式,此外,这个方式还非常简单。

如上图,整个解决方案里只有两个项目,首先让我们来看UI项目,UI项目只包含了那些和Website相关的文件并且不包含任何后台代码,这包括:

  • 没有Controller
  • 没有Model
  • 没有Global.asa.cs
  • 不包含任何后台代码

为什么不包含后台代码?因为UI项目只包含那些和UI相关的文件,而我的UI项目和部署项目相匹配,这包括

  • Views
  • Css
  • Images
  • Global.asax
  • Web.Config
  • 引用Core项目

因为我的UI项目和部署结构相匹配,所以弄清楚开发是怎样工作的将会变得容易.反之,将Model和Controller混合起来将会使得开发中将各部分分开变得困难。所以我的组织方法会将代码和内容这两部分分开。

我们的代码部分怎么办呢?放到另一个项目中,我喜欢称之为”Core”,当然名字你可以随便起,所有的代码文件都会放到这个项目中,包括持久层 Model,View Model,Controller,repositories,ORM相关文件以及任何和后台代码相关的部分。

组织好你的代码

对于组织好的代码,我喜欢用简单的方式,如果可以的话,我宁愿不编译UI项目–这仅仅是一个用于存放内容的文件夹,而这个文件夹下的bin目录里只有来自于”Core”项目的dll。

另外,我喜欢用文件夹来组织代码。利用项目的方式组织代码是可以的,但是这种缺乏灵活性的做法会将你禁锢在很难改变的层和结构当中。我已经在大型项目中因 为错误的组织代码而犯过许多错误,而最后我发现最好的做法并不是大兴土木,因为我曾经呆过的一个Team里动辄就是上百个项目,但是只有不到一半的项目被 实际部署.所以要牢记,编译所需的时间很大程度上取决于你的解决方案中项目的多寡。在一个含有1000个文件的项目和100个文件放入10个项目中比较, 前者所需的编译时间要大大的小于后者。至少在我见过的项目中是这样。

而另一个实际的问题是当你需要重新组织你现有的代码时,你会发现对于包含N个项目的解决方案来说,发现你被禁锢在各个不同项目的结构中会让你捶胸顿足。此 外,这种方式在Ctrl+F5进行调试时,超慢的速度更会让你欲哭无泪。而这种方式在你的代码被源码管理软件管理时,更会显出不足,仅仅将一个文件从一个 项目中移到另一个项目中就会让你的历史记录暴涨。在我最近的一个项目中,情况已经发展到甚至我们连一个简单的源码控制指令都无法执行从而导致我们所有的源 码控制历史记录全部丢失,最后我们不得不重新手动创建整个项目。移动文件夹要比在项目中移动文件让人惬意很多…

如果你想将你的代码进行很好的组织和分层,使用项目的方式或许有些帮助,因为这强制让你遵循你自己一开始设定的规则,但是一旦你开始这样做,你设立 了”Common”项目,”Configuration”项目”,”mapping”项目等,在后面的开发中最好考虑将这些代码回归到同一个项目当中.

所以,为什么不考虑将所有代码放入一个Core项目,所有UI放入一个项目,从而能给你极大的灵活性和最快的编译速度呢?无论你准备何种构架来组织你的代码,请确保不要将后台代码放入UI项目中,这样才能避免将代码和内容的关注点分离结构混为一谈。

—————————————————–

原文链接:http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/12/08/organizing-asp-net-mvc-solutions.aspx

[转载]T-SQL查询进阶--深入浅出视图

mikel阅读(832)

[转载]T-SQL查询进阶–深入浅出视图 – CareySon – 博客园.

简介

视图可以看作定义在SQL Server上的虚拟表.视图正如其名字的含义一样,是另一种查看数据的入口.常规视图本身并不存储实际的数据,而仅仅存储一个Select语句和所涉及表的metadata.

视图简单的理解如下:

5

通过视图,客户端不再需要知道底层table的表结构及其之间的关系。视图提供了一个统一访问数据的接口。

为什么要使用视图(View)

从而我们不难发现,使用视图将会得到如下好处:

  • 视图隐藏了底层的表结构,简化了数据访问操作
  • 因为隐藏了底层的表结构,所以大大加强了安全性,用户只能看到视图提供的数据
  • 使用视图,方便了权限管理,让用户对视图有权限而不是对底层表有权限进一步加强了安全性
  • 视图提供了一个用户访问的接口,当底层表改变后,改变视图的语句来进行适应,使已经建立在这个视图上客户端程序不受影响

视图(View)的分类

视图在SQL中可以分为三类

  1. 普通视图(Regular View)
  2. 索引视图(Indexed View)
  3. 分割视图(Partitioned View)

下面从这几种视图类型来谈视图

普通视图(Rugular View)

普通视图由一个Select语句所定义,视图仅仅包含其定义和被引用表的metadata.并不实际存储数据。MSDN中创建视图的模版如下:

CREATE VIEW [ schema_name . ] view_name [ (column [ ,...n ] ) ] 
[ WITH <view_attribute> [ ,...n ] ] 
AS select_statement 
[ WITH CHECK OPTION ] [ ; ]

<view_attribute> ::= 
{
    [ ENCRYPTION ]
    [ SCHEMABINDING ]
    [ VIEW_METADATA ]     }

参数还是比较少的,现在解释一下上面的参数:

ENCRYPTION:视图是加密的,如果选上这个选项,则无法修改.创建视图的时候需要将脚本保存,否则再也不能修改了

SCHEMABINDING:和底层引用到的表进行定义绑定。这个选项选上的话,则视图所引用到的表不能随便更改构架(比如列的数据类型),如果需要更改底层表构架,则先drop或者alter在底层表之上绑定的视图.

VIEW_METADATA:这个是个很有意思的选项.正如这个选项的名称所指示,如果不选择,返回给客户端的metadata是View所引用表的 metadata,如果选择了这个选项,则返回View的metadata.再通俗点解释,VIEW_METADATA可以让视图看起来貌似表一样。 View的每一个列的定义等直接告诉客户端,而不是所引用底层表列的定义。

WITH Check Option:这个选项用于更新数据做限制,下面会在通过视图更新数据一节解释.

当然了,创建视图除了需要符合上面的语法规则之外,还有一些规则需要遵守:

  • 在View中,除非有TOP关键字,否则不能用Order By子句(如果你一意孤行要用Order by,这里有个hack是使用Top 100 percent…..)
  • View在每个Schema中命名必须独一无二
  • View嵌套不能超过32层(其实实际工作中谁嵌套超过两层就要被打PP了-.-)
  • Compute,compute by,INTO关键字不允许出现在View中
  • View不能建立在临时表上
  • View不能对全文索引进行查询

建立View一个简单的例子:

CREATE VIEW v_Test_View1
AS
SELECT TOP 100 * FROM HumanResources.Employee

视图建立完成后,就可以像访问表一样访问视图了:

SELECT * FROM v_Test_View1

在Management studio中,我创建视图的时候更喜欢用这样一种方法,将会便捷很多:

6

7

索引视图(Indexed View)

在谈到索引视图之前,我突然想起以前看过的一个漫画.话说咱们高端产品买不起,但是省吃俭用攒点钱买个IPhone装装高端总还是可以的吧:

1

其实索引视图也很类似,在普通的视图的基础上,为视图建立唯一聚集索引,这时这个视图就变成了索引视图.套用上面漫画的公式:视图+聚集索引=索引视图

索引视图可以看作是一个和表(Table)等效的对象!

SQL Server中的索引视图和Oracle中的Materialized View是一个概念.想要理解索引视图,必须先理解聚集索引。聚集索引简单来说理解成主键,数据库中中的数据按照主键的顺序物理存储在表中,就像新华字 典,默认是按照ABCD….这样的方式进行内容设置。ABCD….就相当于主键.这样就避免了整表扫描从而提高了性能.因此一个表中只能有一个聚集索引。

对于索引视图也是,为一个视图加上了聚集索引后。视图就不仅仅再是select语句和表的metadata了,索引视图会将数据物理存在数据库中,索引视图所存的数据和索引视图中所涉及的底层表保持同步。

理解了索引视图的原理之后,我们可以看出,索引视图对于LDAP这种大量数据分析和查询来说,性能将会得到大幅提升。尤其是索引视图中有聚合函数,涉及大 量高成本的JOIN,因为聚合函数计算的结果物理存入索引视图,所以当面对大量数据使用到了索引视图之后,并不必要每次都进行聚合运算,这无疑会大大提升 性能.

而同时,每次索引视图所涉及的表进行Update,Insert,Delete操作之后,SQL Server都需要标识出改变的行,让索引视图进行数据同步.所以LDTP这类增删改很多的业务,数据库需要做大量的同步操作,这会降低性能。

谈完了索引视图的基本原理和好处与坏处之后,来看看在SQL Server中的实现:

在SQL Server中实现索引视图是一件非常,简单的事,只需要在现有的视图上加上唯一聚集索引.但SQL Server对于索引视图的限制却使很多DBA对其并不青睐:

比如:

  • 索引视图涉及的基本表必须ANSI_NULLS设置为ON
  • 索引视图必须设置ANSI_NULLS和QUOTED_INDETIFIER为ON
  • 索引视图只能引用基本表
  • SCHEMABINDING必须设置
  • 定义索引视图时必须使用Schema.ViewName这样的全名
  • 索引视图中不能有子查询
  • avg,max,min,stdev,stdevp,var,varp这些聚合函数不能用

………………

还有更多…就不一一列举了,有兴趣的请自行Google之.

下面我来通过一个例子来说明索引视图:

假设在adventureWorks数据库中,我们有一个查询:

SELECT p.Name,s.OrderQty
FROM Production.Product p
 inner join Sales.SalesOrderDetail s
ON p.ProductID=s.ProductID

这个查询的执行计划:

8before

这时,我建立视图并在这个视图上建立唯一聚集索引:

--建立视图
CREATE VIEW v_Test_IndexedView
WITH SCHEMABINDING
AS
SELECT p.Name,s.OrderQty,s.SalesOrderDetailID
FROM Production.Product p
 inner join Sales.SalesOrderDetail s
ON p.ProductID=s.ProductID
GO
--在视图上建立索引
CREATE UNIQUE CLUSTERED INDEX indexedview_test1
ON v_Test_IndexedView(SalesOrderDetailID)

接下来,套用刘谦的台词:见证奇迹的时刻到了,我们再次执行之前的查询:

8after

从上面这个例子中,可以体会到索引视图的强大威力,即使你的查询语句中不包含这个索引视图,查询分析器会自动选择这个视图,从而大大的提高了性能.当然, 这么强力的性能,只有在SQL SERVER企业版和开发版才有哦(虽然我见过很多SQL Server的开发人员让公司掏着Enterprise版的钱,用着Express版的功能……)

分割视图(Partitioned View)

分割视图其实从微观实现方式来说,整个视图所返回的数据由几个平行表(既是几个表有相同的表结构,也就是列和数据类型,但存储的行集合不同)进行UNION连接(对于UNION连接如果不了解,请看我之前的博文)所获得的数据集.

分割视图总体上可以分为两种:

1.本地分割视图(Local Partitioned View)

2.分布式分割视图(Distributed Partitioned View)

因为本地分割视图仅仅是为了和SQL Server 2005之前的版本的一种向后兼容,所以这里仅仅对分布式分割视图进行说明.

分布式分割视图其实是将由几个由不同数据源或是相同数据源获得的平行数据集进行连接所获得的,一个简单的概念图如下:

2

上面的视图所获得的数据分别来自三个不同数据源的表,每一个表中只包含四行数据,最终组成了这个分割视图.

使用分布式分割视图最大的好处就是提升性能.比如上面的例子中,我仅仅想取得ContactID为8这位员工的信息,如果通过分布式视图获取的话,SQL Server可以非常智能的仅仅扫描包含ContactID为8的表2,从而避免了整表扫描。这大大减少了IO操作,从而提升了性能.

这里要注意的是,分布式分割视图所涉及的表之间的主键不能重复,比如上面的表A ContactID是1-4,则表B的ContactID不能是2-8这个样子.

还有一点要注意的是,一定要为分布式分割索引的主键加Check约束,从而让SQL Server的查询分析器知道该去扫描哪个表,下面来看个例子.

在微软示例数据库AdventureWorks数据库,我通过ContactID从前100行和100-200行的数据分别存入两个表,Employee100和Employee200,代码如下:

--create Employee100
SELECT TOP 100 * INTO Employee100
FROM HumanResources.Employee 
ORDER BY EmployeeID
--create Employee200
SELECT *  INTO Employee200
FROM 
(SELECT TOP 100 *
FROM HumanResources.Employee 
WHERE EmployeeID NOT IN (SELECT TOP 100 EmployeeID FROM HumanResources.Employee ORDER BY EmployeeID)
ORDER BY HumanResources.Employee.EmployeeID)AS e

这时来建立分布式分割视图:

CREATE VIEW v_part_view_test
AS
SELECT * FROM Employee100
UNION 
SELECT * FROM Employee200

这时我们对这个索引进行查询操作:

SELECT * FROM v_part_view_test
WHERE EmployeeID=105

下面是执行计划:

3

通过上图可以看出,通过这种分割的方式,执行计划仅仅是扫描Employee200,从而避免了扫描所有数据,这无疑提升了性能.

所以,当你将不同的数据表之间放到不同的服务器或是使用RAID5磁盘阵列时,分布式分割视图则进一步会提升查询性能.

使用分布式分割视图能够在所有情况下都提升性能吗?那妥妥的不可能.使用这种方式如果面对的查询包含了聚合函数,尤其是聚合函数中还包含 distinct。或是不加where条件进行排序.那绝对是性能的杀手。因为聚合函数需要扫描分布式分割视图中所有的表,然后进行UNION操作后再进 行运算.

通过视图(View)更新数据

通过视图更新数据是我所不推荐的.因为视图并不能接受参数.我更推荐使用存储过程来实现.

使用View更新数据和更新Table中数据的方式完全一样(前面说过,View可以看作是一个虚拟表,如果是索引视图则是具体的一张表)

通过视图来更新数据需要注意以下几点

1.视图中From子句之后至少有一个用户表

2.View的查询无论涉及多少张表,一次只能更新其中一个表的数据

3.对于表达式计算出来的列,常量列,聚合函数算出来的列无法更新

4.Group By,Having,Distinct关键字不能影响到的列不能更新

这里说一下创建View有一个WITH Check Option选项,如果选择这个选项,则通过View所更新的数据必须符合View中where子句所限定的条件,比如:

我创建一个View:

4

视图(View)中的几个小技巧

1.通过视图名称查到视图的定义

SELECT * FROM sys.sql_modules
WHERE object_id=OBJECT_ID('视图名称')

2.前面说过,普通视图仅仅存储的是select语句和所引用表的metadata,当底层表数据改变时,有时候视图中表的metadata并没有及时同步,可以通过如下代码进行手动同步

EXEC sp_refreshview 视图名称

视图(View)的最佳实践

这是我个人一些经验,欢迎补充

  • 一定要将View中的Select语句性能调到最优(貌似是废话,不过真理都是废话…)
  • View最好不要嵌套,如果非要嵌套,最多只嵌套一层
  • 能用存储过程和自定义函数替代View的,尽量不要使用View,存储过程会缓存执行计划,性能更优,限制更少
  • 在分割视图上,不要使用聚合函数,尤其是聚合函数还包含了Distinct
  • 在视图内,如果Where子句能加在视图内,不要加在视图外(因为调用视图会返回所有行,然后再筛选,性能杀手,如果你还加上了order by…..)

总结

文中对视图的三种类型进行了详解.每种视图都有各自的使用范围,使用得当会将性能提升一个档次,而使用不当反而会拖累性能.

我想起一句名言:“everything has price,always trade-off”…..

[转载]ASP.NET MVC3 20个秘方-(15)使用CAPTCHA去防止恶意软件自动提交评论(防灌水)

mikel阅读(942)

[转载]【译】MVC3 20个秘方-(15)使用CAPTCHA去防止恶意软件自动提交评论(防灌水) – 技术弟弟 – 博客园.

问题

有种不太幸运的情况,有人用自动程序去提交表单,在整个互联网中造成大量的垃圾。为了防止这种情况的方法之一,是使用一个验证码—CAPTCHA:全自动区分计算机和人类的图灵测试,这迫使用户把生成的文字输入到文本框。

(译者:CAPTCHA是一种更人性化的验证码,可以通过视觉和听觉来区分post的请求是人类还是计算机发出的)

解决方案

NuGet安装ASP.NET Web Helpers Library 从而在BookCommentsController实现防止而已添加书评的功能。

讨论

需要安装一个新的类包,使在表单上应用CAPTCHA成为可能。微软已经创建了一个NuGet web helpers 类包含了CAPTCHA,让我们很容易实施并且验证用户输入的CAPTCHA。先打开MVC项目,在vs中选择Tools→LibraryPackage Manager→Add Library Package Reference。点击左边的Online,在第一页的下方您就可以发现 ASP.NET web helpers Library。点击安装。

在我们的例子里。那些自动发送post请求的软件一般会用在图书评论上。所以是这里最完美的添加CAPTCHA的地方。在开始之前你必须在RECAPTCHA website为你的域名注册。(译者:一般要用一个gmail账户。我们使用自己已有的或者重新注册一个,在这里由于我们的项目是在本机练习使用的,我就为我的localhost注册)。注册成功之后你可以得到一个公钥(public key)和一个私钥(private key)。

提示:如果你不使用Ajax去包含CAPTCHA,你可以通过以下两行代码改变你的view:

@using Microsoft.Web.Helpers;
@ReCaptcha.GetHtml("<你的公钥>", "<你的私钥>")

一旦配置完成了,是时候开始更新我们的代码了。我们需要在BookComments/Index view里做一些小更改。这个view是前一段创建的,用于使用ajax提交书评。这个Ajax需要更新成:当请求完毕,调用JavaScript函数去 显示CAPTCHA按钮。代码如下:

@model IEnumerable<MvcApplication.Models.BookComment>
@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<p>
    @Ajax.ActionLink("Create New", "Create", new
{
    BookId = ViewBag.BookId
},
new AjaxOptions { UpdateTargetId = "AddComment" })
</p>
<div id="AddComment">
</div>
<script type="text/javascript" src=
"http://www.google.com/recaptcha/api/js/recaptcha_ajax.js">
</script>
<script type="text/javascript">
    function DisplayCaptcha() {
        Recaptcha.destroy();
        Recaptcha.create("6Le27coSAAAAAK8KqpUIGvz3qTDXGa9ud9Xst4yY", "captcha", {});//你的公钥

    }
</script>
<table>
    <tr>
        <th>
            Comment
        </th>
        <th>
            Created
        </th>
    </tr>
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Comment)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Created)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Book.Title)
            </td>
        </tr>
    }
</table>

现在,去更新BookComments/create view 。首先添加一个地点去展示CAPTCHA.然后添加一个新的HTML 错误消息,当他们输入错误的验证码时,会提示错误。最后在ReloadComment  JavaScript 函数里更改代码成不自动reload 书评(仅仅当没错的时候才reload)。

@model MvcApplication.Models.BookComment
@{
    ViewBag.Title = "Create";
}
<h2>
    Create</h2>
@section JavascriptAndCSS {
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")"
type="text/javascript"></script>
    <script src="
@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
type="text/javascript"></script>
}
<script type="text/javascript">
    function ReloadComments() {
        var reload = "@ViewBag.RefreshComments";
        if (reload == "False") {
            DisplayCaptcha();
        } else {
            $("#Comments").load("/BookComments/Index?BookId=@ViewBag.BookId");
        }
    }
</script>
@using (Ajax.BeginForm(new AjaxOptions
{
    UpdateTargetId = "AddComment",
    OnComplete = "ReloadComments"
}))
{
    @Html.Hidden("BookId", (int)ViewBag.BookId);
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>BookComment</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Comment)
        </div>
        <div class="editor-field">
            @Html.TextAreaFor(model => model.Comment)
            @Html.ValidationMessageFor(model => model.Comment)
        </div>
        <div class="editor-label">
            Are you human?
        </div>
        <div class="editor-field">
            <div id="captcha">
            </div>
            @Html.ValidationMessage("Captcha")
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

最后我们要更新BookCommentsController 去验证输入的CAPTCHA。如果验证不合法,我们就把错误消息添加到ModelState里去,view把它展示出来。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication.Models;
using Microsoft.Web.Helpers;
using MvcApplication.Models;
namespace MvcApplication.Controllers
{
    public class BookCommentsController : Controller
    {
        private BookDBContext db = new BookDBContext();
        //
        // GET: /BookComments/
        public ActionResult Index(int BookId)
        {
            ViewBag.BookId = BookId;
            var bookcomments = db.BookComments.Include(
            b => b.Book).Where(b => b.BookId == BookId);
            return PartialView(bookcomments.ToList());
        }
        //
        // GET: /BookComments/Create
        public ActionResult Create(int BookId)
        {
            ViewBag.BookId = BookId;
            ViewBag.RefreshComments = false;
            return PartialView();
        }
        //
        // POST: /BookComments/Create
        [HttpPost]
        public ActionResult Create(BookComment bookcomment)
        {
            ViewBag.RefreshComments = false;
            var captchaSuccess = ReCaptcha.Validate(
            "6Le27coSAAAAAM6kZnXU8m1j9");//你的私钥

            if (ModelState.IsValid && captchaSuccess)
            {
                bookcomment.Created = DateTime.Now;
                db.BookComments.Add(bookcomment);
                db.SaveChanges();
                ViewBag.RefreshComments = true;
            }
            // if captcha failed add error message
            if (!captchaSuccess)
            {
                ModelState.AddModelError("Captcha",
                "Invalid CAPTCHA");
            }
            ViewBag.BookId = bookcomment.BookId;
            return PartialView(bookcomment);
        }
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

(译者:下图是我实践之后的截图,不知道这个CAPTCHA的背景样式是否能自定义,如果可以的话就太酷了!)