ฉันรู้ว่าเซสชันและส่วนที่เหลือไม่ได้จับมือกัน แต่ไม่สามารถเข้าถึงสถานะเซสชันโดยใช้ Web API ใหม่ได้หรือไม่ HttpContext.Current.Session
เป็นโมฆะเสมอ
ฉันรู้ว่าเซสชันและส่วนที่เหลือไม่ได้จับมือกัน แต่ไม่สามารถเข้าถึงสถานะเซสชันโดยใช้ Web API ใหม่ได้หรือไม่ HttpContext.Current.Session
เป็นโมฆะเสมอ
คำตอบ:
MVC
สำหรับโครงการ MVC ทำการเปลี่ยนแปลงต่อไปนี้ (คำตอบของ WebForms และ Dot Net Core ด้านล่าง):
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);
}
}
โซลูชันนี้มีโบนัสเพิ่มที่เราสามารถดึง URL พื้นฐานใน javascript สำหรับการโทร AJAX:
<body>
@RenderBody()
<script type="text/javascript">
var apiBaseUrl = '@Url.Content(ProjectNameSpace.WebApiConfig.UrlPrefixRelative)';
</script>
@RenderSection("scripts", required: false)
จากนั้นภายในไฟล์ / รหัส Javascript ของเราเราสามารถทำการเรียก webapi ที่สามารถเข้าถึงเซสชันได้:
$.getJSON(apiBaseUrl + '/MyApi')
.done(function (data) {
alert('session data received: ' + data.whatever);
})
);
WebForms
ทำตามข้างต้น แต่เปลี่ยนฟังก์ชั่น WebApiConfig.Register ให้เป็น RouteCollection แทน:
public static void Register(RouteCollection routes)
{
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: WebApiConfig.UrlPrefix + "/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
แล้วเรียกสิ่งต่อไปนี้ใน Application_Start:
WebApiConfig.Register(RouteTable.Routes);
Dot Net Core
เพิ่มแพ็คเกจMicrosoft.AspNetCore.Session NuGet จากนั้นทำการเปลี่ยนแปลงรหัสต่อไปนี้:
เรียกวิธีการAddDistributedMemoryCacheและAddSessionบนวัตถุบริการภายในฟังก์ชัน ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
และในฟังก์ชั่นการตั้งค่าให้เพิ่มการเรียกไปยังUseSession :
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
ภายในตัวควบคุมของคุณเพิ่มคำสั่งการใช้ที่ด้านบน:
using Microsoft.AspNetCore.Http;
และใช้วัตถุ HttpContext.Session ภายในรหัสของคุณดังนี้:
[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);
}
ตอนนี้คุณควรจะสามารถที่จะตี:
http://localhost:1234/api/session/set/thisissomedata
จากนั้นไปที่ URL นี้จะดึงออกมา:
http://localhost:1234/api/session/get
ข้อมูลเพิ่มเติมมากมายเกี่ยวกับการเข้าถึงข้อมูลเซสชันภายใน dot net core ได้ที่นี่: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
ความกังวลเกี่ยวกับประสิทธิภาพ
อ่านคำตอบของ Simon Weaver ด้านล่างเกี่ยวกับประสิทธิภาพ หากคุณเข้าถึงข้อมูลเซสชั่นภายในโครงการ WebApi อาจมีผลการปฏิบัติงานที่ร้ายแรงมาก - ฉันได้เห็น ASP.NET บังคับล่าช้า 200ms สำหรับคำขอที่เกิดขึ้นพร้อมกัน สิ่งนี้สามารถเพิ่มและกลายเป็นหายนะถ้าคุณมีคำขอพร้อมกันจำนวนมาก
ความกังวลด้านความปลอดภัย
ตรวจสอบให้แน่ใจว่าคุณกำลังล็อคทรัพยากรต่อผู้ใช้ - ผู้ใช้ที่ได้รับการรับรองความถูกต้องไม่สามารถดึงข้อมูลจาก WebApi ที่พวกเขาไม่มีสิทธิ์เข้าถึง
อ่านบทความของ Microsoft เกี่ยวกับการรับรองความถูกต้องและการอนุญาตใน ASP.NET Web API - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
อ่านบทความของ Microsoft เกี่ยวกับการหลีกเลี่ยงการโจมตีแฮกจากการปลอมแปลงคำขอ (ในระยะสั้นตรวจสอบวิธี AntiForgery.Validate) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
คุณสามารถเข้าถึงสถานะเซสชันโดยใช้ RouteHandler ที่กำหนดเอง
// 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;
}
}
พบได้ที่นี่: http://techhasnoboundary.blogspot.com/2012/03/mvc-4-web-api-access-session.html
ประสิทธิภาพ, ประสิทธิภาพ, ประสิทธิภาพ!
มีเหตุผลที่ดีมากและมักถูกมองข้ามว่าทำไมคุณไม่ควรใช้เซสชันใน WebAPI เลย
วิธี ASP.NET ทำงานเมื่อเซสชันในการใช้งานคือการเป็นอันดับคำขอทั้งหมดที่ได้รับจากลูกค้าเดียว ตอนนี้ฉันไม่ได้พูดถึงการทำให้เป็นอันดับวัตถุ - แต่ให้พวกเขาตามลำดับที่ได้รับและรอให้แต่ละให้เสร็จสมบูรณ์ก่อนที่จะใช้ต่อไป นี่คือเพื่อหลีกเลี่ยงเงื่อนไขเธรด / การแข่งขันที่น่ารังเกียจหากสองคำขอแต่ละครั้งพยายามเข้าถึงเซสชันพร้อมกัน
คำขอพร้อมกันและสถานะเซสชัน
การเข้าถึงสถานะเซสชัน ASP.NET นั้นไม่รวมต่อเซสชันซึ่งหมายความว่าหากผู้ใช้สองคนที่แตกต่างกันทำการร้องขอพร้อมกันการเข้าถึงแต่ละเซสชันที่แยกต่างหากจะได้รับพร้อมกัน อย่างไรก็ตามหากมีการร้องขอที่เกิดขึ้นพร้อมกันสองครั้งสำหรับเซสชันเดียวกัน (โดยใช้ค่า SessionID เดียวกัน) คำขอแรกจะได้รับสิทธิ์ในการเข้าถึงข้อมูลเซสชันโดยเฉพาะ การร้องขอที่สองจะดำเนินการหลังจากการร้องขอครั้งแรกเสร็จสิ้น(เซสชันที่สองยังสามารถเข้าถึงได้หากการล็อกแบบเอกสิทธิ์เฉพาะบุคคลกับข้อมูลถูกปล่อยให้เป็นอิสระเนื่องจากการร้องขอแรกเกินกว่าการหมดเวลาการล็อก) ถ้าค่า EnableSessionState ใน @ หน้าไดเรกทีฟถูกกำหนดเป็น ReadOnly คำขอสำหรับอ่านอย่างเดียว ข้อมูลเซสชันไม่ส่งผลให้มีการล็อกแบบเอกสิทธิ์เฉพาะบุคคลในข้อมูลเซสชัน อย่างไรก็ตามการร้องขอการอ่านอย่างเดียวสำหรับข้อมูลเซสชันอาจยังคงต้องรอการล็อกที่ตั้งค่าโดยการร้องขอการอ่าน - เขียนเพื่อล้างข้อมูลเซสชัน
ดังนั้นสิ่งนี้หมายความว่าสำหรับ Web API หากคุณมีแอปพลิเคชันที่เรียกใช้คำขอ AJAX จำนวนมากจะมีเพียงหนึ่งแอปเท่านั้นที่สามารถเรียกใช้ได้ในแต่ละครั้ง หากคุณมีคำขอช้าลงมันจะปิดกั้นผู้อื่นทั้งหมดจากไคลเอนต์นั้นจนกว่าจะเสร็จสมบูรณ์ ในบางแอปพลิเคชันอาจทำให้ประสิทธิภาพการทำงานช้าลงอย่างเห็นได้ชัด
ดังนั้นคุณควรใช้คอนโทรลเลอร์ MVC หากคุณต้องการบางสิ่งบางอย่างจากเซสชันผู้ใช้และหลีกเลี่ยงการปรับประสิทธิภาพที่ไม่จำเป็นในการเปิดใช้งานสำหรับ WebApi
คุณสามารถทดสอบสิ่งนี้ได้ด้วยตัวเองง่ายๆเพียงแค่ใส่Thread.Sleep(5000)
เมธอด WebAPI และเปิดใช้งานเซสชัน เรียกใช้ 5 คำขอไปและพวกเขาจะใช้เวลาทั้งหมด 25 วินาทีให้เสร็จสมบูรณ์ หากไม่มีเซสชันพวกเขาจะใช้เวลาทั้งหมดเพียง 5 วินาที
(เหตุผลเดียวกันนี้นำไปใช้กับ SignalR)
คุณพูดถูกแล้ว REST นั้นไร้สัญชาติ หากคุณใช้เซสชันการประมวลผลจะกลายเป็นคำร้องขอที่ตามมาจะสามารถใช้สถานะ (จากเซสชัน)
เพื่อให้เซสชันได้รับการคืนสถานะคุณจะต้องใส่รหัสเพื่อเชื่อมโยงรัฐ ในแอปพลิเคชัน asp.net ปกติที่ให้คีย์โดยใช้คุกกี้ (เซสชันคุกกี้) หรือพารามิเตอร์ url (เซสชันที่ไม่มีการปรุงอาหาร)
หากคุณต้องการเซสชั่นลืมส่วนที่เหลือเซสชั่นจะไม่เกี่ยวข้องในการออกแบบตามส่วนที่เหลือ หากคุณต้องการเซสชันสำหรับการตรวจสอบความถูกต้องให้ใช้โทเค็นหรืออนุญาตตามที่อยู่ IP
ทำเครื่องหมายถ้าคุณตรวจสอบตัวอย่าง nerddinner MVCตรรกะนั้นค่อนข้างเหมือนกัน
คุณจะต้องดึงคุกกี้และตั้งค่าในเซสชันปัจจุบันเท่านั้น
Global.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
คุณจะต้องกำหนดของคุณ "SampleIdentity" ชั้นซึ่งคุณสามารถยืมจากโครงการ nerddinner
ในการแก้ไขปัญหา:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
ใน Global.asax.cs
อันสุดท้ายไม่ทำงานตอนนี้เอาอันนี้ใช้ได้กับฉัน
ใน WebApiConfig.cs ที่ 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
);
Global.asax
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
private static bool IsWebApiRequest()
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(_WebApiExecutionPath);
}
ที่สี่ที่นี่: http://forums.asp.net/t/1773026.aspx/1
ทำตามคำตอบของ LachlanB หาก ApiController ของคุณไม่ได้อยู่ในไดเรกทอรีเฉพาะ (เช่น / api) คุณสามารถทดสอบคำขอโดยใช้ RouteTable.Routes.GetRouteData แทนเช่น:
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);
}
ฉันมีปัญหาเดียวกันนี้ใน asp.net mvc ฉันแก้ไขได้โดยใส่วิธีนี้ในตัวควบคุม api ฐานของฉันที่ตัวควบคุม api ของฉันสืบทอดมาจาก:
/// <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;
}
จากนั้นในการเรียก API ของคุณที่คุณต้องการเข้าถึงเซสชั่นที่คุณทำ:
HttpContextWrapper httpContextWrapper = GetHttpContextWrapper();
var someVariableFromSession = httpContextWrapper.Session["SomeSessionValue"];
ฉันมีสิ่งนี้ในไฟล์ Global.asax.cs เหมือนคนอื่น ๆ ที่โพสต์ไม่แน่ใจว่าคุณยังต้องการมันด้วยวิธีการข้างต้น แต่ที่นี่เป็นเพียงในกรณี:
/// <summary>
/// The following method makes Session available.
/// </summary>
protected void Application_PostAuthorizeRequest()
{
if (HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith("~/api"))
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
คุณยังสามารถสร้างแอตทริบิวต์ตัวกรองแบบกำหนดเองที่คุณสามารถใช้ในการเรียก API ของคุณที่คุณต้องการจากนั้นคุณสามารถใช้เซสชันในการเรียก API ของคุณเหมือนปกติผ่าน HttpContext.Current.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;
}
}
}
}
หวังว่านี่จะช่วยได้
ฉันได้ปฏิบัติตามวิธีการของ @LachlanB และแน่นอนว่าเซสชันนั้นสามารถใช้งานได้เมื่อมีการใช้คุกกี้เซสชันตามคำขอ ส่วนที่ขาดหายไปคือวิธีที่คุกกี้เซสชันถูกส่งไปยังลูกค้าเป็นครั้งแรก
ฉันสร้าง HttpModule ซึ่งไม่เพียง แต่เปิดใช้งานความพร้อมใช้งาน HttpSessionState แต่ยังส่งคุกกี้ไปยังไคลเอนต์เมื่อสร้างเซสชันใหม่
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);
}
}
สิ่งหนึ่งที่ต้องพูดถึงในคำตอบของ @LachlanB
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
หากคุณเว้นบรรทัด if (IsWebApiRequest())
เว็บไซต์ทั้งหมดจะมีปัญหาการโหลดหน้าช้าหากเว็บไซต์ของคุณผสมกับหน้าเว็บฟอร์ม
ใช่เซสชันไม่ได้ทำงานควบคู่กับ Rest API และเราควรหลีกเลี่ยงวิธีปฏิบัตินี้ แต่ตามความต้องการเราจำเป็นต้องรักษาเซสชั่นอย่างใดในเซิร์ฟเวอร์คำขอของลูกค้าทุกคนสามารถแลกเปลี่ยนหรือรักษาสถานะหรือข้อมูล ดังนั้นวิธีที่ดีที่สุดในการบรรลุเป้าหมายโดยไม่ทำลายโปรโตคอล REST ก็คือการสื่อสารผ่านโทเค็นอย่าง JWT
กลับไปสู่พื้นฐานทำไมไม่ให้เรียบง่ายและเก็บค่าเซสชันไว้ในค่า html ที่ซ่อนอยู่เพื่อส่งต่อไปยัง API ของคุณ
ตัวควบคุม
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" />
จาวาสคริ
$ (เอกสาร) .ready (function () {
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}
[SessionState(SessionStateBehavior.Required)]
ในการApiController
หลอกลวง (หรือ.ReadOnly
ตามความเหมาะสม)