您的位置:首页 > 理论基础 > 计算机网络

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

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

[csharp] view
plain copy

 





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,包含一个

[csharp] view
plain copy

 





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中。

[csharp] view
plain copy

 





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  

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

[csharp] view
plain copy

 





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 等机制 

[csharp] view
plain copy

 





[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的扩展方法:

[csharp] view
plain copy

 





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: 

[html] view
plain copy

 





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

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

[csharp] view
plain copy

 





@using Mvc3App.Models;  

@Html.GenerateVerficationToken()  

假设我们这里有一个简单的Login.cshtml,然后插入其中:


[html] view
plain copy

 





<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.


[csharp] view
plain copy

 





[HttpPost]  

[ValidateReHttpPostToken]  

public ActionResult Index(FormCollection formCollection)  

{  

    return View();  

}  

  

public ActionResult Login()  

{  

    return View();  

}  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: