原文来自互联网,由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除。限于译者的能力有限,个别语句翻译略显生硬,还请见谅。

作者简介:Jon(Jonathan)Seeley,一位资深.NET开发者,主要从事Asp.NET/Asp.NET CORE/WPF等技术栈的开发,他的博客地址为https://www.seeleycoder.com/。

原文链接[1]

对于那些习惯于在传统ASP.NET中使用Cookie的人来说,改用ASP.NET Core可能会让我们抓狂。在旧系统中,我们能够直接从请求和响应对象中添加和删除cookie(无论好坏)。这可能导致我们在请求期间多次写入和覆盖相同的cookie,因为不同部分的代码会影响它。DotNetCore改变了游戏规则,这是一件好事,相信我。今天,我们将学习DotNetCore Web应用程序中的cookie管理技术。

这篇文章的所有代码都可以在我的GitHub上找到[2]

了解过去

为了论证,我想介绍一下传统的ASP.NET MVC中用于加载Cookie的“通用”代码。当然,问题在于,如果代码中的某处设置了cookie值,而我们稍后又在寻找它,我们想确保我们始终获得最新的副本,而不必一定是请求中包含的内容。下面的代码看起来是否响应中首先匹配。

public static System.Web.HttpCookie GetCookie(this System.Web.HttpContextBase context, string keyName)
{System.Web.HttpCookieCollection cookies = new System.Web.HttpCookieCollection();System.Web.HttpCookie cookie = null;// check for response value first...if (context.Response.Cookies.AllKeys.Any(key => string.Equals(key, keyName, StringComparison.OrdinalIgnoreCase)))cookie = context.Response.Cookies.Get(keyName);else if (context.Request.Cookies.AllKeys.Any(key => string.Equals(key, keyName, StringComparison.OrdinalIgnoreCase)))cookie = context.Request.Cookies.Get(keyName);return cookie;
}

因此,这就是我们可能访问Cookie进行消费的方式,我们在修改过程中会不会无意中把这个流程搞乱了?我敢肯定,大家也许有很多方式,以下这是我可能做过的一个例子:

public static void SetCookie(this System.Web.HttpContextBase context, string keyName, string value, DateTime? expiry = null)
{if (context.Response.HeadersWritten)return;// a null value is equivalent to deletionif (value == null){context.Request.Cookies.Remove(keyName);context.Response.Cookies.Add(new System.Web.HttpCookie(keyName, "") { Expires = DateTime.Today.AddYears(-1) });return;}System.Web.HttpCookie newCookie = new System.Web.HttpCookie(keyName, value);if (expiry.HasValue)newCookie.Expires = expiry.Value;context.Response.Cookies.Add(newCookie);
}

在上面的代码中,我们试图确保删除cookie也可以防止在未找到同一请求的情况下尝试使用它。如果已经发送了标头,我们也将阻止编写cookie(因为它将引发异常)。该代码“不做”的一件事是防止重复,我是故意这样做的。一旦将其写到浏览器中,响应中的最后一个将调用,因此它仍将按预期“工作”,但同样,我们还有一个错误。如果您想知道,您不想随意,context.Response.Cookies.Add但是应该检查它是否已经存在,如果存在,请调用context.Response.SetCookie。

尽管编写一个cookie管理器并确保您所有的cookie代码都能通过它并不困难,但对于菜鸟和经验丰富的开发人员来说,普遍认为“它可以正常工作”是很常见的。从这个角度来说,如果您确实了解了Asp.NET中Cookie的设置方法并习惯了它,DotNetCore会让您失望。

DotNetCore的差异

既然我们已经介绍了一些您可能期望在传统的ASP.NET MVC中执行操作的方式,那么强调DotNetCore中的差异非常重要。

首先,HttpContext.Request.CookiesDotNetCore中的集合不能被修改。希望您在以前的示例中注意到,当我们删除传统版本的cookie时,我们也删除了请求副本,以确保以后不再使用无效的cookie。同样,HttpContext.Response.Cookies不允许您删除附加到该项目的项目。当然,您可以要求“删除” cookie,但这只是修改了到期时间,因此浏览器将其删除。一旦请求来了,就会调用这个方法。

当我用DotNetCore重写大型应用程序并从旧系统“复制”代码时,这些差异是我很早就遇到的,并导致了对ASP.NET Core中cookie管理的了解。

这些差异是一件好事,因为它们迫使您对正在做的事情多加思考,而不是仅仅假设一切正常。如果使用传统ASP.NET MVC的示例代码来设置Cookie,除非小心,否则最终可能会在响应中获得cookie的多个副本。

如果发生这种情况,并且您稍后尝试在同一请求中读取该值,则可能实际上并没有获得您希望的结果。这样的操作很糟糕。

介绍Cookie Service

鉴于我们之间的差异,再加上DotNetCore确实尽力让您使用依赖项注入这一事实,那么您将如何进行cookie管理?我个人认为,您所有的cookie管理都应通过服务进行分配,然后由中间件负责将最终状态写回到响应中。让我们开始吧:

public class CachedCookie
{public string Name { get; set; }public string Value { get; set; }public CookieOptions Options { get; set; }public bool IsDeleted { get; set; }
}
public interface ICookieService
{void Delete(string cookieName);T Get<T>(string cookieName, bool isBase64 = false) where T : class;T GetOrSet<T>(string cookieName, Func<T> setFunc, DateTimeOffset? expiry = null, bool isBase64 = false) where T : class;void Set<T>(string cookieName, T data, DateTimeOffset? expiry = null, bool base64Encode = false) where T : class;void WriteToResponse(HttpContext context);
}
public class CookieService : ICookieService
{private readonly HttpContext _httpContext;private Dictionary<string, CachedCookie> _pendingCookies = null;public CookieService(IHttpContextAccessor httpContextAccessor){_httpContext = httpContextAccessor.HttpContext;_pendingCookies = new Dictionary<string, CachedCookie>();}public void Delete(string cookieName){}public T Get<T>(string cookieName, bool isBase64 = false) where T : class{throw new NotImplementedException();}public T GetOrSet<T>(string cookieName, Func<T> setFunc, DateTimeOffset? expiry = null, bool isBase64 = false) where T : class{throw new NotImplementedException();}public void Set<T>(string cookieName, T data, DateTimeOffset? expiry = null, bool base64Encode = false) where T : class{}public void WriteToResponse(HttpContext context){}
}

在上面的代码块中,我添加了一个CachedCookie类, 对我们的接口进行了存根CookieService,并为我们的服务设置了框架。

我们早应了解的一件事是,由于某种原因,该服务基于泛型。我希望能够将几乎所有的价值写到我的cookie中。在这种情况下,我选择将泛型限制在一个类中(该类string可以限定,但所有基本值类型都将失败)。为了使这种魔术起作用,我将使用JSON将我的值序列化为字符串。

为了弄清楚所有部分如何组合在一起,我认为我们将一次迈出这一步。

我们的构造函数正在注入,IHttpContextAccessor这使我们能够访问HttpContext请求的当前值。这类似于我们曾经使用过的旧ASP.NET HttpContext.Current。但是,要使此方法起作用,我们需要将其注册,因此请跳至Startup.cs您的ConfigureServices方法并将这些行添加到您的方法中:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

services.AddScoped<ICookieService, CookieService>();

您还会在构造函数中注意到的另一件事是,我们正在为的实例设置一个空字典CachedCookie。在中间件将它们转储到响应之前,这是我们在请求期间跟踪cookie状态的地方。

中间件

我们需要照顾的下一件事是创建我们的中间件并将其放入我们的管道中。让我们添加CookieServiceMiddleware.cs并编写下列代码:

internal class CookieServiceMiddleware
{private readonly RequestDelegate _next;public CookieServiceMiddleware(RequestDelegate next){_next = next;}public async Task Invoke(HttpContext context, ICookieService cookieService){// write cookies to response right before it starts writing out from MVC/api responses...context.Response.OnStarting(() =>{// cookie service should not write out cookies on 500, possibly others as wellif (!context.Response.StatusCode.IsInRange(500, 599)){cookieService.WriteToResponse(context);}return Task.CompletedTask;});await _next(context);}
}

无法在构造函数级别将范围服务注入中间件。您会注意到,我在Invoke方法中[3]注入了它,这似乎有点像魔术。在DotNetCore底层的某个地方的IServiceProvider组件知道如何进行注入。要注意的另一件事是,我检测到响应何时开始,然后检查状态码是否不在特定范围内。如果超出该范围,那么我们将继续通过服务将Cookie写入响应中。该IsInRange扩展方法是一个我已经添加的话,事不宜迟,这里是一个基本的IntExtensions.cs添加到项目中我:

public static class IntExtensions
{public static bool IsInRange(this int checkVal, int value1, int value2){// First check to see if the passed in values are in order. If so, then check to see if checkVal is between themif (value1 <= value2)return checkVal >= value1 && checkVal <= value2;// Otherwise invert them and check the checkVal to see if it is between themreturn checkVal >= value2 && checkVal <= value1;}
}

注册中间件

好的 最后,我们需要添加到中间件代码中并进行连接。中间件的约定是创建一个静态类和扩展方法来处理中间件的注册。让我们添加CookieServiceMiddlewareExtensions:

public static class CookieServiceMiddlewareExtensions
{public static IApplicationBuilder UseCookieService(this IApplicationBuilder builder){return builder.UseMiddleware<CookieServiceMiddleware>();}
}

让我们进入Startup.cs进入我们的Configure方法中,并添加app.UseCookieService();到链的某处。这里的窍门是您希望它在您的app.UseMvc呼叫之前以及可能影响您响应的任何其他内容之前显示,但不要太高以至于过早地向响应写出cookie。在这种情况下,我选择在app.UseCookiePolicy通话后添加它。如果您有很多其他中间件,则您自己的工作量可能会有所不同。补充一下。如果我的中间件稍微复杂一点,并且有多个服务需要注册,那么我可能还创建了一个扩展方法来从我的ConfigureServices方法中调用。如果我正在创建一个用于分发的中间件,那么即使只有一个服务,我也绝对可以做到。我不想强迫某人必须了解一切,才能为DI配置我的中间件,他们应该能够简单地要求添加它并继续前进。

该扩展方法可能具有这样的签名public static IServiceCollection ConfigureCookieService(this IServiceCollection services, IConfiguration configuration)。(这里的IConfiguration是可选的……在某些方面我需要它,但是显然在这种情况下我们不需要它)。

实现

太好了,我们现在已经注册了我们的服务和中间件,但是它什么也没做。让我们继续,一次开始实现一种方法。由于我们实际上要尝试做的第一件事是加载cookie以供消费,也许我们应该从那里开始。进入CookieService.cs并将以下代码添加到public T Get方法中:

Get

public T Get<T>(string cookieName, bool isBase64 = false)where T : class
{return ExceptionHandler.SwallowOnException(() =>{// check local cache first...if (_pendingCookies.TryGetValue(cookieName, out CachedCookie cookie)){// don't retrieve a "deleted" cookieif (cookie.IsDeleted)return default(T);return isBase64 ? Newtonsoft.Json.JsonConvert.DeserializeObject<T>(cookie.Value.FromBase64String()): Newtonsoft.Json.JsonConvert.DeserializeObject<T>(cookie.Value);}if (_httpContext.Request.Cookies.TryGetValue(cookieName, out string cookieValue))return isBase64 ? Newtonsoft.Json.JsonConvert.DeserializeObject<T>(cookieValue.FromBase64String()): Newtonsoft.Json.JsonConvert.DeserializeObject<T>(cookieValue);return default(T);});
}

在讨论之前,让我们先看一下这些内容ExceptionHandler。我们的Get方法首先询问我们的pendingCookies字典是否有与键匹配的东西。如果有,它将询问我们是否已对其进行标记IsDeleted。如果我们有一个并且未被删除,那么我们继续将其反序列化为请求的对象类型,并且可选地,我们需要首先从base64对其进行解码。如果我们在缓存中没有它的本地副本,那么我们继续看是否HttpContext.Request.Cookies具有它,并且像我们的本地缓存一样,可以选择在最终反序列化之前从base64解码。

现在,为什么我要对它进行base64编码?从本质上讲,我并不是要“保护”我的cookie免受窥视,但是,如果我有一个非常复杂的对象,我要写出一个cookie,我想对其进行分解。对象的JSON字符串表示形式可能非常笨拙。

说到base64编码…这些是我在StringExtensions.cs文件中添加的几个扩展方法。干得好:

public static class StringExtensions
{public static string FromBase64String(this string value, bool throwException = true){try{byte[] decodedBytes = System.Convert.FromBase64String(value);string decoded = System.Text.Encoding.UTF8.GetString(decodedBytes);return decoded;}catch (Exception ex){if (throwException)throw new Exception(ex.Message, ex);elsereturn value;}}public static string ToBase64String(this string value){byte[] bytes = System.Text.ASCIIEncoding.UTF8.GetBytes(value);string encoded = System.Convert.ToBase64String(bytes);return encoded;}
}

好吧,这是什么ExceptionHandler.SwallowOnException法术?我本可以使用该try {} catch {}块,但这是一个用例,其中我100%可以接受,失败只是因为存在cookie而已,因为cookie根本就不存在。现在……如果您深入研究该处理程序的代码,您会发现它仍在执行try / catch块,我只是对其进行了抽象。让我向您证明这一点。

异常处理程序

public static class ExceptionHandler
{public static T SwallowOnException<T>(Func<T> func){try{return func();}catch{return default(T);}}
}

Set

嘿,我们很酷,可以加载cookie,但是如果我们不能创建cookie,那不是很有用,对吧?让那部分起作用。

public void Set<T>(string cookieName, T data, DateTimeOffset? expiry = null, bool base64Encode = false)where T : class
{// info about cookieoptionsCookieOptions options = new CookieOptions(){Secure = _httpContext.Request.IsHttps};if (expiry.HasValue)options.Expires = expiry.Value;if (!_pendingCookies.TryGetValue(cookieName, out CachedCookie cookie))cookie = Add(cookieName);// always set options and value;cookie.Options = options;cookie.Value = base64Encode? Newtonsoft.Json.JsonConvert.SerializeObject(data).ToBase64String(): Newtonsoft.Json.JsonConvert.SerializeObject(data);
}

创建Cookie时,我们需要设置一些信息。我在这里几乎没有内容,但我强烈建议您阅读CookieOptions[4]。不设置Expires将默认为“会话” cookie。如果您将Google Chrome浏览器用于“始终打开”模式(或所谓的“笨拙”),则它们将无法正常工作。在这里的代码中,我们将查看是否已经有一个待处理的Cookie实例,如果没有,则添加一个实例。一分钟后,我将介绍该方法。在获得cookie实例之后,我们将附加选项并编写可选的以base64编码的值。Add现在让我们看一下该方法。

protected CachedCookie Add(string cookieName)
{var cookie = new CachedCookie{Name = cookieName};_pendingCookies.Add(cookieName, cookie);return cookie;
}

很基本的东西。我们只是放宽信任,我们可以添加它并添加它。该方法没有公开,所以我相信我不必先检查字典。如果您对此不满意,请随时进行修改。

删除Cookie

在某个时候,我们将要删除Cookie,对吗?我们希望确保对同一cookie的后续查询都知道它已被删除,正如我们在Get调用中所看到的那样。为了使它正常工作,我们需要本地缓存来跟踪它。

void ICookieService.Delete(string cookieName)
{Delete(cookieName);
}
protected CachedCookie Delete(string cookieName)
{if (_pendingCookies.TryGetValue(cookieName, out CachedCookie cookie))cookie.IsDeleted = true;else{cookie = new CachedCookie{Name = cookieName,IsDeleted = true};_pendingCookies.Add(cookieName, cookie);}return cookie;
}

在上面的代码中,我们具有接口Delete方法和类Delete方法,它们都具有相同的签名。我可以给他们起个不同的名字,但我真的不想这么做。但是,为了防止编译器报错,我们必须将接口方法设为显式接口调用。我们只需将该调用传递到我们的类实例方法中。进入类实例delete方法后,我们将查看是否已经有一个暂挂实例,如果有,请将其标记为已删除。如果没有,我们将其添加到缓存中并标记为已删除。

GetOrSet

有时,您希望Cookie不管存在如何,但是如果已经存在,那么您就想获得它的价值。一个用例是如果您要加载cookie(如果存在)或设置默认值。在我工作过的一个站点上,我们有一个适合该用例的“行程计划器”。我想知道他们的详细信息(如果有的话),否则我将设置一些默认值,以便其余的会话体验基于相同的信息。设置非常简单:

public T GetOrSet<T>(string cookieName, Func<T> setFunc, DateTimeOffset? expiry = null, bool isBase64 = false)where T : class
{T cookie = Get<T>(cookieName, isBase64);if (cookie != null)return cookie;T data = setFunc();Set(cookieName, data, expiry, isBase64);return data;
}

如果cookie存在,我们得到它。如果没有,我们将其设置。十分简单。

输出

如果我们从不将其写回响应中,那么以上所有代码实际上都没有关系,对吗?还记得在context.Response.OnStarting我们告诉服务期间在中间件中执行的服务WriteToResponse吗?让我们现在实际做点什么:

public void WriteToResponse(HttpContext context)
{foreach (var cookie in _pendingCookies.Values){if (cookie.IsDeleted)context.Response.Cookies.Delete(cookie.Name);elsecontext.Response.Cookies.Append(cookie.Name, cookie.Value, cookie.Options);}
}

我们重复我们的每一个悬而未决的饼干和要么Delete或者Append他们根据我们的缓存值。现在我们只写出每个cookie的一个副本,而不是我们在本文开头介绍的经典ASP.NET崩溃。

与测试代码一起实现

GitHub上的代码在HomeController中有一个相当蹩脚的小演示。接下来是一些单元测试。在发布一些代码之前,我想回顾一下我的BaseTest.cs工作方式。我可以(坦率地说应该有),但是由于我从生产代码中复制了这个代码,而这个代码还有其他问题,所以我没有使用)DotNetCore服务集合。相反,BaseTest依赖于UnityContainer。对于我而言,这是设置依赖项引擎的一种非常简单的方法。你想怎么嘲笑就怎么嘲笑吧。

随之而来的将是CookieServiceTests类的一大堆垃圾。该Initialize方法设置了每个测试将要使用的内容,然后每个单独的测试都设置了自己的场景。如何使用该服务应该变得显而易见,并希望为您提供一些如何在自己的项目中使用该服务的想法。

[TestClass]
public class CookieServiceTests : BaseTest
{IHttpContextAccessor _httpContextAccessor;HttpContext _httpContext;CookieService _target;[TestInitialize]public void Initialize(){_httpContextAccessor = Substitute.For<IHttpContextAccessor>();_httpContext = new DefaultHttpContext();_httpContextAccessor.HttpContext.Returns(_httpContext);Container.RegisterInstance(_httpContextAccessor);_target = Container.Resolve<CookieService>();}[TestMethod]public void CookieService_SetCookie_Success(){CookieFake cookie = new CookieFake { TestProperty = 25, TestPropertyString = "blah" };_target.Set("fakecookie", cookie);CookieFake cachedCookie = _target.Get<CookieFake>("fakecookie");Assert.IsNotNull(cachedCookie);Assert.AreEqual(cookie.TestProperty, cachedCookie.TestProperty);Assert.AreEqual(cookie.TestPropertyString, cachedCookie.TestPropertyString);}[TestMethod]public void CookieService_SetCookie_StringOnly_Success(){string value = "I'm a cookie value";_target.Set("fakecookie", value);string result = _target.Get<string>("fakecookie");Assert.IsFalse(string.IsNullOrWhiteSpace(result));Assert.AreEqual(value, result);}[TestMethod]public void CookieService_SetCookie_Base64_Success(){CookieFake cookie = new CookieFake { TestProperty = 25, TestPropertyString = "blah" };_target.Set("fakecookie", cookie, base64Encode: true);CookieFake cachedCookie = _target.Get<CookieFake>("fakecookie", true);Assert.IsNotNull(cachedCookie);Assert.AreEqual(cookie.TestProperty, cachedCookie.TestProperty);Assert.AreEqual(cookie.TestPropertyString, cachedCookie.TestPropertyString);}[TestMethod]public void CookieService_GetOrSetCookie_SetsCookie_Success(){Func<CookieFake> createCookie = () =>{return new CookieFake { TestProperty = 25, TestPropertyString = "blah" };};var cookie = _target.GetOrSet<CookieFake>("fakecookie", createCookie);Assert.IsNotNull(cookie);Assert.AreEqual(cookie.TestProperty, 25);}[TestMethod]public void CookieService_GetOrSetCookie_GetsCookie_Success(){CookieFake cookie = new CookieFake { TestProperty = 25, TestPropertyString = "blah" };_target.Set("fakecookie", cookie);Func<CookieFake> createCookie = () =>{return new CookieFake { TestProperty = 55, TestPropertyString = "blah2" };};var retrievedCookie = _target.GetOrSet<CookieFake>("fakecookie", createCookie);Assert.IsNotNull(retrievedCookie);Assert.AreEqual(retrievedCookie.TestProperty, cookie.TestProperty);Assert.AreEqual(retrievedCookie.TestPropertyString, cookie.TestPropertyString);}[TestMethod]public void CookieService_GetCookie_Fail(){CookieFake cachedCookie = _target.Get<CookieFake>("fakecookie");Assert.IsNull(cachedCookie);}[TestMethod]public void CookieService_GetCookie_Base64_Fail(){CookieFake cookie = new CookieFake { TestProperty = 25, TestPropertyString = "blah" };_target.Set("fakecookie", cookie);CookieFake cachedCookie = _target.Get<CookieFake>("fakecookie", true);Assert.IsNull(cachedCookie);}
}
public class CookieFake
{public int TestProperty { get; set; }public string TestPropertyString { get; set; }
}

结论

DotNetCore Web应用程序中的Cookie管理并不是一件复杂的事情,但是很容易使效率低下。我们通过引入CookieService和中间件,研究了一种确保响应尽可能干净的方法。

今天发布的所有代码都可以在我的GitHub上找到[5]

我鼓励您查看整个项目,查看我在Web应用程序中蹩脚的示例,我相信你能从中学到有用的知识。

References

[1] 原文链接: https://www.seeleycoder.com/blog/cookie-management-asp-net-core/
[2] 我的GitHub上找到: https://github.com/fooberichu150/CookieService
[3] Invoke方法中: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#service-lifetimes
[4] 您阅读CookieOptions: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookieoptions?view=aspnetcore-2.1
[5] 我的GitHub上找到: https://github.com/fooberichu150/CookieService

DotNetCore Web应用程序中的Cookie管理相关推荐

  1. DotNetCore Web应用程序中的Session管理

    原文来自互联网,由长沙DotNET技术社区编译.如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除.限于译者的能力有限,个别语句翻译略显生硬,还请见谅. 作者简介:Jon(Jonathan ...

  2. 在中间层 .NET 应用程序中通过授权管理器使用基于角色的安全

    基于角色的安全是从 Windows NT 的第一个版本开始在 Windows 平台上发展而来的.使用角色,操作系统可以通过检查称为 BUILTIN\Administrators 的组的安全上下文做出一 ...

  3. spring_在基于Spring的Web应用程序中使用Http Session

    spring 在基于Spring的Web应用程序中拥有和使用Http会话有多种方法. 这是基于最近项目经验的总结. 方法1 只需在需要的HttpSession中注入即可. @Service publi ...

  4. 如何使用recaptcha_在Spring MVC Web应用程序中使用reCaptcha

    如何使用recaptcha CAPTCHA是一个程序,可以生成人类可以通过但计算机程序" 不能 "通过的测试并对其进行评分. 所采取的策略之一是向用户显示具有扭曲文本的图像,并且用 ...

  5. threadlocal线程_线程故事:Web应用程序中的ThreadLocal

    threadlocal线程 本周,我花了一些合理的时间来消除Web应用程序中的所有ThreadLocal变量. 原因是他们造成了类加载器泄漏,我们不能再适当地取消部署我们的应用程序. 取消部署应用程序 ...

  6. 在基于Spring的Web应用程序中使用Http Session

    在基于Spring的Web应用程序中拥有和使用Http会话有多种方法. 这是基于最近项目经验的总结. 方法1 只需在需要的HttpSession中注入即可. @Service public class ...

  7. 在Spring MVC Web应用程序中添加社交登录:单元测试

    Spring Social 1.0具有spring-social-test模块,该模块为测试Connect实现和API绑定提供支持. 该模块已从Spring Social 1.1.0中删除,并由 Sp ...

  8. 在Spring MVC Web应用程序中使用reCaptcha

    CAPTCHA是一种程序,可以生成人类可以通过的测试并对其进行评分,而计算机程序" 不能 "通过. 所采取的策略之一是向用户显示具有扭曲文本的图像,并且用户应在输入区域中书写文本. ...

  9. 线程故事:Web应用程序中的ThreadLocal

    本周,我花了一些合理的时间来消除Web应用程序中的所有ThreadLocal变量. 原因是他们造成了类加载器泄漏,我们不能再适当地取消部署我们的应用程序. 取消部署应用程序后,当GC根目录继续引用应用 ...

最新文章

  1. 从特斯拉到英伟达,那些端到端自动驾驶研发系统有何不同?
  2. c++ vector查找_C++ vector内存分配策略浅析
  3. JetBrains WebStorm 快捷键失效
  4. 大数据告诉你:学历真的能改变命运!!
  5. 手把手教你使用Pandas读取结构化数据
  6. Uncaught ReferenceError: jie is not defined
  7. 记一次mysql故障恢复
  8. oracle一步一步01
  9. Picasso,Glide,Fresco对比分析
  10. 编写一个matlab矩阵函数,MATLAB矩阵 及图像函数
  11. Linux入门命令解释(1)
  12. android软件游戏显示fps测试工具,fpsviewer—实时显示fps,监控Android卡顿的可视化工具...
  13. STM32F207时钟系统解析
  14. 正则表达式(Regular Expression)基本概念及理解
  15. 印能捷服务器中文字显示方块,修改Preps中文标记字体解决PJTF/JDF无法导入印能捷问题...
  16. 有关网络安全基础知识
  17. 三极管Vbeo、Vceo的介绍
  18. title显示不全的处理方法
  19. java如何实现动态时钟_Java实现动态数字时钟
  20. IdentityServer4 获取Token及刷新Token

热门文章

  1. TCP/IP 协议简单分析(建立连接握手过程)
  2. 实现 VC 最小化到 托盘
  3. 开发Teams Tabs应用程序
  4. mac 不能连接wi-fi_如何阻止Mac自动连接到Wi-Fi网络
  5. 固态硬盘可靠性_您可以通过使用较少的总容量来提高硬盘的可靠性吗?
  6. PropertiesUtil 获取文件属性值
  7. 异常检测之浅谈入侵检测
  8. 一次面试引发的思考(中小型网站优化思考) (转)
  9. JS partial-application
  10. IBM-X3650 6核处理器安装sql server 2005报错解决方法