日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

DotNetCore Web应用程序中的Cookie管理

發布時間:2023/12/4 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 DotNetCore Web应用程序中的Cookie管理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文來自互聯網,由長沙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管理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。