Tôi nhận thấy phiên và REST không thực sự đi đôi với nhau nhưng không thể truy cập trạng thái phiên bằng API Web mới? HttpContext.Current.Session
luôn luôn là null.
Tôi nhận thấy phiên và REST không thực sự đi đôi với nhau nhưng không thể truy cập trạng thái phiên bằng API Web mới? HttpContext.Current.Session
luôn luôn là null.
Câu trả lời:
MVC
Đối với một dự án MVC, hãy thực hiện các thay đổi sau (WebForms và Dot Net Core trả lời bên dưới):
public static class WebApiConfig
{
public static string UrlPrefix { get { return "api"; } }
public static string UrlPrefixRelative { get { return "~/api"; } }
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
public class MvcApplication : System.Web.HttpApplication
{
...
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
private bool IsWebApiRequest()
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(WebApiConfig.UrlPrefixRelative);
}
}
Giải pháp này có phần thưởng bổ sung mà chúng tôi có thể tìm nạp URL cơ sở trong javascript để thực hiện các cuộc gọi AJAX:
<body>
@RenderBody()
<script type="text/javascript">
var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
</script>
@RenderSection("scripts", required: false)
và sau đó trong các tệp / mã Javascript của chúng tôi, chúng tôi có thể thực hiện các cuộc gọi webapi có thể truy cập phiên:
$.getJSON(apiBaseUrl + '/MyApi')
.done(function (data) {
alert('session data received: ' + data.whatever);
})
);
WebForms
Thực hiện như trên nhưng thay đổi hàm WebApiConfig.Register để lấy RouteCollection thay thế:
public static void Register(RouteCollection routes)
{
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Và sau đó gọi như sau trong Application_Start:
WebApiConfig.Register(RouteTable.Routes);
Lõi lưới
Thêm gói Microsoft.AspNetCore.Session NuGet và sau đó thực hiện các thay đổi mã sau:
Gọi AddDistributedMemoryCache và AddSession phương pháp trên các dịch vụ đối tượng trong ConfigureServices chức năng:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
và trong chức năng Cấu hình, thêm một cuộc gọi đến UseSession :
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
Trong bộ điều khiển của bạn, thêm một câu lệnh sử dụng ở trên cùng:
using Microsoft.AspNetCore.Http;
và sau đó sử dụng đối tượng HttpContext.Session trong mã của bạn như vậy:
[HttpGet("set/{data}")]
public IActionResult setsession(string data)
{
HttpContext.Session.SetString("keyname", data);
return Ok("session data set");
}
[HttpGet("get")]
public IActionResult getsessiondata()
{
var sessionData = HttpContext.Session.GetString("keyname");
return Ok(sessionData);
}
bây giờ bạn có thể đánh:
http://localhost:1234/api/session/set/thisissomedata
và sau đó truy cập URL này sẽ kéo nó ra:
http://localhost:1234/api/session/get
Nhiều thông tin hơn về việc truy cập dữ liệu phiên trong lõi mạng chấm tại đây: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
Mối quan tâm về hiệu suất
Đọc câu trả lời của Simon Weaver dưới đây về hiệu suất. Nếu bạn đang truy cập dữ liệu phiên bên trong dự án WebApi, nó có thể có hậu quả hiệu suất rất nghiêm trọng - Tôi đã thấy ASP.NET thực thi độ trễ 200ms cho các yêu cầu đồng thời. Điều này có thể cộng lại và trở thành thảm họa nếu bạn có nhiều yêu cầu đồng thời.
Quan ngại về an ninh
Đảm bảo rằng bạn đang khóa tài nguyên trên mỗi người dùng - một người dùng được xác thực sẽ không thể truy xuất dữ liệu từ WebApi mà họ không có quyền truy cập.
Đọc bài viết của Microsoft về Xác thực và Ủy quyền trong API Web ASP.NET - https://www.asp.net/web-api/overview/security/authentication-and-masterization-in-aspnet-web-api
Đọc bài viết của Microsoft về việc tránh các cuộc tấn công hack Yêu cầu chéo trang web. (Tóm lại, hãy kiểm tra phương pháp AntiForgery.Validate) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
Bạn có thể truy cập trạng thái phiên bằng cách sử dụng RouteHandler tùy chỉnh.
// In global.asax
public class MvcApp : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
var route = routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
route.RouteHandler = new MyHttpControllerRouteHandler();
}
}
// Create two new classes
public class MyHttpControllerHandler
: HttpControllerHandler, IRequiresSessionState
{
public MyHttpControllerHandler(RouteData routeData) : base(routeData)
{ }
}
public class MyHttpControllerRouteHandler : HttpControllerRouteHandler
{
protected override IHttpHandler GetHttpHandler(
RequestContext requestContext)
{
return new MyHttpControllerHandler(requestContext.RouteData);
}
}
// Now Session is visible in your Web API
public class ValuesController : ApiController
{
public string Get(string input)
{
var session = HttpContext.Current.Session;
if (session != null)
{
if (session["Time"] == null)
session["Time"] = DateTime.Now;
return "Session Time: " + session["Time"] + input;
}
return "Session is not availabe" + input;
}
}
Tìm thấy ở đây: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
Hiệu suất, hiệu suất, hiệu suất!
Có một lý do rất hay và thường bị bỏ qua tại sao bạn không nên sử dụng Phiên trong WebAPI.
Cách ASP.NET hoạt động khi Phiên được sử dụng là để tuần tự hóa tất cả các yêu cầu nhận được từ một khách hàng . Bây giờ tôi không nói về tuần tự hóa đối tượng - nhưng chạy chúng theo thứ tự nhận được và chờ đợi từng cái hoàn thành trước khi chạy tiếp theo. Điều này là để tránh các điều kiện luồng / cuộc đua khó chịu nếu hai yêu cầu mỗi lần thử truy cập Phiên.
Yêu cầu đồng thời và trạng thái phiên
Quyền truy cập vào trạng thái phiên ASP.NET là độc quyền cho mỗi phiên, có nghĩa là nếu hai người dùng khác nhau thực hiện các yêu cầu đồng thời, quyền truy cập vào từng phiên riêng biệt được cấp đồng thời. Tuy nhiên, nếu hai yêu cầu đồng thời được thực hiện cho cùng một phiên (bằng cách sử dụng cùng một giá trị SessionID), yêu cầu đầu tiên sẽ có quyền truy cập độc quyền vào thông tin phiên. Yêu cầu thứ hai chỉ thực hiện sau khi yêu cầu đầu tiên kết thúc.(Phiên thứ hai cũng có thể có quyền truy cập nếu khóa độc quyền trên thông tin được giải phóng vì yêu cầu đầu tiên vượt quá thời gian khóa.) Nếu giá trị EnableSessionState trong chỉ thị @ Trang được đặt thành ReadOnly, yêu cầu chỉ đọc thông tin phiên không dẫn đến khóa độc quyền trên dữ liệu phiên. Tuy nhiên, các yêu cầu chỉ đọc cho dữ liệu phiên có thể vẫn phải chờ khóa được đặt bởi yêu cầu đọc ghi để dữ liệu phiên xóa.
Vậy điều này có ý nghĩa gì với API Web? Nếu bạn có một ứng dụng chạy nhiều yêu cầu AJAX thì chỉ có MỘT là có thể chạy cùng một lúc. Nếu bạn có một yêu cầu chậm hơn thì nó sẽ chặn tất cả những người khác từ khách hàng đó cho đến khi hoàn thành. Trong một số ứng dụng, điều này có thể dẫn đến hiệu suất chậm chạp đáng chú ý.
Vì vậy, có lẽ bạn nên sử dụng bộ điều khiển MVC nếu bạn thực sự cần thứ gì đó từ phiên người dùng và tránh hình phạt hiệu năng không cần thiết khi kích hoạt nó cho WebApi.
Bạn có thể dễ dàng tự kiểm tra điều này bằng cách chỉ cần đưa Thread.Sleep(5000)
vào một phương thức WebAPI và kích hoạt Phiên. Chạy 5 yêu cầu đến nó và họ sẽ mất tổng cộng 25 giây để hoàn thành. Nếu không có Phiên, họ sẽ mất tổng cộng chỉ hơn 5 giây.
(Lý do tương tự này áp dụng cho SignalR).
Vâng, bạn đúng, REST là không quốc tịch. Nếu bạn sử dụng một phiên, quá trình xử lý sẽ trở nên trạng thái, các yêu cầu tiếp theo sẽ có thể sử dụng trạng thái (từ một phiên).
Để phiên được bù nước, bạn sẽ cần cung cấp khóa để liên kết trạng thái. Trong một ứng dụng asp.net bình thường, khóa đó được cung cấp bằng cách sử dụng cookie (phiên cookie) hoặc tham số url (phiên không nấu ăn).
Nếu bạn cần một phiên quên phần còn lại, các phiên không liên quan trong các thiết kế dựa trên REST. Nếu bạn cần một phiên để xác thực thì hãy sử dụng mã thông báo hoặc ủy quyền theo địa chỉ IP.
Đánh dấu, nếu bạn kiểm tra ví dụ MVC nerddinner thì logic khá giống nhau.
Bạn chỉ cần truy xuất cookie và đặt nó trong phiên hiện tại.
Toàn cầu.asax.cs
public override void Init()
{
this.AuthenticateRequest += new EventHandler(WebApiApplication_AuthenticateRequest);
base.Init();
}
void WebApiApplication_AuthenticateRequest(object sender, EventArgs e)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
SampleIdentity id = new SampleIdentity(ticket);
GenericPrincipal prin = new GenericPrincipal(id, null);
HttpContext.Current.User = prin;
}
enter code here
Bạn sẽ phải xác định lớp "SampleIdentity" mà bạn có thể mượn từ dự án nerddinner .
Để khắc phục sự cố:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
trong Global.asax.cs
Cái cuối cùng không hoạt động bây giờ, hãy lấy cái này, nó làm việc cho tôi.
trong WebApiConfig.cs tại App_Start
public static string _WebApiExecutionPath = "api";
public static void Register(HttpConfiguration config)
{
var basicRouteTemplate = string.Format("{0}/{1}", _WebApiExecutionPath, "{controller}");
// Controller Only
// To handle routes like `/api/VTRouting`
config.Routes.MapHttpRoute(
name: "ControllerOnly",
routeTemplate: basicRouteTemplate//"{0}/{controller}"
);
// Controller with ID
// To handle routes like `/api/VTRouting/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: string.Format ("{0}/{1}", basicRouteTemplate, "{id}"),
defaults: null,
constraints: new { id = @"^\d+$" } // Only integers
);
Toàn cầu
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
private static bool IsWebApiRequest()
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(_WebApiExecutionPath);
}
thứ tư tại đây: http://forums.asp.net/t/1773026.aspx/1
Theo dõi câu trả lời của LachlanB, nếu ApiControll của bạn không ngồi trong một thư mục cụ thể (như / api), bạn có thể kiểm tra yêu cầu bằng RouteTable.Routes.GetRouteData, ví dụ:
protected void Application_PostAuthorizeRequest()
{
// WebApi SessionState
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(HttpContext.Current));
if (routeData != null && routeData.RouteHandler is HttpControllerRouteHandler)
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
Tôi gặp vấn đề tương tự trong asp.net mvc, tôi đã khắc phục bằng cách đưa phương thức này vào bộ điều khiển api cơ sở mà tất cả các bộ điều khiển api của tôi thừa hưởng từ:
/// <summary>
/// Get the session from HttpContext.Current, if that is null try to get it from the Request properties.
/// </summary>
/// <returns></returns>
protected HttpContextWrapper GetHttpContextWrapper()
{
HttpContextWrapper httpContextWrapper = null;
if (HttpContext.Current != null)
{
httpContextWrapper = new HttpContextWrapper(HttpContext.Current);
}
else if (Request.Properties.ContainsKey("MS_HttpContext"))
{
httpContextWrapper = (HttpContextWrapper)Request.Properties["MS_HttpContext"];
}
return httpContextWrapper;
}
Sau đó, trong cuộc gọi api của bạn mà bạn muốn truy cập vào phiên bạn vừa làm:
HttpContextWrapper httpContextWrapper = GetHttpContextWrapper();
var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
Tôi cũng có tệp này trong tệp Global.asax.cs của mình như những người khác đã đăng, không chắc bạn có còn cần nó bằng phương pháp trên không, nhưng đây chỉ là trong trường hợp:
/// <summary>
/// The following method makes Session available.
/// </summary>
protected void Application_PostAuthorizeRequest()
{
if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api"))
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Bạn cũng có thể tạo một thuộc tính bộ lọc tùy chỉnh mà bạn có thể dính vào các cuộc gọi api mà bạn cần phiên, sau đó bạn có thể sử dụng phiên trong cuộc gọi api của mình như bạn thường làm thông qua HttpContext.Cản.Session ["someValue"]:
/// <summary>
/// Filter that gets session context from request if HttpContext.Current is null.
/// </summary>
public class RequireSessionAttribute : ActionFilterAttribute
{
/// <summary>
/// Runs before action
/// </summary>
/// <param name="actionContext"></param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (HttpContext.Current == null)
{
if (actionContext.Request.Properties.ContainsKey("MS_HttpContext"))
{
HttpContext.Current = ((HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).ApplicationInstance.Context;
}
}
}
}
Hi vọng điêu nay co ich.
Tôi đã làm theo cách tiếp cận @LachlanB và thực sự phiên đã có sẵn khi cookie phiên có mặt theo yêu cầu. Phần còn thiếu là làm thế nào cookie phiên được gửi cho khách hàng lần đầu tiên?
Tôi đã tạo một HTTPModule, nó không chỉ cho phép tính khả dụng của httpSessionState mà còn gửi cookie cho khách hàng khi một phiên mới được tạo.
public class WebApiSessionModule : IHttpModule
{
private static readonly string SessionStateCookieName = "ASP.NET_SessionId";
public void Init(HttpApplication context)
{
context.PostAuthorizeRequest += this.OnPostAuthorizeRequest;
context.PostRequestHandlerExecute += this.PostRequestHandlerExecute;
}
public void Dispose()
{
}
protected virtual void OnPostAuthorizeRequest(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
if (this.IsWebApiRequest(context))
{
context.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
protected virtual void PostRequestHandlerExecute(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
if (this.IsWebApiRequest(context))
{
this.AddSessionCookieToResponseIfNeeded(context);
}
}
protected virtual void AddSessionCookieToResponseIfNeeded(HttpContext context)
{
HttpSessionState session = context.Session;
if (session == null)
{
// session not available
return;
}
if (!session.IsNewSession)
{
// it's safe to assume that the cookie was
// received as part of the request so there is
// no need to set it
return;
}
string cookieName = GetSessionCookieName();
HttpCookie cookie = context.Response.Cookies[cookieName];
if (cookie == null || cookie.Value != session.SessionID)
{
context.Response.Cookies.Remove(cookieName);
context.Response.Cookies.Add(new HttpCookie(cookieName, session.SessionID));
}
}
protected virtual string GetSessionCookieName()
{
var sessionStateSection = (SessionStateSection)ConfigurationManager.GetSection("system.web/sessionState");
return sessionStateSection != null && !string.IsNullOrWhiteSpace(sessionStateSection.CookieName) ? sessionStateSection.CookieName : SessionStateCookieName;
}
protected virtual bool IsWebApiRequest(HttpContext context)
{
string requestPath = context.Request.AppRelativeCurrentExecutionFilePath;
if (requestPath == null)
{
return false;
}
return requestPath.StartsWith(WebApiConfig.UrlPrefixRelative, StringComparison.InvariantCultureIgnoreCase);
}
}
một điều cần đề cập đến câu trả lời của @LachlanB.
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Nếu bạn bỏ qua dòng if (IsWebApiRequest())
Toàn bộ trang web sẽ có vấn đề tải trang chậm nếu trang web của bạn được trộn với các trang mẫu web.
Có, phiên không đi đôi với Rest API và chúng ta cũng nên tránh thực hành này. Nhưng theo yêu cầu, chúng tôi cần duy trì phiên bằng cách nào đó để trong mọi máy chủ khách hàng yêu cầu có thể trao đổi hoặc duy trì trạng thái hoặc dữ liệu. Vì vậy, cách tốt nhất để đạt được điều này mà không phá vỡ các giao thức REST là giao tiếp thông qua mã thông báo như JWT.
Quay trở lại vấn đề cơ bản tại sao không giữ nó đơn giản và lưu trữ giá trị Phiên trong một giá trị html ẩn để chuyển đến API của bạn?
Bộ điều khiển
public ActionResult Index()
{
Session["Blah"] = 609;
YourObject yourObject = new YourObject();
yourObject.SessionValue = int.Parse(Session["Blah"].ToString());
return View(yourObject);
}
cshtml
@model YourObject
@{
var sessionValue = Model.SessionValue;
}
<input type="hidden" value="@sessionValue" id="hBlah" />
Javascript
$ (tài liệu). yet (function () {
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}
[SessionState(SessionStateBehavior.Required)]
trênApiController
lừa đảo (hoặc.ReadOnly
khi thích hợp).