セッションとRESTが正確に連動していないことに気付きましたが、新しいWeb APIを使用してセッション状態にアクセスすることはできませんか?HttpContext.Current.Session
常にnullです。
セッションとRESTが正確に連動していないことに気付きましたが、新しいWeb APIを使用してセッション状態にアクセスすることはできませんか?HttpContext.Current.Session
常にnullです。
回答:
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);
}
}
このソリューションには、AJAX呼び出しを行うためにJavaScriptでベースURLをフェッチできるという追加のボーナスがあります。
<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
上記を実行しますが、代わりにRouteCollectionを取得するようにWebApiConfig.Register関数を変更します。
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);
ドットネットコア
Microsoft.AspNetCore.Session NuGetパッケージを追加して、次のコードを変更します。
コールAddDistributedMemoryCacheとAddSession機能ConfigureServices内のサービスオブジェクトのメソッドを:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
services.AddDistributedMemoryCache();
services.AddSession();
そして、Configure関数にUseSessionへの呼び出しを追加します。
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc();
コントローラ内で、先頭にusingステートメントを追加します。
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
ドットネットコア内のセッションデータへのアクセスに関する詳細については、https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-stateをご覧ください。
パフォーマンスの懸念
パフォーマンスに関する下記のSimon Weaverの回答を読んでください。WebApiプロジェクト内のセッションデータにアクセスしている場合は、パフォーマンスに非常に深刻な影響を与える可能性があります。ASP.NETが同時リクエストに200ミリ秒の遅延を強制するのを見ました。これは、多くの同時リクエストがある場合に、合計して悲惨になる可能性があります。
セキュリティ上の懸念
ユーザーごとにリソースをロックダウンしていることを確認してください。認証されたユーザーは、アクセスできないWebApiからデータを取得できません。
ASP.NET Web APIでの認証と承認に関するMicrosoftの記事をお読みください-https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
Cross-Site Request Forgeryハック攻撃の回避に関する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でSessionを使用してはならない理由は、よく見逃されがちです。
セッションの使用中にASP.NETが機能する方法は、単一のクライアントから受信したすべての要求をシリアル化することです。ここで私はオブジェクトのシリアル化について話しているのではなく、受け取った順に実行し、それぞれが完了するのを待ってから次の実行を開始します。これは、2つのリクエストがそれぞれセッションに同時にアクセスしようとした場合に、厄介なスレッド/競合状態を回避するためです。
同時リクエストとセッション状態
ASP.NETセッション状態へのアクセスはセッションごとに排他的です。つまり、2人の異なるユーザーが同時に要求を行うと、別々の各セッションへのアクセスが同時に許可されます。ただし、同じセッションに対して(同じSessionID値を使用して)2つの同時要求が行われた場合、最初の要求はセッション情報への排他的アクセスを取得します。2番目の要求は、最初の要求が完了した後にのみ実行されます。(最初の要求がロックのタイムアウトを超えたために情報の排他ロックが解放された場合、2番目のセッションもアクセスできます。)@ PageディレクティブのEnableSessionState値がReadOnlyに設定されている場合、読み取り専用の要求セッション情報によって、セッションデータが排他的にロックされることはありません。ただし、セッションデータの読み取り専用要求は、セッションデータの読み取り/書き込み要求によって設定されたロックがクリアされるまで待機する必要がある場合があります。
では、これはWeb APIにとって何を意味するのでしょうか。多くのAJAXリクエストを実行しているアプリケーションがある場合、一度に実行できるのは1つだけです。遅いリクエストがある場合は、完了するまでそのクライアントからの他のすべてをブロックします。一部のアプリケーションでは、これによりパフォーマンスが著しく遅くなる可能性があります。
したがって、ユーザーセッションから何かが絶対に必要な場合は、おそらくMVCコントローラーを使用し、WebApiで有効にすることによる不必要なパフォーマンスの低下を回避する必要があります。
Thread.Sleep(5000)
WebAPIメソッドを入力してセッションを有効にするだけで、これを自分で簡単にテストできます。リクエストを5回実行すると、完了するまでに合計25秒かかります。セッションがない場合、合計で5秒強かかります。
(これと同じ理由がSignalRにも当てはまります)。
そうですね、RESTはステートレスです。セッションを使用する場合、処理はステートフルになり、後続のリクエストは(セッションからの)状態を使用できます。
セッションを元に戻すには、状態を関連付けるためのキーを提供する必要があります。通常のasp.netアプリケーションでは、そのキーはcookie(cookie-sessions)またはurlパラメーター(cookielessセッション)を使用して提供されます。
残りのセッションを忘れるセッションが必要な場合、セッションはRESTベースの設計では無関係です。検証のためのセッションが必要な場合は、トークンを使用するか、IPアドレスで認証します。
マーク、オタクのMVCサンプルを確認すると、ロジックはほとんど同じです。
Cookieを取得して現在のセッションで設定するだけです。
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
nerddinnerプロジェクトから借用できる「SampleIdentity」クラスを定義する必要があります。
問題を修正するには:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Global.asax.cs
最後の1つは現在機能していません。これを1つ選択してください。
App_StartのWebApiConfig.cs内
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);
}
4番目: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呼び出しに固執できるカスタムフィルター属性を作成することもできます。その後、HttpContext.Current.Session ["SomeValue"]を介して通常行うように、api呼び出しでセッションを使用できます。
/// <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アプローチに従い、実際にセッションCookieがリクエストに存在するときにセッションを利用できました。不足している部分は、セッションCookieがクライアントに初めて送信される方法ですか?
HttpSessionStateの可用性を有効にするだけでなく、新しいセッションが作成されたときにCookieをクライアントに送信するHttpModuleを作成しました。
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);
}
}
はい、セッションはRest APIと連動しません。また、このプラクティスを回避する必要があります。しかし、要件ごとに、すべての要求でクライアントサーバーが状態またはデータを交換または維持できるように、セッションを何らかの方法で維持する必要があります。したがって、RESTプロトコルを壊すことなくこれを実現する最良の方法は、JWTのようなトークンを介して通信することです。
基本に戻り、単純にして、APIに渡す非表示のhtml値にセッション値を保存しませんか?
コントローラ
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
$(document).ready(function(){
var sessionValue = $('#hBlah').val();
alert(sessionValue);
/* Now call your API with the session variable */}
}
[SessionState(SessionStateBehavior.Required)]
上のApiController
トリック(または行い.ReadOnly
、適切な場合)。