码迷,mamicode.com
首页 > Web开发 > 详细

MVC_防止HttpPost重复提交

时间:2017-04-25 16:57:05      阅读:220      评论:0      收藏:0      [点我收藏+]

标签:rar   不一致   ace   ado   void   .com   names   cti   put   

重复提交的场景很常见,可能是当时服务器延迟的原因,如购物车物品叠加,重复提交多个订单。常见的解决方法是提交后把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();
}

MVC_防止HttpPost重复提交

标签:rar   不一致   ace   ado   void   .com   names   cti   put   

原文地址:http://www.cnblogs.com/ingstyle/p/6762132.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!