开发者社区> 吞吞吐吐的> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

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,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Spring MVC拦截器+注解方式实现防止表单重复提交
Spring MVC拦截器+注解方式实现防止表单重复提交
0 0
Spring MVC表单防重复提交
利用Spring MVC的过滤器及token传递验证来实现表单防重复提交。 创建注解
0 0
Spring MVC表单防重复提交
​ 利用Spring MVC的过滤器及token传递验证来实现表单防重复提交。 创建注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.
836 0
Spring MVC防止数据重复提交
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010741376/article/details/50764427          下面来讲一下如何在Spring MVC里面解决此问题(其它框架也一样,逻辑一样,思想一样,和具体框架没什么关系)。
425 0
Spring MVC拦截器+注解方式实现防止表单重复提交
原理:在新建页面中Session保存token随机码,当保存时验证,通过后删除,当再次点击保存时由于服务器端的Session中已经不存在了,所有无法验证通过。 1.新建注解: /**  * <p>  * 防止重复提交注解,用于方法上<br/>  * 在新建页面方法上,设置needSaveToken()为true,
944 0
基于Asp.Net Mvc开发的个人博客系统
一个基于Mvc 5构建的简单、代码层级分明的开源个人博客系统。前端美观大气、后台采用RightControl .NET通用角色权限系统,开发简单、效率高。网站配置采用XML配置,灵活可以根据自己是需求进行个性化配置。系统功能完备,完全可以满足需求,基本不用二次开发,非常使用程序员的个人博客。
0 0
解决ASP.NET MVC间歇性响应缓慢
解决ASP.NET MVC间歇性响应缓慢
0 0
ASP.NET MVC增删改查带图片路径读取
ASP.NET MVC增删改查带图片路径读取
0 0
ASP.NET MVC多表示例题-酒店管理
ASP.NET MVC多表示例题-酒店管理
0 0
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Java Spring Boot开发实战系列课程【第7讲】:Spring Boot 2.0安全机制与MVC身份验证实战(Java面试题)
立即下载
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载