重复提交的场景很常见,可能是当时服务器延迟的原因,如购物车物品叠加,重复提交多个订单。常见的解决方法是提交后把Button在客户端Js禁用,或是用Js禁止后退键等。在ASP.NET MVC 3 Web Application中 如何去防止这类HTTP-Post的重复提交呢? 我们可以借助Session,放置一个Token在View/Page上,然后在Server端去验证是不是同一个Token来判断此次Http-Post是否有效。看下面的代码:  首先定义一个接口,便于扩展。

public interface IPageTokenView
{/// <summary>/// Generates the page token./// </summary>string GeneratePageToken();/// <summary>/// Gets the get last page token from Form/// </summary>string GetLastPageToken { get; }/// <summary>/// Gets a value indicating whether [tokens match]./// </summary>/// <value>///   <c>true</c> if [tokens match]; otherwise, <c>false</c>./// </value>bool TokensMatch { get; }
}

定义一个Abstract Class,包含一个

public abstract class PageTokenViewBase : IPageTokenView
{public static readonly string HiddenTokenName = "hiddenToken";public static readonly string SessionMyToken = "Token";/// <summary>/// Generates the page token./// </summary>/// <returns></returns>public abstract string GeneratePageToken();/// <summary>/// Gets the get last page token from Form/// </summary>public abstract string GetLastPageToken { get; }/// <summary>/// Gets a value indicating whether [tokens match]./// </summary>/// <value>///   <c>true</c> if [tokens match]; otherwise, <c>false</c>./// </value>public abstract bool TokensMatch { get; }}

接着是实现SessionPageTokenView类型,记得需要在验证通过后生成新的Token,对于这个Class是把它放到Session中。

    public class SessionPageTokenView : PageTokenViewBase{#region PageTokenViewBase/// <summary>/// Generates the page token./// </summary>/// <returns></returns>public override string GeneratePageToken(){if (HttpContext.Current.Session[SessionMyToken] != null){return HttpContext.Current.Session[SessionMyToken].ToString();}else{var token = GenerateHashToken();HttpContext.Current.Session[SessionMyToken] = token;return token;}}/// <summary>/// Gets the get last page token from Form/// </summary>public override string GetLastPageToken{get{return HttpContext.Current.Request.Params[HiddenTokenName];}}/// <summary>/// Gets a value indicating whether [tokens match]./// </summary>/// <value>///   <c>true</c> if [tokens match]; otherwise, <c>false</c>./// </value>public override bool TokensMatch{get{string formToken = GetLastPageToken;if (formToken != null){if (formToken.Equals(GeneratePageToken())){//Refresh tokenHttpContext.Current.Session[SessionMyToken] = GenerateHashToken();return true;}}return false;}}#endregion #region Private Help Method/// <summary>/// Generates the hash token./// </summary>/// <returns></returns>private string GenerateHashToken(){return Utility.Encrypt(HttpContext.Current.Session.SessionID + DateTime.Now.Ticks.ToString());} #endregion

这里有到一个简单的加密方法,你可以实现自己的加密方法.

public static string Encrypt(string plaintext)
{string cl1 = plaintext;string pwd = string.Empty;MD5 md5 = MD5.Create();byte[] s = md5.ComputeHash(Encoding.Unicode.GetBytes(cl1));for (int i = 0; i < s.Length; i++){pwd = pwd + s[i].ToString("X");}return pwd;
}

我们再来编写一个Attribute继承FilterAttribute, 实现IAuthorizationFilter接口。然后比较Form中Token与Session中是否一致,不一致就Throw Exception. Tips:这里最好使用依赖注入IPageTokenView类型,增加Logging 等机制

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ValidateReHttpPostTokenAttribute : FilterAttribute, IAuthorizationFilter
{public IPageTokenView PageTokenView { get; set; }/// <summary>/// Initializes a new instance of the <see cref="ValidateReHttpPostTokenAttribute"/> class./// </summary>public ValidateReHttpPostTokenAttribute(){//It would be better use DI inject it.PageTokenView = new SessionPageTokenView();}/// <summary>/// Called when authorization is required./// </summary>/// <param name="filterContext">The filter context.</param>public void OnAuthorization(AuthorizationContext filterContext){if (filterContext == null){throw new ArgumentNullException("filterContext");}if (!PageTokenView.TokensMatch){//log...throw new Exception("Invaild Http Post!");}}
}

还需要一个HtmlHelper的扩展方法:

public static HtmlString GenerateVerficationToken(this HtmlHelper htmlhelper)
{string formValue = Utility.Encrypt(HttpContext.Current.Session.SessionID+DateTime.Now.Ticks.ToString());HttpContext.Current.Session[PageTokenViewBase.SessionMyToken] = formValue;string fieldName = PageTokenViewBase.HiddenTokenName;TagBuilder builder = new TagBuilder("input");builder.Attributes["type"] = "hidden";builder.Attributes["name"] = fieldName;builder.Attributes["value"] = formValue;return new HtmlString(builder.ToString(TagRenderMode.SelfClosing));
}

将输出这类的HtmlString:

<input name="hiddenToken" type="hidden" value="1AB01826F590A1829E65CBD23CCE8D53" />

我们创建一个叫_ViewToken.cshtml的Partial View,这样便于模块化,让我们轻易加入到具体View里,就两行代码,第一行是扩展方法NameSpace

@using Mvc3App.Models;
@Html.GenerateVerficationToken()


假设我们这里有一个简单的Login.cshtml,然后插入其中:
   <form method="post" id="form1" action="@Url.Action("Index")"><p>@Html.Partial("_ViewToken")UserName:<input type="text" id="fusername" name="fusername" /><br />Password:<input type="password" id="fpassword" name="fpassword" /><input type="submit" value="Sign-in" /></p></form>

这里我们Post的Index  Action,看Controller代码,我们在Index上加上ValidateReHttpPostToken的attribute.


[HttpPost]
[ValidateReHttpPostToken]
public ActionResult Index(FormCollection formCollection)
{return View();
}public ActionResult Login()
{return View();
}



好的,完了,由于篇幅有限,单元测试代码不贴了。让我们运行程序在IE中. 正常点击Button后提交表单,此时按F5再次提交,看到这个提示框:




点击Retry后,这时就会出现预期Exception,这里只是为了演示,实际中可能需要记录日志,做异常处理。

Invaild Http Post!

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.Exception: Invaild Http Post! 
有兴趣您可以自己试一下,希望对您Web开发有帮助。

Asp.net MVC中防止HttpPost重复提交相关推荐

  1. ASP.NET MVC中在Action获取提交的表单数据方法总结

    有Index视图如下: 视图代码如下: [html] view plaincopy <%@ Page Language="C#" MasterPageFile="~ ...

  2. ASP.NET MVC中在Action获取提交的表单数据方法总结 (4种方法,转载备忘)

    有Index视图如下: 视图代码如下: [html] view plaincopyprint? <%@ Page Language="C#" MasterPageFile=& ...

  3. ASP.NET MVC中实现多个按钮提交的几种方法

    有时候会遇到这种情况:在一个表单上需要多个按钮来完成不同的功能,比如一个简单的审批功能. 如果是用webform那不需要讨论,但asp.net mvc中一个表单只能提交到一个Action处理,相对比较 ...

  4. 如何在ASP.NET MVC中实现提交若干个某模型的数据(某Model的List或ICollection,大小不定)

    背景说明 在ASP.NET MVC中,有一个我们经常使用且十分好用的功能--模型绑定. 即在页面中指定该页面将会使用到的数据模型Model,然后在"显示数据"或"提交数据 ...

  5. 获取ASP.NET MVC中的完整操作URL [重复]

    本文翻译自:Getting full URL of action in ASP.NET MVC [duplicate] This question already has an answer here ...

  6. MVC中实现多按钮提交(转)

    有时候会遇到这种情况:在一个表单上需要多个按钮来完成不同的功能,比如一个简单的审批功能. 如果是用webform那不需要讨论,但asp.net mvc中一个表单只能提交到一个Action处理,相对比较 ...

  7. 在ASP.NET MVC中实现Select多选

    我们知道,在ASP.NET MVC中实现多选Select的话,使用Html.ListBoxFor或Html.ListBox方法就可以.在实际应用中,到底该如何设计View Model, 控制器如何接收 ...

  8. Asp.Net MVC中DropDownListFor的用法(转)

    2016.03.04 扩展:如果 view中传入的是List<T>类型 怎么使用 DropList 既然是List<T> 那么我转化成 T  List<T>的第一个 ...

  9. Asp.Net MVC中DropDownListFor的用法

    在Asp.Net MVC中可以用DropDownListFor的方式来让用户选择已定列表中的一个数值.用法不复杂,这里简单做一个记录. 首先我们要定义一个 Model ,用户在 DropDownLis ...

最新文章

  1. Django_ORM数据表查询总结
  2. bzoj4034: [HAOI2015]树上操作
  3. 温州大学《深度学习》课程课件(十二、自然语言处理和词嵌入)
  4. learning python学习小记(一)
  5. grep和egrep的一些简单用法
  6. 浅谈项目开发现状(一)
  7. 10分钟10行代码开发APP(delphi 应用案例)
  8. Linux编译LLVM,如何使用ninja快速编译LLVM和Clang(以llvm3.3为例子)
  9. 转 shell awk 使用详解
  10. 计算机算法实验报告二——递归
  11. 剧情系统实战,我们要做一个什么样的剧情系统
  12. 虚拟机ruc_sched Self-detected stall on cpu{4}(t=60001)
  13. Linux中rps/rfs的原理及实现
  14. 家用计算机的计算速度,计算机CPU运算速度是多少
  15. 联想笔记本无法识别USB(通用串行总线(USB)的控制器问题)
  16. Cocos精品《地下城堡2》:从挂机到RPG 唯有暗黑始终如一
  17. 配电站智能巡检机器人,电力智能巡检机器人
  18. linux环境下安装pyhdf
  19. 【SQL Server系列】_01数据库系统概述
  20. Testing Process - 读书笔记

热门文章

  1. QML模拟示波器界面。
  2. adb linux 权限更改,使用命令chmod修改android文件权限
  3. HDFS权限设置 \ HDFS涉及ACLs的命令
  4. Kylin安装,Kylin网页版教程学习
  5. 在tnsnames.ora中配置监听
  6. 修改JBOSS服务器的端口号
  7. 使用DOM解析常用方法
  8. 05-ServletconfigServletCOntext
  9. Mysql 优化器内部JOIN算法hash join Nestloopjoin及classic hash join CHJ过程详解
  10. 写好的python如何在其它电脑上运行_如何在另一个文件中运行一个python文件?