web-dev-qa-db-ja.com

ASP.NET Web APIを使用したセッションへのアクセス

私はsessionとRESTが正確に密接に関連していないことを理解していますが、新しいWeb APIを使用してセッション状態にアクセスすることは不可能ですか? HttpContext.Current.Sessionは常にnullです。

251
Mark

MVC

MVCプロジェクトの場合は、次のように変更します(WebFormsとDot Net Coreの回答は下にあります)。

WebApiConfig.cs

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 }
        );
    }
}

Global.asax.cs

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を取得できるという追加のボーナスがあります。

_Layout.cshtml

<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);
   })
);

Webフォーム

上記を行いますが、代わりに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.SessionNuGetパッケージを追加して、次のようにコードを変更します。

Startup.cs

ConfigureServices関数内のサービスオブジェクトでAddDistributedMemoryCacheおよびAddSessionメソッドを呼び出します。

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();

SessionController.cs

コントローラ内で、先頭に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/ja-jp/aspnet/core/fundamentals/app-state

パフォーマンスの懸念

パフォーマンスについては、Simon Weaverの答えを読んでください。 WebApiプロジェクト内のセッションデータにアクセスしていると、パフォーマンスに非常に大きな影響を与える可能性があります。ASP.NETが同時リクエストに対して200msの遅延を強制しているのを見ました。同時リクエストが多数あると、これが累積して悲惨になる可能性があります。


セキュリティ上の懸念

ユーザーごとにリソースをロックダウンしていることを確認してください - 認証されたユーザーは、自分のWebApiからアクセス権のないデータを取得できないようにする必要があります。

ASP.NET Web APIでの認証と承認に関するマイクロソフトの記事を読む - https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet -web-api

クロスサイトリクエストフォージェリハック攻撃の回避に関するマイクロソフトの記事を読んでください。 (要するにAntiForgery.Validateメソッドをチェックしてください) - https://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery- csrf-attack

315
Rocklan

カスタム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

63
warrickh

WebAPIでSessionを使用しないのはなぜですか?

パフォーマンス、パフォーマンス、パフォーマンス!

WebAPIでSessionを使用してはいけない理由は非常によく、見落とされがちな理由があります。

Sessionが使用されているときにASP.NETが機能する方法は、単一のクライアントから受信したすべての要求をシリアル化することです。今、私はオブジェクトの直列化について話していません - しかし、それらを受け取った順番で実行し、それぞれが完了するのを待ってから次の実行をします。これは、2つの要求がそれぞれ同時にSessionにアクセスしようとした場合に、厄介なスレッド/競合状態を回避するためです。

同時要求とセッション状態

ASP.NETセッション状態へのアクセスはセッションごとに排他的です。つまり、2人の異なるユーザーが同時に要求を行うと、それぞれ別々のセッションへのアクセスが同時に許可されます。ただし、同じセッションに対して2つの同時要求が(同じSessionID値を使用して)行われた場合、最初の要求はセッション情報への排他的アクセスを取得します。 2番目の要求は、最初の要求が終了した後にのみ実行されます(最初の要求がロックタイムアウトを超えたために情報の排他ロックが解放された場合、2番目のセッションもアクセスできます) @ PageディレクティブでReadOnlyが設定されている場合、読み取り専用セッション情報を要求しても、セッションデータは排他的にロックされません。ただし、セッションデータに対する読み取り専用要求では、セッションデータに対する読み書き要求で設定されたロックが解除されるまで待機する必要があります。

では、これはWeb APIにとって何を意味するのでしょうか。多数のAJAX要求を実行しているアプリケーションがある場合は、一度に実行できるのは1つだけです。あなたがより遅い要求を持っているなら、それはそれが完了するまでそのクライアントからの他のすべてのものをブロックします。一部のアプリケーションでは、これは非常に顕著にパフォーマンスの低下につながる可能性があります。

そのため、ユーザーセッションから何かを絶対に必要とし、WebApiに対してそれを有効にすることによる予期しないパフォーマンスの低下を避けるためには、おそらくMVCコントローラーを使用する必要があります。

Thread.Sleep(5000)をWebAPIメソッドに入れてSessionを有効にするだけで、自分で簡単にテストすることができます。それに5つの要求を実行すればそれらは完了するのに合計25秒かかります。 Sessionがなければ、合計5秒以上かかります。

(これと同じ理由がSignalRにも当てはまります)。

44
Simon_Weaver

そうですね、RESTはステートレスです。セッションを使用すると、処理はステートフルになり、後続のリクエストは(セッションからの)ステートを使用できるようになります。

セッションを水分補給するには、状態を関連付けるためのキーを指定する必要があります。通常のasp.netアプリケーションでは、そのキーはcookie(cookie-sessions)またはurlパラメータ(cookieless sessions)を使用して提供されます。

セッションで休憩を忘れる必要がある場合は、RESTベースのデザインではセッションは関係ありません。検証にセッションが必要な場合は、トークンを使用するかIPアドレスで承認してください。

21
Nickz

Mark、あなたが 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プロジェクト から借用できます。

20
JSancho

問題を解決するには

protected void Application_PostAuthorizeRequest()
{
    System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}

global.asax.csで

12
Suresh Muttagi

最後のものは今動作していない、これを取る、それは私のために働いた。

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

10
Cruiser KID

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);
    }
8
Stumblor

私は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;
        }
      }
    }
  }

お役に立てれば。

7
Treyphor

私は@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);
    }
}
6
JCallico

@LachlanBの答えについて言及する必要があります。

protected void Application_PostAuthorizeRequest()
    {
        if (IsWebApiRequest())
        {
            HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
        }
    }

if (IsWebApiRequest())の行を省略した場合

あなたのサイトがWebフォームページと混在している場合は、サイト全体のページ読み込みが遅くなる問題があります。

3
maxisam