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()
[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>
[csharp] view
plain copy
[HttpPost]
[ValidateReHttpPostToken]
public ActionResult Index(FormCollection formCollection)
{
return View();
}
public ActionResult Login()
{
return View();
}
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();
}
相关文章推荐
- Asp.net MVC中防止HttpPost重复提交
- Asp.net MVC中防止HttpPost重复提交
- Asp.net MVC中防止HttpPost重复提交
- Asp.net MVC中防止HttpPost重复提交
- Asp.net MVC中防止HttpPost重复提交
- 在ASP.NET Web Form和MVC中防止F5刷新引起的重复提交
- ASP.NET中的ListView中防止刷新导致的重复post提交的问题
- ASP.NET Web Form和MVC中防止F5刷新引起的重复提交问题
- MVC中防止HttpPost重复提交
- AspNetMvc 防止重复提交
- asp.net mvc 防止重复提交
- 使用Post/Redirect/Get实现Asp.net防止表单重复提交
- MVC_防止HttpPost重复提交
- 在asp.net mvc中使用ActionFilter防止重复提交
- Asp.net的MVC中如何避免POST请求中出现的重复提交
- ASP.NET的防止重复提交的脚本
- asp.net 服务器控件防止重复提交表单
- 移动项目开发笔记(asp.net防止页面刷新引起重复提交数据)
- Asp.net实现弹出窗口提示,又防止刷新被重复提交的方法
- 收集:asp.net 防止页面重复提交