MVC中防止HttpPost重复提交

重复提交的场景很常见,
1、可能是当时服务器延迟的原因,如购物车物品叠加,重复提交多个表单。
2、按F5 不断的刷新提交页地址,进行HttpPost


解决方法流程:
1、页面中生成一个加密的字符串,分别存在Session、Input隐藏域中
2、页面提交时,将Input隐藏域的值与Session的值进行比较,第一次提交两个值相等,不会抛出异常。
3、将Session存入随机数(刷新Session)
4、按F5刷新提交页地址,因为Session的值已经刷新,Input隐藏域的值没刷新,所有抛出异常"无效的HTTP POST!"

 

一、Models层

首先定义一个接口,便于扩展

namespace MvcApplication4.Models
{
    public interface IPageTokenView
    {
        /// <summary>
        /// 生成的页面标记
        /// </summary>
        string GeneratePageToken();

        /// <summary>
        /// 取得最后一页的标记形式
        /// </summary>
        string GetLastPageToken { get; }

        /// <summary>
        /// 获取一个值,指示是否匹配[标记]。
        /// </summary>
        /// <value>
        ///   <c>true</c> 如果[标记]不匹配<c>false</c>.
        /// </value>
        bool TokensMatch { get; }
    }
}


定义一个Abstract Class定义一个抽象类,继承自接口

namespace MvcApplication4.Models
{
    public abstract class PageTokenViewBase : IPageTokenView
    {
        public static readonly string HiddenTokenName = "hiddenToken";//隐藏域名字
        public static readonly string SessionMyToken = "Token";//Session名

        /// <summary>
        /// 生成的页面标记
        /// </summary>
        /// <returns></returns>
        public abstract string GeneratePageToken();

        /// <summary>
        /// 取得最后一页的标记形式
        /// </summary>
        public abstract string GetLastPageToken { get; }

        /// <summary>
        /// 获取一个值,指示是否匹配[标记]。
        /// </summary>
        /// <value>
        ///   <c>true</c> 如果[标记]不匹配 <c>false</c>.
        /// </value>
        public abstract bool TokensMatch { get; }

    }
}


实例化抽象类的方法

namespace MvcApplication4.Models
{
    public class SessionPageTokenView : PageTokenViewBase
    {
        #region PageTokenViewBase

        /// <summary>
        /// 生成的页面标记
        /// </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>
        /// 取得最后一页的标记形式
        /// </summary>
        public override string GetLastPageToken
        {
            get
            {
                return HttpContext.Current.Request.Params[HiddenTokenName];
            }
        }

        /// <summary>
        /// 获取一个值,指示是否匹配[标记]。
        /// </summary>
        /// <value>
        ///   <c>true</c> 如果[标记]不匹配  <c>false</c>.
        /// </value>
        public override bool TokensMatch
        {
            get
            {
                string formToken = GetLastPageToken;
                if (formToken != null)
                {
                    if (formToken.Equals(GeneratePageToken()))
                    {
                        //刷新Session["token"]
                        HttpContext.Current.Session[SessionMyToken] = GenerateHashToken();
                        return true;
                    }
                }
                return false;
            }
        }
        #endregion

        #region Private Help Method
        /// <summary>
        /// 生成哈希标记.
        /// </summary>
        /// <returns></returns>
        private string GenerateHashToken()
        {
            return Utility.Encrypt(
                HttpContext.Current.Session.SessionID + DateTime.Now.Ticks.ToString());
        }
        #endregion

    }
}


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

namespace MvcApplication4.Models
{
    public class Utility
    {
        /// <summary>
        /// 加密
        /// </summary>
        /// <param name="plaintext">加密文本</param>
        /// <returns></returns>
        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 等机制

namespace MvcApplication4.Models
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class ValidateReHttpPostTokenAttribute : FilterAttribute, IAuthorizationFilter
    {
        public IPageTokenView PageTokenView { get; set; }

        /// <summary>
        /// 新实例初始化 <see cref="ValidateReHttpPostTokenAttribute"/> class.
        /// </summary>
        public ValidateReHttpPostTokenAttribute()
        {
            PageTokenView = new SessionPageTokenView();//类实例化接口
        }

        /// <summary>
        /// 在需要授权时调用.
        /// </summary>
        /// <param name="filterContext">筛选器上下文.</param>
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            //比较Form中Token与Session中是否一致,不一致就Throw Exception. Tips:这里最好使用依赖注入IPageTokenView类型,增加Logging 等机制
            if (!PageTokenView.TokensMatch)
            {
                //log...
                throw new Exception("无效的HTTP POST!");
            }
        }
    }
}


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

namespace MvcApplication4.Models
{
    /// <summary>
    /// 拓展方法如何使用?      
    ///@using MvcApplication4.Models; 1、引入拓展方法的命名空间
    ///@Html.GenerateVerficationToken() 2、调用拓展方法
    /// </summary>
    public static class GenerateVerficationTokenE
    {
        /// <summary>
        /// htmlHelper拓展方法(将输出这类的HtmlString: <input name="hiddenToken" type="hidden" value="1AB01826F590A1829E65CBD23CCE8D53" />)
        /// </summary>
        /// <param name="htmlhelper"></param>
        /// <returns></returns>
        public static HtmlString GenerateVerficationToken(this HtmlHelper htmlhelper)
        {
            //Session
            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));
        }
    }
}


 

二、Views层

@using MvcApplication4.Models;//1、引入拓展方法的命名空间
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>编辑用户信息</h2>

<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)
        <div>
        @Html.GenerateVerficationToken() //2、调用拓展方法
        </div>
        <div>
         <input type="submit" value="保存" name="tj"/>
        </div>
}


 

三、Controllers层

namespace MvcApplication4.Controllers
{
    public class TestController : Controller
    {

        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        [ValidateReHttpPostToken]//注意防止页面反复提交
        public ActionResult Index(Person person)
        {
            string str = "";

            if (ModelState.IsValid)//模型状态字典实例有效
            {
                //......
            }

            return Content(str);
        }     
    }
}


让我们运行程序在IE中. 正常点击Button后提交表单后,在按F5刷新就会抛出异常

 

 代码下载地址:http://download.csdn.net/detail/tiz198183/5148104

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tiz198183

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值