DotNetCore Web应用程序中的Cookie管理
原文來自互聯(lián)網(wǎng),由長沙DotNET技術(shù)社區(qū)編譯。如譯文侵犯您的署名權(quán)或版權(quán),請聯(lián)系小編,小編將在24小時(shí)內(nèi)刪除。限于譯者的能力有限,個(gè)別語句翻譯略顯生硬,還請見諒。
作者簡介:Jon(Jonathan)Seeley,一位資深.NET開發(fā)者,主要從事Asp.NET/Asp.NET CORE/WPF等技術(shù)棧的開發(fā),他的博客地址為https://www.seeleycoder.com/。
原文鏈接[1]
對于那些習(xí)慣于在傳統(tǒng)ASP.NET中使用Cookie的人來說,改用ASP.NET Core可能會(huì)讓我們抓狂。在舊系統(tǒng)中,我們能夠直接從請求和響應(yīng)對象中添加和刪除cookie(無論好壞)。這可能導(dǎo)致我們在請求期間多次寫入和覆蓋相同的cookie,因?yàn)椴煌糠值拇a會(huì)影響它。DotNetCore改變了游戲規(guī)則,這是一件好事,相信我。今天,我們將學(xué)習(xí)DotNetCore Web應(yīng)用程序中的cookie管理技術(shù)。
這篇文章的所有代碼都可以在我的GitHub上找到[2]。
了解過去
為了論證,我想介紹一下傳統(tǒng)的ASP.NET MVC中用于加載Cookie的“通用”代碼。當(dāng)然,問題在于,如果代碼中的某處設(shè)置了cookie值,而我們稍后又在尋找它,我們想確保我們始終獲得最新的副本,而不必一定是請求中包含的內(nèi)容。下面的代碼看起來是否響應(yīng)中首先匹配。
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進(jìn)行消費(fèi)的方式,我們在修改過程中會(huì)不會(huì)無意中把這個(gè)流程搞亂了?我敢肯定,大家也許有很多方式,以下這是我可能做過的一個(gè)例子:
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也可以防止在未找到同一請求的情況下嘗試使用它。如果已經(jīng)發(fā)送了標(biāo)頭,我們也將阻止編寫cookie(因?yàn)樗鼘⒁l(fā)異常)。該代碼“不做”的一件事是防止重復(fù),我是故意這樣做的。一旦將其寫到瀏覽器中,響應(yīng)中的最后一個(gè)將調(diào)用,因此它仍將按預(yù)期“工作”,但同樣,我們還有一個(gè)錯(cuò)誤。如果您想知道,您不想隨意,context.Response.Cookies.Add但是應(yīng)該檢查它是否已經(jīng)存在,如果存在,請調(diào)用context.Response.SetCookie。
盡管編寫一個(gè)cookie管理器并確保您所有的cookie代碼都能通過它并不困難,但對于菜鳥和經(jīng)驗(yàn)豐富的開發(fā)人員來說,普遍認(rèn)為“它可以正常工作”是很常見的。從這個(gè)角度來說,如果您確實(shí)了解了Asp.NET中Cookie的設(shè)置方法并習(xí)慣了它,DotNetCore會(huì)讓您失望。
DotNetCore的差異
既然我們已經(jīng)介紹了一些您可能期望在傳統(tǒng)的ASP.NET MVC中執(zhí)行操作的方式,那么強(qiáng)調(diào)DotNetCore中的差異非常重要。
首先,HttpContext.Request.CookiesDotNetCore中的集合不能被修改。希望您在以前的示例中注意到,當(dāng)我們刪除傳統(tǒng)版本的cookie時(shí),我們也刪除了請求副本,以確保以后不再使用無效的cookie。同樣,HttpContext.Response.Cookies不允許您刪除附加到該項(xiàng)目的項(xiàng)目。當(dāng)然,您可以要求“刪除” cookie,但這只是修改了到期時(shí)間,因此瀏覽器將其刪除。一旦請求來了,就會(huì)調(diào)用這個(gè)方法。
當(dāng)我用DotNetCore重寫大型應(yīng)用程序并從舊系統(tǒng)“復(fù)制”代碼時(shí),這些差異是我很早就遇到的,并導(dǎo)致了對ASP.NET Core中cookie管理的了解。
這些差異是一件好事,因?yàn)樗鼈兤仁鼓鷮φ谧龅氖虑槎嗉铀伎?#xff0c;而不是僅僅假設(shè)一切正常。如果使用傳統(tǒng)ASP.NET MVC的示例代碼來設(shè)置Cookie,除非小心,否則最終可能會(huì)在響應(yīng)中獲得cookie的多個(gè)副本。
如果發(fā)生這種情況,并且您稍后嘗試在同一請求中讀取該值,則可能實(shí)際上并沒有獲得您希望的結(jié)果。這樣的操作很糟糕。
介紹Cookie Service
鑒于我們之間的差異,再加上DotNetCore確實(shí)盡力讓您使用依賴項(xiàng)注入這一事實(shí),那么您將如何進(jìn)行cookie管理?我個(gè)人認(rèn)為,您所有的cookie管理都應(yīng)通過服務(wù)進(jìn)行分配,然后由中間件負(fù)責(zé)將最終狀態(tài)寫回到響應(yīng)中。讓我們開始吧:
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){} }在上面的代碼塊中,我添加了一個(gè)CachedCookie類, 對我們的接口進(jìn)行了存根CookieService,并為我們的服務(wù)設(shè)置了框架。
我們早應(yīng)了解的一件事是,由于某種原因,該服務(wù)基于泛型。我希望能夠?qū)缀跛械膬r(jià)值寫到我的cookie中。在這種情況下,我選擇將泛型限制在一個(gè)類中(該類string可以限定,但所有基本值類型都將失敗)。為了使這種魔術(shù)起作用,我將使用JSON將我的值序列化為字符串。
為了弄清楚所有部分如何組合在一起,我認(rèn)為我們將一次邁出這一步。
我們的構(gòu)造函數(shù)正在注入,IHttpContextAccessor這使我們能夠訪問HttpContext請求的當(dāng)前值。這類似于我們曾經(jīng)使用過的舊ASP.NET HttpContext.Current。但是,要使此方法起作用,我們需要將其注冊,因此請?zhí)罶tartup.cs您的ConfigureServices方法并將這些行添加到您的方法中:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<ICookieService, CookieService>();
您還會(huì)在構(gòu)造函數(shù)中注意到的另一件事是,我們正在為的實(shí)例設(shè)置一個(gè)空字典CachedCookie。在中間件將它們轉(zhuǎn)儲(chǔ)到響應(yīng)之前,這是我們在請求期間跟蹤cookie狀態(tài)的地方。
中間件
我們需要照顧的下一件事是創(chuàng)建我們的中間件并將其放入我們的管道中。讓我們添加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);} }無法在構(gòu)造函數(shù)級別將范圍服務(wù)注入中間件。您會(huì)注意到,我在Invoke方法中[3]注入了它,這似乎有點(diǎn)像魔術(shù)。在DotNetCore底層的某個(gè)地方的IServiceProvider組件知道如何進(jìn)行注入。要注意的另一件事是,我檢測到響應(yīng)何時(shí)開始,然后檢查狀態(tài)碼是否不在特定范圍內(nèi)。如果超出該范圍,那么我們將繼續(xù)通過服務(wù)將Cookie寫入響應(yīng)中。該IsInRange擴(kuò)展方法是一個(gè)我已經(jīng)添加的話,事不宜遲,這里是一個(gè)基本的IntExtensions.cs添加到項(xiàng)目中我:
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;} }注冊中間件
好的 最后,我們需要添加到中間件代碼中并進(jìn)行連接。中間件的約定是創(chuàng)建一個(gè)靜態(tài)類和擴(kuò)展方法來處理中間件的注冊。讓我們添加CookieServiceMiddlewareExtensions:
public static class CookieServiceMiddlewareExtensions {public static IApplicationBuilder UseCookieService(this IApplicationBuilder builder){return builder.UseMiddleware<CookieServiceMiddleware>();} }讓我們進(jìn)入Startup.cs進(jìn)入我們的Configure方法中,并添加app.UseCookieService();到鏈的某處。這里的竅門是您希望它在您的app.UseMvc呼叫之前以及可能影響您響應(yīng)的任何其他內(nèi)容之前顯示,但不要太高以至于過早地向響應(yīng)寫出cookie。在這種情況下,我選擇在app.UseCookiePolicy通話后添加它。如果您有很多其他中間件,則您自己的工作量可能會(huì)有所不同。補(bǔ)充一下。如果我的中間件稍微復(fù)雜一點(diǎn),并且有多個(gè)服務(wù)需要注冊,那么我可能還創(chuàng)建了一個(gè)擴(kuò)展方法來從我的ConfigureServices方法中調(diào)用。如果我正在創(chuàng)建一個(gè)用于分發(fā)的中間件,那么即使只有一個(gè)服務(wù),我也絕對可以做到。我不想強(qiáng)迫某人必須了解一切,才能為DI配置我的中間件,他們應(yīng)該能夠簡單地要求添加它并繼續(xù)前進(jìn)。
該擴(kuò)展方法可能具有這樣的簽名public static IServiceCollection ConfigureCookieService(this IServiceCollection services, IConfiguration configuration)。(這里的IConfiguration是可選的……在某些方面我需要它,但是顯然在這種情況下我們不需要它)。
實(shí)現(xiàn)
太好了,我們現(xiàn)在已經(jīng)注冊了我們的服務(wù)和中間件,但是它什么也沒做。讓我們繼續(xù),一次開始實(shí)現(xiàn)一種方法。由于我們實(shí)際上要嘗試做的第一件事是加載cookie以供消費(fèi),也許我們應(yīng)該從那里開始。進(jìn)入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);}); }在討論之前,讓我們先看一下這些內(nèi)容ExceptionHandler。我們的Get方法首先詢問我們的pendingCookies字典是否有與鍵匹配的東西。如果有,它將詢問我們是否已對其進(jìn)行標(biāo)記IsDeleted。如果我們有一個(gè)并且未被刪除,那么我們繼續(xù)將其反序列化為請求的對象類型,并且可選地,我們需要首先從base64對其進(jìn)行解碼。如果我們在緩存中沒有它的本地副本,那么我們繼續(xù)看是否HttpContext.Request.Cookies具有它,并且像我們的本地緩存一樣,可以選擇在最終反序列化之前從base64解碼。
現(xiàn)在,為什么我要對它進(jìn)行base64編碼?從本質(zhì)上講,我并不是要“保護(hù)”我的cookie免受窺視,但是,如果我有一個(gè)非常復(fù)雜的對象,我要寫出一個(gè)cookie,我想對其進(jìn)行分解。對象的JSON字符串表示形式可能非常笨拙。
說到base64編碼…這些是我在StringExtensions.cs文件中添加的幾個(gè)擴(kuò)展方法。干得好:
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法術(shù)?我本可以使用該try {} catch {}塊,但這是一個(gè)用例,其中我100%可以接受,失敗只是因?yàn)榇嬖赾ookie而已,因?yàn)閏ookie根本就不存在。現(xiàn)在……如果您深入研究該處理程序的代碼,您會(huì)發(fā)現(xiàn)它仍在執(zhí)行try / catch塊,我只是對其進(jìn)行了抽象。讓我向您證明這一點(diǎn)。
異常處理程序
public static class ExceptionHandler {public static T SwallowOnException<T>(Func<T> func){try{return func();}catch{return default(T);}} }Set
嘿,我們很酷,可以加載cookie,但是如果我們不能創(chuàng)建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); }創(chuàng)建Cookie時(shí),我們需要設(shè)置一些信息。我在這里幾乎沒有內(nèi)容,但我強(qiáng)烈建議您閱讀CookieOptions[4]。不設(shè)置Expires將默認(rèn)為“會(huì)話” cookie。如果您將Google Chrome瀏覽器用于“始終打開”模式(或所謂的“笨拙”),則它們將無法正常工作。在這里的代碼中,我們將查看是否已經(jīng)有一個(gè)待處理的Cookie實(shí)例,如果沒有,則添加一個(gè)實(shí)例。一分鐘后,我將介紹該方法。在獲得cookie實(shí)例之后,我們將附加選項(xiàng)并編寫可選的以base64編碼的值。Add現(xiàn)在讓我們看一下該方法。
protected CachedCookie Add(string cookieName) {var cookie = new CachedCookie{Name = cookieName};_pendingCookies.Add(cookieName, cookie);return cookie; }很基本的東西。我們只是放寬信任,我們可以添加它并添加它。該方法沒有公開,所以我相信我不必先檢查字典。如果您對此不滿意,請隨時(shí)進(jìn)行修改。
刪除Cookie
在某個(gè)時(shí)候,我們將要?jiǎng)h除Cookie,對嗎?我們希望確保對同一cookie的后續(xù)查詢都知道它已被刪除,正如我們在Get調(diào)用中所看到的那樣。為了使它正常工作,我們需要本地緩存來跟蹤它。
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方法,它們都具有相同的簽名。我可以給他們起個(gè)不同的名字,但我真的不想這么做。但是,為了防止編譯器報(bào)錯(cuò),我們必須將接口方法設(shè)為顯式接口調(diào)用。我們只需將該調(diào)用傳遞到我們的類實(shí)例方法中。進(jìn)入類實(shí)例delete方法后,我們將查看是否已經(jīng)有一個(gè)暫掛實(shí)例,如果有,請將其標(biāo)記為已刪除。如果沒有,我們將其添加到緩存中并標(biāo)記為已刪除。
GetOrSet?
有時(shí),您希望Cookie不管存在如何,但是如果已經(jīng)存在,那么您就想獲得它的價(jià)值。一個(gè)用例是如果您要加載cookie(如果存在)或設(shè)置默認(rèn)值。在我工作過的一個(gè)站點(diǎn)上,我們有一個(gè)適合該用例的“行程計(jì)劃器”。我想知道他們的詳細(xì)信息(如果有的話),否則我將設(shè)置一些默認(rèn)值,以便其余的會(huì)話體驗(yàn)基于相同的信息。設(shè)置非常簡單:
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存在,我們得到它。如果沒有,我們將其設(shè)置。十分簡單。
輸出
如果我們從不將其寫回響應(yīng)中,那么以上所有代碼實(shí)際上都沒有關(guān)系,對嗎?還記得在context.Response.OnStarting我們告訴服務(wù)期間在中間件中執(zhí)行的服務(wù)WriteToResponse嗎?讓我們現(xiàn)在實(shí)際做點(diǎn)什么:
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);} }我們重復(fù)我們的每一個(gè)懸而未決的餅干和要么Delete或者Append他們根據(jù)我們的緩存值。現(xiàn)在我們只寫出每個(gè)cookie的一個(gè)副本,而不是我們在本文開頭介紹的經(jīng)典ASP.NET崩潰。
與測試代碼一起實(shí)現(xiàn)
GitHub上的代碼在HomeController中有一個(gè)相當(dāng)蹩腳的小演示。接下來是一些單元測試。在發(fā)布一些代碼之前,我想回顧一下我的BaseTest.cs工作方式。我可以(坦率地說應(yīng)該有),但是由于我從生產(chǎn)代碼中復(fù)制了這個(gè)代碼,而這個(gè)代碼還有其他問題,所以我沒有使用)DotNetCore服務(wù)集合。相反,BaseTest依賴于UnityContainer。對于我而言,這是設(shè)置依賴項(xiàng)引擎的一種非常簡單的方法。你想怎么嘲笑就怎么嘲笑吧。
隨之而來的將是CookieServiceTests類的一大堆垃圾。該Initialize方法設(shè)置了每個(gè)測試將要使用的內(nèi)容,然后每個(gè)單獨(dú)的測試都設(shè)置了自己的場景。如何使用該服務(wù)應(yīng)該變得顯而易見,并希望為您提供一些如何在自己的項(xiàng)目中使用該服務(wù)的想法。
[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; } }結(jié)論
DotNetCore Web應(yīng)用程序中的Cookie管理并不是一件復(fù)雜的事情,但是很容易使效率低下。我們通過引入CookieService和中間件,研究了一種確保響應(yīng)盡可能干凈的方法。
今天發(fā)布的所有代碼都可以在我的GitHub上找到[5]。
我鼓勵(lì)您查看整個(gè)項(xiàng)目,查看我在Web應(yīng)用程序中蹩腳的示例,我相信你能從中學(xué)到有用的知識(shí)。
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
總結(jié)
以上是生活随笔為你收集整理的DotNetCore Web应用程序中的Cookie管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Angular SPA基于Ocelot
- 下一篇: .NET Core开发实战(第25课:路