原文来自互联网,由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除。限于译者的能力有限,个别语句翻译略显生硬,还请见谅。
作者简介:Jon(Jonathan)Seeley,一位资深.NET开发者,主要从事Asp.NET/Asp.NET CORE/WPF等技术栈的开发,他的博客地址为https://www.seeleycoder.com/。
查看原文[1]
上个月,我们讨论了DotNetCore Web应用程序中的Cookie管理,[2]并介绍了通用Cookie服务。今天,我们将研究一种用于存储用户数据的替代选项。会话状态是链接到浏览会话的信息的服务器存储,让我们看一下dotnetcore Web应用程序中的通用会话管理技术。
这篇文章的所有代码都可以在我的GitHub上找到[3]。
开始之前
如果您在该领域工作了一段时间,您可能已经注意到很多教程向您展示了如何做某事。我的意思是,那就是他们在那里的权利吗?他们通常不会向您显示的是*
正确地
的操作方法*。我今天要做同样的事情。可是等等!请允许我快速解释。今天,我们将在此会话中使用内存中会话状态。
从本质上说,这没错,但也不对。对于任何大小的应用程序,您都希望使用非易失性会话提供程序。例如Redis,数据库,XML文件或其他内容。好吧,Redis实际上也在内存中,对吗?是的,我们不要被语义所困扰。他们将其作为单独的服务运行。
入门
假设您刚刚加载了控制台并输入了以下命令:dotnet new mvc -n SessionManager
现在,您拥有一个崭新的网站品牌,在撰写本文时,该网站正在使用netcoreapp2.2。精彩。
让我们打开它并转到Startup.cs文件,然后在ConfigureServices调用中添加以下几行。这将注册ISession到请求生命周期中,并允许我们从httpcontext访问它。我们还将公开IHttpContextAccessor注入,因为稍后需要使用它。
// have to add this in order to access HttpContext from our own services
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// register ISession into our HttpContext lifecycle
services.AddSession(options =>
{options.Cookie.HttpOnly = true;
});
ISession无法正常注入,但是您可以通过提供者工厂做一些魔术来将其公开为可注入对象。为了我们的目的,我们不需要,但让我们假装您想要。您可以执行以下操作:
services.AddScoped(provider => provider.GetRequiredService().HttpContext?.Session);
我们还没有完成。我们需要指示管道实际使用会话。弹出该Configure方法,并app.UseSession();在app.UseMvc(...)调用之前添加某处。
添加SessionService
由于我们不会直接注入ISession,因此会出现“我们该怎么办?”的问题。让我们创建一个ISessionService并使用一些通用方法进行设置,以便我们可以序列化/反序列化我们想要的任何内容。如果您查看过Cookie管理方面[4]的信息,则看起来非常相似。
public interface ISessionService
{T Get<T>(string key);void Set<T>(string key, T value);void Remove(params string[] keys);
}
public class SessionService : ISessionService
{private readonly ISession _session;public SessionService(IHttpContextAccessor httpContextRepository){_session = httpContextRepository.HttpContext.Session;if (_session == null)throw new ArgumentNullException("Session cannot be null.");}public T Get<T>(string key){throw new NotImplementedException();}public void Set<T>(string key, T value){throw new NotImplementedException();}public void Remove(params string[] keys){throw new NotImplementedException();}
}
在上面的代码块中,我们为ISessionService创建了存根,并为实现创建了框架。我们注入了IHttpContextAccessor,然后从HttpContext访问了Session属性。我们进行检查以确保Session不为空。如果您尝试将其注入通常不具有HttpContext的某个地方(可能是托管服务等),则可能会发生这种情况。我们需要跳回到Startup.cs并添加services.AddScoped<ISessionService, SessionService>();到我们的ConfigureServices调用中。只需在AddSession通话后直接添加即可,但诚实订购无所谓。
实施SessionService
让该服务变得有趣。进入并将以下代码添加到方法中:
public T Get<T>(string key)
{string value = _session.GetString(key);if (string.IsNullOrWhiteSpace(value))return default(T);return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(value);
}
public void Set<T>(string key, T value)
{if (value == null)Remove(key);else_session.SetString(key, Newtonsoft.Json.JsonConvert.SerializeObject(value));
}
public void Remove(params string[] keys)
{if (keys.IsNullOrEmpty())return;foreach (string key in keys)_session.Remove(key);
}
很基本。在该Get方法中,我们从会话中获取字符串值,如果该字符串值不存在,则返回我们类型的默认值。否则,我们将从JSON反序列化为该类型。Set如果传递null或将JSON序列化到会话,我们的方法将删除该值。
我们的Remove方法只是要求会话删除一个值。您不必先检查它是否存在,那样的会话很友好。它确实引用了我添加的扩展方法IsNullOrEmpty,该方法只是检查IEnumerable(在本例中为字符串数组)中是否包含任何内容。
整合代码
打开HomeController.cs并对其进行修改,如下所示:
public class HomeController : Controller
{private const string c_CONTRIVEDSESSIONKEY = "contrived";private const string c_NAMESESSIONKEY = "basicname";private readonly ISessionService _sessionService;public HomeController(ISessionService sessionService){_sessionService = sessionService;}public IActionResult Index(){var name = _sessionService.Get<string>(c_NAMESESSIONKEY);var contrived = _sessionService.Get<ContrivedValues>("contrived") ?? new ContrivedValues { Name = "Guest" };var viewModel = new HomeViewModel{Name = name,Contrived = contrived};return View(viewModel);}[HttpPost]public IActionResult PostBasic(NameRequest request){_sessionService.Set(c_NAMESESSIONKEY, request.Name);return RedirectToAction(nameof(Index));}[HttpPost]public IActionResult PostContrived(ContrivedValues request){_sessionService.Set(c_CONTRIVEDSESSIONKEY, request);return RedirectToAction(nameof(Index));}public IActionResult DeleteContrived(){_sessionService.Remove(c_CONTRIVEDSESSIONKEY);return RedirectToAction(nameof(Index));}public IActionResult Privacy(){return View();}[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]public IActionResult Error(){return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });}
}
在上面的代码中我们注入我们的新ISessionService进HomeController,然后建立一些非常蹩脚的,以及如何与它进行交互的例子。HomeController:Index例如,该方法将尝试从会话中获取字符串值以及一个实例(ContrivedValues如果找不到)或默认实例。我们将它们包装到viewModel中,然后传递到视图中。
HomeController:PostBasic操作方法接受一个NameRequest岗位价值,并将其设置到会话中。PostContrived接一个ContrivedValues帖子并设置它。DeleteContrived将删除我们的会话对象ContrivedValues。
视图模型
显然,以上内容尚未编译。我们需要添加一些模型。进入“模型”文件夹,然后将以下代码添加到新文件中HomeViewModel.cs。
public class HomeViewModel
{public string Name { get; set; }public ContrivedValues Contrived { get; set; }
}
public class NameRequest
{public string Name { get; set; }
}
public class ContrivedValues
{public int? Age { get; set; }public string Name { get; set; }
}
Index.cshtml(视图/主页) 最后,我们需要更新视图以处理该演示。
@model SessionManagerDemo.Models.HomeViewModel
@{ViewData["Title"] = "Home Page";
}
<div class="row"><div class="col-md-3"><h2>Basic Cookie</h2>@if (!string.IsNullOrWhiteSpace(Model.Name)){<p>Your name is: @Model.Name</p>}<form action="@Url.Action("PostBasic")" method="post"><div class="form-group"><label for="name">Name: </label><input type="text" class="form-control" name="name" placeholder="Name" /></div><button type="submit" class="btn btn-primary">Submit</button></form></div><div class="col-md-3"><h2>Cookie w/Default</h2><p>Current values:</p><ul><li>Age: @Model.Contrived.Age</li><li>Name: @Model.Contrived.Name</li></ul><form action="@Url.Action("PostContrived")" method="post"><div class="form-group"><label for="name">Name: </label><input type="text" class="form-control" name="name" placeholder="Name" /></div><div class="form-group"><label for="name">Age: </label><input type="number" class="form-control" name="age" placeholder="Age" /></div><button type="submit" class="btn btn-primary">Submit</button><!-- yeah yeah, I know... this is naughty --><a href="@Url.Action("DeleteContrived")" class="btn btn-danger">Delete</a></form></div>
</div>
单元测试
我不会在此处粘贴单元测试,但是GitHub代码中[5]有一些单元测试也可以体现其功能。ISession在这种情况下,替换的确很有趣,因为我们使用的是ISession.GetString,它实际上是包装ISession.TryGetValue调用的扩展方法。随意看看,看看我是如何做到这一点的。
结论
DotNetCore Web应用程序中的会话管理非常简单。我们只需要注册会话中间件和会话服务,但是它本身是非常有限的。添加基于通用的包装器可以使我们对过程有更多的控制和灵活性。对于除基本以外的任何功能,您都希望使用某种会话状态服务器,而不是使用此示例所示的内存中状态。
今天发布的所有代码都可以在我的GitHub上找到[6]。
References
[1]
查看原文: https://www.seeleycoder.com/blog/session-management-dotnetcore/#more-413[2]
DotNetCore Web应用程序中的Cookie管理,: https://www.seeleycoder.com/blog/cookie-management-asp-net-core/[3]
我的GitHub上找到: https://github.com/fooberichu150/SessionService[4]
Cookie管理方面: https://www.seeleycoder.com/blog/cookie-management-asp-net-core/[5]
GitHub代码中: https://github.com/fooberichu150/SessionService[6]
我的GitHub上找到: https://github.com/fooberichu150/SessionService