Asp.net MVC中防止HttpPost重复提交

简介:

重复提交的场景很常见,可能是当时服务器延迟的原因,如购物车物品叠加,重复提交多个订单。常见的解决方法是提交后把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 token
                        HttpContext.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
本文转自左正博客园博客,原文链接:http://www.cnblogs.com/soundcode/archive/2013/06/12/3132576.html ,如需转载请自行联系原作者
相关文章
|
3月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
38 0
|
29天前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
28 0
|
29天前
mvc.net分页查询案例——mvc-paper.css
mvc.net分页查询案例——mvc-paper.css
5 0
|
5月前
|
前端开发 Java Spring
Spring MVC拦截器+注解方式实现防止表单重复提交
Spring MVC拦截器+注解方式实现防止表单重复提交
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
95 5
|
3月前
|
XML 前端开发 定位技术
C#(NET Core3.1 MVC)生成站点地图(sitemap.xml)
C#(NET Core3.1 MVC)生成站点地图(sitemap.xml)
25 0
|
3月前
|
前端开发
.net core mvc获取IP地址和IP所在地(其实是百度的)
.net core mvc获取IP地址和IP所在地(其实是百度的)
123 0
|
5月前
|
开发框架 自然语言处理 前端开发
基于ASP.NET MVC开发的、开源的个人博客系统
基于ASP.NET MVC开发的、开源的个人博客系统
51 0
|
8月前
|
SQL 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(完:内附源码)
经过一段时间的准备,【ASP.NET Core MVC开发实战之商城系统】已经完成,目前代码已开发完成,先将全部内容整理分享,如有不足之处,还请指正。
106 0
|
8月前
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
114 0