ASP.NET Core의 세션 및 상태 관리

작성자: Rick Anderson, Kirk Larkin, Diana LaRose

HTTP는 상태 비저장 프로토콜입니다. 기본적으로 HTTP 요청은 사용자 값을 유지하지 않는 독립적인 메시지입니다. 이 문서에서는 요청 간에 사용자 데이터를 유지하는 여러 가지 방법을 설명합니다.

상태 관리

상태는 여러 방법을 사용하여 저장할 수 있습니다. 각 방법은 이 항목 뒷부분에서 설명합니다.

스토리지 접근 방식 스토리지 메커니즘
Cookie HTTP cookie. 서버 쪽 앱 코드를 사용하여 저장된 데이터를 포함할 수 있습니다.
세션 상태 HTTP cookie 및 서버 쪽 앱 코드
TempData HTTP cookie 또는 세션 상태
쿼리 문자열 HTTP 쿼리 문자열
숨겨진 필드 HTTP 양식 필드
HttpContext.Items 서버 쪽 앱 코드
캐시 서버 쪽 앱 코드

SignalR/Blazor Server 및 HTTP 컨텍스트 기반 상태 관리

SignalR 앱은 안정적인 HTTP 컨텍스트를 사용하여 정보를 저장하는 세션 상태 및 기타 상태 관리 방법을 사용하면 안 됩니다. SignalR앱은 허브에Context.Items 연결당 상태를 저장할 수 있습니다. 앱에 대한 Blazor Server 자세한 내용 및 대체 상태 관리 방법은 상태 관리 ASP.NET Core Blazor 참조하세요.

Cookies

Cookie는 요청 간에 데이터를 저장합니다. cookie는 모든 요청과 함께 전송되므로 해당 크기는 최소로 유지되어야 합니다. 이상적으로 식별자만 앱에 저장된 데이터와 함께 cookie에 저장되어야 합니다. 대부분의 브라우저는 cookie 크기를 4,096바이트로 제한합니다. 제한된 수의 cookie만 각 도메인에 사용할 수 있습니다.

cookie는 변조될 수 있기 때문에 앱에서 유효성을 검사해야 합니다. Cookie는 사용자가 삭제할 수 있으며 클라이언트에서 만료됩니다. 그러나 cookie는 일반적으로 클라이언트에서 데이터 지속성의 가장 안정적인 형태입니다.

Cookie는 종종 개인 설정에 사용됩니다. 여기서 콘텐츠는 알려진 사용자에 대해 사용자 지정됩니다. 사용자만 식별되고 대부분의 경우 인증되지 않습니다. cookie는 사용자 이름, 계정 이름 또는 고유한 사용자 ID(예: GUID)를 저장할 수 있습니다. cookie를 사용하여 선호하는 웹 사이트 배경색과 같은 사용자의 맞춤형 설정에 액세스할 수 있습니다.

cookie를 발급하고 개인 정보 보호 문제를 처리하는 경우 유럽 연합 GDPR(일반 데이터 보호 규정)을 참조하세요. 자세한 내용은 ASP.NET Core의 GDPR(일반 데이터 보호 규정) 지원을 참조하세요.

세션 상태

세션 상태는 사용자가 웹앱을 탐색하는 동안 사용자 데이터를 스토리지하기 위한 ASP.NET Core 시나리오입니다. 세션 상태는 앱에서 유지 관리하는 저장소를 사용하여 클라이언트의 요청 간에 데이터를 유지합니다. 세션 데이터는 캐시에 백업되며 임시 데이터로 간주됩니다. 세션 데이터 없이도 사이트가 계속 작동해야 합니다. 중요한 애플리케이션 데이터는 사용자 데이터베이스에 저장되고 성능 최적화로 세션에 캐시되어야 합니다.

SignalR 허브가 HTTP 컨텍스트와 독립적으로 실행될 수 있으므로, 세션은 SignalR 앱에서 지원되지 않습니다. 예를 들어, 허브에서 긴 폴링 요청이 HTTP 컨텍스트 수명을 초과하여 계속 열려 있을 경우 이 문제가 발생할 수 있습니다.

ASP.NET Core는 세션 ID를 포함하는 cookie를 클라이언트에 제공하여 세션 상태를 유지 관리합니다. cookie 세션 ID:

  • 각 요청과 함께 앱에 전송됩니다.
  • 앱에서 세션 데이터를 가져오는 데 사용됩니다.

세션 상태는 다음과 같은 동작을 보여 줍니다.

  • 세션 cookie는 브라우저와 관련이 있습니다. 세션은 브라우저 간에 공유되지 않습니다.
  • 세션 cookie는 브라우저 세션이 끝나면 삭제됩니다.
  • cookie가 만료된 세션에 대해 수신되면 동일한 세션 cookie를 사용하는 새 세션이 생성됩니다.
  • 빈 세션은 유지되지 않습니다. 요청 간에 세션을 유지하려면 세션에 하나 이상의 값이 설정되어 있어야 합니다. 세션이 유지되지 않으면 새 요청마다 새 세션 ID가 생성됩니다.
  • 앱은 마지막 요청 이후 제한된 시간 동안 세션을 유지합니다. 앱은 세션 시간 제한을 설정하거나 20분의 기본값을 사용합니다. 세션 상태는 다음 조건을 충족하는 사용자 데이터를 저장하는 데 적합합니다.
    • 특정 세션과 관련이 있습니다.
    • 세션 간에 데이터의 영구 스토리지가 필요하지 않습니다.
  • 세션 데이터는 ISession.Clear 구현이 호출되거나 세션이 만료될 때 삭제됩니다.
  • 클라이언트 브라우저가 닫혔거나 세션 cookie가 삭제 또는 클라이언트에서 만료되었을 때 앱 코드에 이를 알려주는 기본 메커니즘은 없습니다.
  • 기본적으로 세션 상태 cookie는 필수로 표시되어 있지 않습니다. 사이트 방문자가 추적을 허용하지 않는 한, 세션 상태는 작동하지 않습니다. 자세한 내용은 ASP.NET Core의 GDPR(일반 데이터 보호 규정) 지원을 참조하세요.
  • 참고: 안전하지 않은 것으로 간주되고 세션 고정 공격으로 이어질 수 있으므로 ASP.NET Framework에서 더 적은 세션 기능을 대체할 cookie수 없습니다.

경고

중요한 데이터를 세션 상태에 저장하지 마세요. 사용자는 브라우저를 닫지 않고 세션 cookie를 지울 수 있습니다. 일부 브라우저는 브라우저 창이 닫혀도 유효한 세션 cookie를 유지 관리합니다. 세션을 단일 사용자로 제한하지 못할 수도 있습니다. 다음 사용자가 동일한 세션 cookie로 앱을 계속 검색할 수 있습니다.

메모리 내 캐시 공급자는 앱이 있는 서버의 메모리에 세션 데이터를 저장합니다. 서버 팜 시나리오:

  • 고정 세션을 사용하여 각 세션을 개별 서버의 특정 앱 인스턴스에 연결합니다. Azure App ServiceARR(애플리케이션 요청 라우팅)을 사용하여 기본적으로 고정 세션을 적용합니다. 그러나 고정 세션은 확장성에 영향을 주고 웹앱 업데이트를 복잡하게 만들 수 있습니다. 더 나은 방법은 고정 세션이 필요 없는 Redis 또는 SQL Server 분산 캐시를 사용하는 것입니다. 자세한 내용은 ASP.NET Core의 분산 캐싱을 참조하세요.
  • 세션 cookie는 IDataProtector를 통해 암호화됩니다. 데이터 보호는 각 머신에서 세션 cookie를 읽을 수 있도록 올바르게 구성되어야 합니다. 자세한 내용은 ASP.NET Core 데이터 보호 개요키 스토리지 공급자를 참조하세요.

세션 상태 구성

Microsoft.AspNetCore.Session 패키지와 관련해서 다음 사항을 확인합니다.

  • 프레임워크를 통해 암시적으로 포함됩니다.
  • 세션 상태를 관리하기 위한 미들웨어를 제공합니다.

세션 미들웨어를 활성화하려면 Program.cs은 다음을 포함해야 합니다.

다음 코드에서는 IDistributedCache의 기본 메모리 내 구현을 사용하여 메모리 내 세션 공급자를 설정하는 방법을 보여 줍니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromSeconds(10);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseSession();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

위의 코드는 테스트를 간소화하기 위해 짧은 시간 제한을 설정합니다.

미들웨어의 순서가 중요합니다. UseRouting 이후, MapRazorPagesMapDefaultControllerRoute 이전에 UseSession을 호출합니다. 미들웨어 순서를 참조하세요.

HttpContext.Session은 세션 상태가 구성된 후에 사용할 수 있습니다.

UseSession이 호출되기 전에 HttpContext.Session에 액세스할 수 없습니다.

앱이 응답 스트림에 쓰기 시작한 후에는 새 세션 cookie가 있는 새 세션을 만들 수 없습니다. 예외는 웹 서버 로그에 기록되며 브라우저에는 표시되지 않습니다.

세션 상태를 비동기적으로 로드

ASP.NET Core에서 기본 세션 공급자는 ISession.LoadAsync 메서드가 TryGetValue, Set 또는 Remove 메서드 전에 명시적으로 호출된 경우에만 기본 IDistributedCache 백업 저장소에서 비동기적으로 세션 레코드를 로드합니다. LoadAsync가 먼저 호출되지 않은 경우 기본 세션 레코드가 동기적으로 로드되며, 이는 크기에 따라 성능 저하를 초래할 수 있습니다.

이 패턴을 앱에 적용하려면 LoadAsync 메서드가 TryGetValue, Set 또는 Remove 이전에 호출되지 않은 경우 예외를 throw하는 버전으로 DistributedSessionStoreDistributedSession 구현을 래핑합니다. 서비스 컨테이너에 래핑된 버전을 등록합니다.

세션 옵션

세션 기본값을 재정의하려면 SessionOptions를 사용합니다.

옵션 설명
Cookie cookie를 만드는 데 사용되는 설정을 결정합니다. Name 기본값은 SessionDefaults.CookieName(.AspNetCore.Session)입니다. Path 기본값은 SessionDefaults.CookiePath(/)입니다. SameSite기본값은 SameSiteMode.Lax(1)입니다. HttpOnly 기본값은 true입니다. IsEssential 기본값은 false입니다.
IdleTimeout IdleTimeout은 콘텐츠가 삭제되기 전까지 세션이 유휴 상태일 수 있는 시간을 나타냅니다. 각 세션 액세스는 시간 제한을 다시 설정합니다. 이 설정은 cookie가 아닌 세션의 콘텐츠에만 적용됩니다. 기본값은 20분입니다.
IOTimeout 저장소에서 세션을 로드하거나 저장소로 다시 커밋할 수 있는 최대 시간입니다. 이 설정은 비동기 작업에만 적용될 수 있습니다. 이 시간 제한은 InfiniteTimeSpan을 사용하여 사용하지 않도록 설정할 수 있습니다. 기본값은 1분입니다.

세션은 cookie를 사용하여 단일 브라우저에서 요청을 추적하고 식별합니다. 기본적으로 이 cookie는 .AspNetCore.Session이라고 하며 /의 경로를 사용합니다. cookie 기본값은 도메인을 지정하지 않으므로 페이지에서 클라이언트 쪽 스크립트에 사용할 수 없습니다(HttpOnly 기본값이 true이므로).

cookie 세션 기본값을 재정의하려면 SessionOptions를 사용합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
    options.Cookie.Name = ".AdventureWorks.Session";
    options.IdleTimeout = TimeSpan.FromSeconds(10);
    options.Cookie.IsEssential = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseSession();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

앱은 IdleTimeout 속성을 사용하여 서버 캐시의 콘텐츠가 중단되기 전에 유휴 상태일 수 있는 세션의 기간을 결정합니다. 이 속성은 cookie 만료와 무관합니다. 세션 미들웨어를 통해 전달되는 각 요청은 시간 제한을 다시 설정합니다.

세션 상태는 잠그지 않음입니다. 두 요청이 동시에 세션의 콘텐츠를 수정하려고 하는 경우 마지막 요청이 첫 번째 요청을 재정의합니다. Session일관된 세션으로 구현됩니다. 즉, 모든 콘텐츠는 함께 저장됩니다. 두 요청이 서로 다른 세션 값을 수정하려고 할 때 마지막 요청이 첫 번째 요청에 의해 수행된 세션 변경 내용을 재정의할 수 있습니다.

세션 값 설정 및 가져오기

세션 상태는 HttpContext.Session이 포함된 Razor Pages PageModel 클래스 또는 MVC Controller 클래스에서 액세스됩니다. 이 속성은 ISession 구현입니다.

ISession 구현은 정수 및 문자열 값을 설정 및 검색하는 몇 가지 확장 메서드를 제공합니다. 확장 메서드는 Microsoft.AspNetCore.Http 네임스페이스에 있습니다.

ISession 확장명 메서드:

다음 예제에서는 Razor Pages 페이지에서 IndexModel.SessionKeyName 키(샘플 앱의 _Name)의 세션 값을 검색합니다.

@page
@using Microsoft.AspNetCore.Http
@model IndexModel

...

Name: @HttpContext.Session.GetString(IndexModel.SessionKeyName)

다음 예제에서는 정수와 문자열을 설정하고 가져오는 방법을 보여 줍니다.

public class IndexModel : PageModel
{
    public const string SessionKeyName = "_Name";
    public const string SessionKeyAge = "_Age";

    private readonly ILogger<IndexModel> _logger;

    public IndexModel(ILogger<IndexModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        if (string.IsNullOrEmpty(HttpContext.Session.GetString(SessionKeyName)))
        {
            HttpContext.Session.SetString(SessionKeyName, "The Doctor");
            HttpContext.Session.SetInt32(SessionKeyAge, 73);
        }
        var name = HttpContext.Session.GetString(SessionKeyName);
        var age = HttpContext.Session.GetInt32(SessionKeyAge).ToString();

        _logger.LogInformation("Session Name: {Name}", name);
        _logger.LogInformation("Session Age: {Age}", age);
    }
}

다음 태그는 Razor 페이지에 세션 값을 표시합니다.

@page
@model PrivacyModel
@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<div class="text-center">
<p><b>Name:</b> @HttpContext.Session.GetString("_Name");<b>Age:

</b> @HttpContext.Session.GetInt32("_Age").ToString()</p>
</div>


메모리 내 캐시를 사용하는 경우에도, 분산된 캐시 시나리오를 사용하려면 모든 세션 데이터를 직렬화해야 합니다. 문자열 및 정수 직렬 변환기는 ISession의 확장 메서드가 제공합니다. 복합 형식은 JSON과 같은 다른 메커니즘을 사용하여 사용자가 직렬화해야 합니다.

다음 샘플 코드를 사용하여 개체를 직렬화할 수 있습니다.

public static class SessionExtensions
{
    public static void Set<T>(this ISession session, string key, T value)
    {
        session.SetString(key, JsonSerializer.Serialize(value));
    }

    public static T? Get<T>(this ISession session, string key)
    {
        var value = session.GetString(key);
        return value == null ? default : JsonSerializer.Deserialize<T>(value);
    }
}

다음 예제는 SessionExtensions 클래스를 사용하여 직렬화 가능 개체를 설정하고 가져오는 방법을 보여 줍니다.

using Microsoft.AspNetCore.Mvc.RazorPages;
using Web.Extensions;    // SessionExtensions

namespace SessionSample.Pages
{
    public class Index6Model : PageModel
    {
        const string SessionKeyTime = "_Time";
        public string? SessionInfo_SessionTime { get; private set; }
        private readonly ILogger<Index6Model> _logger;

        public Index6Model(ILogger<Index6Model> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
            var currentTime = DateTime.Now;

            // Requires SessionExtensions from sample.
            if (HttpContext.Session.Get<DateTime>(SessionKeyTime) == default)
            {
                HttpContext.Session.Set<DateTime>(SessionKeyTime, currentTime);
            }
            _logger.LogInformation("Current Time: {Time}", currentTime);
            _logger.LogInformation("Session Time: {Time}", 
                           HttpContext.Session.Get<DateTime>(SessionKeyTime));

        }
    }
}

TempData

ASP.NET Core는 Razor Pages TempData 또는 컨트롤러 TempData를 공개합니다. 이 속성은 다른 요청에서 읽혀질 때까지만 데이터를 저장합니다. Keep(문자열) 및 Peek(문자열) 메서드를 사용하면 요청이 끝날 때 삭제하지 않고 데이터를 검사할 수 있습니다. Keep은 사전의 모든 항목을 보존하도록 표시합니다. TempData인 경우 다음과 같습니다.

  • 데이터가 둘 이상의 요청에 필요한 경우 리디렉션에 유용합니다.
  • TempData 공급자가 cookie 또는 세션 상태를 사용하여 구현합니다.

TempData 샘플

고객을 만드는 다음 페이지를 살펴보겠습니다.

public class CreateModel : PageModel
{
    private readonly RazorPagesContactsContext _context;

    public CreateModel(RazorPagesContactsContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";

        return RedirectToPage("./IndexPeek");
    }
}

다음 페이지는 TempData["Message"]를 표시합니다.

@page
@model IndexModel

<h1>Peek Contacts</h1>

@{
    if (TempData.Peek("Message") != null)
    {
        <h3>Message: @TempData.Peek("Message")</h3>
    }
}

@*Content removed for brevity.*@

위 태그에서 Peek가 사용되기 때문에 요청 끝에서 TempData["Message"]가 삭제되지 않습니다. 페이지를 새로 고치면 TempData["Message"]의 내용이 표시됩니다.

다음 태그는 위 코드와 비슷하지만 Keep을 사용하여 요청 끝에서 데이터를 유지합니다.

@page
@model IndexModel

<h1>Contacts Keep</h1>

@{
    if (TempData["Message"] != null)
    {
        <h3>Message: @TempData["Message"]</h3>
    }
    TempData.Keep("Message");
}

@*Content removed for brevity.*@

IndexPeek 페이지와 IndexKeep 페이지 사이를 이동해도 TempData["Message"]가 삭제되지 않습니다.

다음 코드는 TempData["Message"]를 표시하지만 요청 끝에서 TempData["Message"]가 삭제됩니다.

@page
@model IndexModel

<h1>Index no Keep or Peek</h1>

@{
    if (TempData["Message"] != null)
    {
        <h3>Message: @TempData["Message"]</h3>
    }
}

@*Content removed for brevity.*@

TempData 공급자

cookie 기반 TempData 공급자는 TempData를 cookie에 저장하는 데 기본적으로 사용됩니다.

cookie 데이터는 IDataProtector를 사용하여 암호화되고, Base64UrlTextEncoder로 인코딩된 후 청크 분할됩니다. 암호화 및 청크로 인해 최대 cookie 크기는 4,096바이트 미만입니다. 암호화된 데이터를 압축하는 것은 CRIMEBREACH 공격과 같은 보안 문제를 일으킬 수 있으므로 cookie 데이터는 압축되지 않습니다. cookie 기반 TempData 공급자에 대한 자세한 내용은 CookieTempDataProvider를 참조하세요.

TempData 공급자 선택

TempData 공급자를 선택하는 데는 다음과 같은 몇 가지 고려 사항이 수반됩니다.

  • 앱이 이미 세션 상태를 사용합니까? 그런 경우, 데이터 크기를 제외하고 세션 상태 TempData 공급자 사용과 관련해서 앱에 부과되는 추가 비용은 없습니다.
  • 앱이 비교적 적은 양의 데이터(최대 500바이트)에만 TempData를 제한적으로 사용합니까? 그런 경우 cookie TempData 공급자는 TempData를 전달하는 각 요청에 적은 비용을 추가합니다. 그렇지 않은 경우 세션 상태 TempData 공급자는 TempData가 사용될 때까지 각 요청에서 많은 양의 데이터를 왕복 작업하지 않도록 하는 데 도움이 될 수 있습니다.
  • 앱이 여러 서버의 서버 팜에서 실행됩니까? 그런 경우 데이터 보호 외부에서 cookie TempData 공급자를 사용하는 데 필요한 추가 구성은 없습니다. 자세한 내용은 ASP.NET Core 데이터 보호 개요키 스토리지 공급자를 참조하세요.

대부분의 웹 클라이언트(예: 웹 브라우저)는 각 cookie의 최대 크기와 cookie의 총 수에 제한을 적용합니다. cookie TempData 공급자를 사용하는 경우, 앱이 해당 제한을 넘지 않도록 확인합니다. 데이터의 총 크기를 고려합니다. 암호화 및 청크 분할로 인한 cookie 크기 증가를 고려합니다.

TempData 공급자 구성

cookie 기반 TempData 공급자는 기본적으로 활성화됩니다.

세션 기반 TempData 공급자를 사용하도록 설정하려면 AddSessionStateTempDataProvider 확장 메서드를 사용합니다. AddSessionStateTempDataProvider를 한 번만 호출하면 됩니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages()
                    .AddSessionStateTempDataProvider();
builder.Services.AddControllersWithViews()
                    .AddSessionStateTempDataProvider();

builder.Services.AddSession();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseSession();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

쿼리 문자열

제한된 양의 데이터는 새 요청의 쿼리 문자열에 추가하여 한 요청에서 다른 요청으로 전달할 수 있습니다. 이는 이메일 또는 소셜 네트워크를 통해 공유되도록 포함된 상태가 있는 링크를 허용하는 영구적인 방식으로 상태를 캡처하는 데 유용합니다. URL 쿼리 문자열은 공용이므로 중요한 데이터에 쿼리 문자열을 사용하지 마세요.

의도하지 않은 공유 외에도, 쿼리 문자열에 데이터를 포함하면 앱이 CSRF(교차 사이트 요청 위조) 공격에 노출될 수 있습니다. 유지된 모든 세션 상태를 CSRF 공격으로부터 보호해야 합니다. 자세한 내용은 ASP.NET Core에서 교차 사이트 요청 위조(XSRF/CSRF) 공격 방지를 참조하세요.

숨겨진 필드

데이터는 숨겨진 양식 필드에 저장되고 다음 요청에서 다시 게시될 수 있습니다. 이는 다중 페이지 폼에서 일반적입니다. 클라이언트는 잠재적으로 데이터를 변조할 수 있으므로 앱은 항상 숨겨진 필드에 저장된 데이터의 유효성을 다시 검사해야 합니다.

HttpContext.Items

HttpContext.Items 컬렉션은 단일 요청을 처리하는 동안 데이터를 저장하는 데 사용됩니다. 컬렉션의 콘텐츠는 요청이 처리된 후 삭제됩니다. Items 컬렉션은 구성 요소 또는 미들웨어가 요청 중에 다른 시점에서 작동하고 매개 변수를 전달할 직접적인 방법이 없는 경우에 통신을 지원하기 위해 자주 사용됩니다.

다음 예제에서 미들웨어isVerifiedItems 컬렉션에 추가합니다.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

ILogger logger = app.Logger;

app.Use(async (context, next) =>
{
    // context.Items["isVerified"] is null
    logger.LogInformation($"Before setting: Verified: {context.Items["isVerified"]}");
    context.Items["isVerified"] = true;
    await next.Invoke();
});

app.Use(async (context, next) =>
{
    // context.Items["isVerified"] is true
    logger.LogInformation($"Next: Verified: {context.Items["isVerified"]}");
    await next.Invoke();
});

app.MapGet("/", async context =>
{
    await context.Response.WriteAsync($"Verified: {context.Items["isVerified"]}");
});

app.Run();

단일 앱에서만 사용되는 미들웨어의 경우 고정된 string 키를 사용하면 키 충돌이 발생할 가능성이 낮습니다. 그러나 키 충돌이 발생하지 않도록 object를 항목 키로 사용할 수 있습니다. 이 방법은 앱 간에 공유되는 미들웨어에 특히 유용하며 또한 코드에서 키 문자열을 사용하지 않아도 된다는 이점이 있습니다. 다음 예제에서는 미들웨어 클래스에 정의된 고유한 object 키를 사용하는 방법을 보여 줍니다.

public class HttpContextItemsMiddleware
{
    private readonly RequestDelegate _next;
    public static readonly object HttpContextItemsMiddlewareKey = new();

    public HttpContextItemsMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        httpContext.Items[HttpContextItemsMiddlewareKey] = "K-9";

        await _next(httpContext);
    }
}

public static class HttpContextItemsMiddlewareExtensions
{
    public static IApplicationBuilder 
        UseHttpContextItemsMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<HttpContextItemsMiddleware>();
    }
}

다른 코드는 미들웨어 클래스에 의해 노출된 키를 사용하여 HttpContext.Items에 저장된 값에 액세스할 수 있습니다.

public class Index2Model : PageModel
{
    private readonly ILogger<Index2Model> _logger;

    public Index2Model(ILogger<Index2Model> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        HttpContext.Items
            .TryGetValue(HttpContextItemsMiddleware.HttpContextItemsMiddlewareKey,
                out var middlewareSetValue);

        _logger.LogInformation("Middleware value {MV}",
            middlewareSetValue?.ToString() ?? "Middleware value not set!");
    }
}

캐시

캐싱은 데이터 저장 및 검색하는 효율적인 방법입니다. 앱은 캐시된 항목의 수명을 제어할 수 있습니다. 자세한 내용은 ASP.NET Core의 응답 캐싱을 참조하세요.

캐시된 데이터는 특정 요청, 사용자 또는 세션과 연관되지 않습니다. 다른 사용자 요청을 통해 검색될 수 있는 사용자별 데이터를 캐시하면 안 됩니다.

애플리케이션 전반의 데이터를 캐시하려면 ASP.NET Core 메모리 내 캐시를 참조하세요.

일반적인 오류

  • "'Microsoft.AspNetCore.Session.DistributedSessionStore'를 활성화하려고 시도하는 동안 'Microsoft.Extensions.Caching.Distributed.IDistributedCache' 형식에 대한 서비스를 확인할 수 없습니다."

    이 오류는 일반적으로 하나 이상의 IDistributedCache 구현을 구성하지 못한 경우에 발생합니다. 자세한 내용은 ASP.NET Core의 분산 캐싱ASP.NET Core 메모리 내 캐시를 참조하세요.

세션 미들웨어가 세션을 유지하지 못한 경우

  • 미들웨어가 예외를 기록하고 요청은 정상적으로 계속 진행됩니다.
  • 이로 인해 예기치 않은 동작이 발생합니다.

백업 저장소를 사용할 수 없는 경우 세션 미들웨어가 세션을 유지하지 못할 수 있습니다. 예를 들어 사용자는 세션에 쇼핑 카트를 저장합니다. 사용자가 카트에 항목을 추가하지만 커밋이 실패합니다. 앱은 실패에 대해 알지 못하므로 항목이 카트에 추가되었다고 보고하지만, 이는 사실이 아닙니다.

오류를 확인하는 권장 방법은 앱이 세션에 기록을 마쳤을 때 await feature.Session.CommitAsync를 호출하는 것입니다. 백업 저장소를 사용할 수 없는 경우 CommitAsync에서 예외를 throw합니다. CommitAsync가 실패하면 앱에서 예외를 처리할 수 있습니다. 데이터 저장소를 사용할 수 없는 경우에는 동일한 조건에서 LoadAsync가 throw합니다.

추가 리소스

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

웹 팜에 ASP.NET Core 호스트

작성자: Rick Anderson, Kirk Larkin, Diana LaRose

HTTP는 상태 비저장 프로토콜입니다. 기본적으로 HTTP 요청은 사용자 값을 유지하지 않는 독립적인 메시지입니다. 이 문서에서는 요청 간에 사용자 데이터를 유지하는 여러 가지 방법을 설명합니다.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

상태 관리

상태는 여러 방법을 사용하여 저장할 수 있습니다. 각 방법은 이 항목 뒷부분에서 설명합니다.

스토리지 접근 방식 스토리지 메커니즘
Cookie HTTP cookie. 서버 쪽 앱 코드를 사용하여 저장된 데이터를 포함할 수 있습니다.
세션 상태 HTTP cookie 및 서버 쪽 앱 코드
TempData HTTP cookie 또는 세션 상태
쿼리 문자열 HTTP 쿼리 문자열
숨겨진 필드 HTTP 양식 필드
HttpContext.Items 서버 쪽 앱 코드
캐시 서버 쪽 앱 코드

SignalR/Blazor Server 및 HTTP 컨텍스트 기반 상태 관리

SignalR 앱은 안정적인 HTTP 컨텍스트를 사용하여 정보를 저장하는 세션 상태 및 기타 상태 관리 방법을 사용하면 안 됩니다. SignalR앱은 허브에Context.Items 연결당 상태를 저장할 수 있습니다. 앱에 대한 Blazor Server 자세한 내용 및 대체 상태 관리 방법은 상태 관리 ASP.NET Core Blazor 참조하세요.

Cookies

Cookie는 요청 간에 데이터를 저장합니다. cookie는 모든 요청과 함께 전송되므로 해당 크기는 최소로 유지되어야 합니다. 이상적으로 식별자만 앱에 저장된 데이터와 함께 cookie에 저장되어야 합니다. 대부분의 브라우저는 cookie 크기를 4,096바이트로 제한합니다. 제한된 수의 cookie만 각 도메인에 사용할 수 있습니다.

cookie는 변조될 수 있기 때문에 앱에서 유효성을 검사해야 합니다. Cookie는 사용자가 삭제할 수 있으며 클라이언트에서 만료됩니다. 그러나 cookie는 일반적으로 클라이언트에서 데이터 지속성의 가장 안정적인 형태입니다.

Cookie는 종종 개인 설정에 사용됩니다. 여기서 콘텐츠는 알려진 사용자에 대해 사용자 지정됩니다. 사용자만 식별되고 대부분의 경우 인증되지 않습니다. cookie는 사용자 이름, 계정 이름 또는 고유한 사용자 ID(예: GUID)를 저장할 수 있습니다. cookie를 사용하여 선호하는 웹 사이트 배경색과 같은 사용자의 맞춤형 설정에 액세스할 수 있습니다.

cookie를 발급하고 개인 정보 보호 문제를 처리하는 경우 유럽 연합 GDPR(일반 데이터 보호 규정)을 참조하세요. 자세한 내용은 ASP.NET Core의 GDPR(일반 데이터 보호 규정) 지원을 참조하세요.

세션 상태

세션 상태는 사용자가 웹앱을 탐색하는 동안 사용자 데이터를 스토리지하기 위한 ASP.NET Core 시나리오입니다. 세션 상태는 앱에서 유지 관리하는 저장소를 사용하여 클라이언트의 요청 간에 데이터를 유지합니다. 세션 데이터는 캐시에 백업되며 임시 데이터로 간주됩니다. 세션 데이터 없이도 사이트가 계속 작동해야 합니다. 중요한 애플리케이션 데이터는 사용자 데이터베이스에 저장되고 성능 최적화로 세션에 캐시되어야 합니다.

SignalR 허브가 HTTP 컨텍스트와 독립적으로 실행될 수 있으므로, 세션은 SignalR 앱에서 지원되지 않습니다. 예를 들어, 허브에서 긴 폴링 요청이 HTTP 컨텍스트 수명을 초과하여 계속 열려 있을 경우 이 문제가 발생할 수 있습니다.

ASP.NET Core는 세션 ID를 포함하는 cookie를 클라이언트에 제공하여 세션 상태를 유지 관리합니다. cookie 세션 ID:

  • 각 요청과 함께 앱에 전송됩니다.
  • 앱에서 세션 데이터를 가져오는 데 사용됩니다.

세션 상태는 다음과 같은 동작을 보여 줍니다.

  • 세션 cookie는 브라우저와 관련이 있습니다. 세션은 브라우저 간에 공유되지 않습니다.
  • 세션 cookie는 브라우저 세션이 끝나면 삭제됩니다.
  • cookie가 만료된 세션에 대해 수신되면 동일한 세션 cookie를 사용하는 새 세션이 생성됩니다.
  • 빈 세션은 유지되지 않습니다. 요청 간에 세션을 유지하려면 세션에 하나 이상의 값이 설정되어 있어야 합니다. 세션이 유지되지 않으면 새 요청마다 새 세션 ID가 생성됩니다.
  • 앱은 마지막 요청 이후 제한된 시간 동안 세션을 유지합니다. 앱은 세션 시간 제한을 설정하거나 20분의 기본값을 사용합니다. 세션 상태는 다음 조건을 충족하는 사용자 데이터를 저장하는 데 적합합니다.
    • 특정 세션과 관련이 있습니다.
    • 세션 간에 데이터의 영구 스토리지가 필요하지 않습니다.
  • 세션 데이터는 ISession.Clear 구현이 호출되거나 세션이 만료될 때 삭제됩니다.
  • 클라이언트 브라우저가 닫혔거나 세션 cookie가 삭제 또는 클라이언트에서 만료되었을 때 앱 코드에 이를 알려주는 기본 메커니즘은 없습니다.
  • 기본적으로 세션 상태 cookie는 필수로 표시되어 있지 않습니다. 사이트 방문자가 추적을 허용하지 않는 한, 세션 상태는 작동하지 않습니다. 자세한 내용은 ASP.NET Core의 GDPR(일반 데이터 보호 규정) 지원을 참조하세요.

경고

중요한 데이터를 세션 상태에 저장하지 마세요. 사용자는 브라우저를 닫지 않고 세션 cookie를 지울 수 있습니다. 일부 브라우저는 브라우저 창이 닫혀도 유효한 세션 cookie를 유지 관리합니다. 세션을 단일 사용자로 제한하지 못할 수도 있습니다. 다음 사용자가 동일한 세션 cookie로 앱을 계속 검색할 수 있습니다.

메모리 내 캐시 공급자는 앱이 있는 서버의 메모리에 세션 데이터를 저장합니다. 서버 팜 시나리오:

  • 고정 세션을 사용하여 각 세션을 개별 서버의 특정 앱 인스턴스에 연결합니다. Azure App ServiceARR(애플리케이션 요청 라우팅)을 사용하여 기본적으로 고정 세션을 적용합니다. 그러나 고정 세션은 확장성에 영향을 주고 웹앱 업데이트를 복잡하게 만들 수 있습니다. 더 나은 방법은 고정 세션이 필요 없는 Redis 또는 SQL Server 분산 캐시를 사용하는 것입니다. 자세한 내용은 ASP.NET Core의 분산 캐싱을 참조하세요.
  • 세션 cookie는 IDataProtector를 통해 암호화됩니다. 데이터 보호는 각 머신에서 세션 cookie를 읽을 수 있도록 올바르게 구성되어야 합니다. 자세한 내용은 ASP.NET Core 데이터 보호 개요키 스토리지 공급자를 참조하세요.

세션 상태 구성

Microsoft.AspNetCore.Session 패키지와 관련해서 다음 사항을 확인합니다.

  • 프레임워크를 통해 암시적으로 포함됩니다.
  • 세션 상태를 관리하기 위한 미들웨어를 제공합니다.

세션 미들웨어를 활성화하려면 Startup은 다음을 포함해야 합니다.

다음 코드에서는 IDistributedCache의 기본 메모리 내 구현을 사용하여 메모리 내 세션 공급자를 설정하는 방법을 보여 줍니다.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDistributedMemoryCache();

        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromSeconds(10);
            options.Cookie.HttpOnly = true;
            options.Cookie.IsEssential = true;
        });

        services.AddControllersWithViews();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseSession();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute();
            endpoints.MapRazorPages();
        });
    }
}

위의 코드는 테스트를 간소화하기 위해 짧은 시간 제한을 설정합니다.

미들웨어의 순서가 중요합니다. UseRouting 이후, UseEndpoints 이전에 UseSession을 호출합니다. 미들웨어 순서를 참조하세요.

HttpContext.Session은 세션 상태가 구성된 후에 사용할 수 있습니다.

UseSession이 호출되기 전에 HttpContext.Session에 액세스할 수 없습니다.

앱이 응답 스트림에 쓰기 시작한 후에는 새 세션 cookie가 있는 새 세션을 만들 수 없습니다. 예외는 웹 서버 로그에 기록되며 브라우저에는 표시되지 않습니다.

세션 상태를 비동기적으로 로드

ASP.NET Core에서 기본 세션 공급자는 ISession.LoadAsync 메서드가 TryGetValue, Set 또는 Remove 메서드 전에 명시적으로 호출된 경우에만 기본 IDistributedCache 백업 저장소에서 비동기적으로 세션 레코드를 로드합니다. LoadAsync가 먼저 호출되지 않은 경우 기본 세션 레코드가 동기적으로 로드되며, 이는 크기에 따라 성능 저하를 초래할 수 있습니다.

이 패턴을 앱에 적용하려면 LoadAsync 메서드가 TryGetValue, Set 또는 Remove 이전에 호출되지 않은 경우 예외를 throw하는 버전으로 DistributedSessionStoreDistributedSession 구현을 래핑합니다. 서비스 컨테이너에 래핑된 버전을 등록합니다.

세션 옵션

세션 기본값을 재정의하려면 SessionOptions를 사용합니다.

옵션 설명
Cookie cookie를 만드는 데 사용되는 설정을 결정합니다. Name 기본값은 SessionDefaults.CookieName(.AspNetCore.Session)입니다. Path 기본값은 SessionDefaults.CookiePath(/)입니다. SameSite기본값은 SameSiteMode.Lax(1)입니다. HttpOnly 기본값은 true입니다. IsEssential 기본값은 false입니다.
IdleTimeout IdleTimeout은 콘텐츠가 삭제되기 전까지 세션이 유휴 상태일 수 있는 시간을 나타냅니다. 각 세션 액세스는 시간 제한을 다시 설정합니다. 이 설정은 cookie가 아닌 세션의 콘텐츠에만 적용됩니다. 기본값은 20분입니다.
IOTimeout 저장소에서 세션을 로드하거나 저장소로 다시 커밋할 수 있는 최대 시간입니다. 이 설정은 비동기 작업에만 적용될 수 있습니다. 이 시간 제한은 InfiniteTimeSpan을 사용하여 사용하지 않도록 설정할 수 있습니다. 기본값은 1분입니다.

세션은 cookie를 사용하여 단일 브라우저에서 요청을 추적하고 식별합니다. 기본적으로 이 cookie는 .AspNetCore.Session이라고 하며 /의 경로를 사용합니다. cookie 기본값은 도메인을 지정하지 않으므로 페이지에서 클라이언트 쪽 스크립트에 사용할 수 없습니다(HttpOnly 기본값이 true이므로).

cookie 세션 기본값을 재정의하려면 SessionOptions를 사용합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedMemoryCache();

    services.AddSession(options =>
    {
        options.Cookie.Name = ".AdventureWorks.Session";
        options.IdleTimeout = TimeSpan.FromSeconds(10);
        options.Cookie.IsEssential = true;
    });

    services.AddControllersWithViews();
    services.AddRazorPages();
}

앱은 IdleTimeout 속성을 사용하여 서버 캐시의 콘텐츠가 중단되기 전에 유휴 상태일 수 있는 세션의 기간을 결정합니다. 이 속성은 cookie 만료와 무관합니다. 세션 미들웨어를 통해 전달되는 각 요청은 시간 제한을 다시 설정합니다.

세션 상태는 잠그지 않음입니다. 두 요청이 동시에 세션의 콘텐츠를 수정하려고 하는 경우 마지막 요청이 첫 번째 요청을 재정의합니다. Session일관된 세션으로 구현됩니다. 즉, 모든 콘텐츠는 함께 저장됩니다. 두 요청이 서로 다른 세션 값을 수정하려고 할 때 마지막 요청이 첫 번째 요청에 의해 수행된 세션 변경 내용을 재정의할 수 있습니다.

세션 값 설정 및 가져오기

세션 상태는 HttpContext.Session이 포함된 Razor Pages PageModel 클래스 또는 MVC Controller 클래스에서 액세스됩니다. 이 속성은 ISession 구현입니다.

ISession 구현은 정수 및 문자열 값을 설정 및 검색하는 몇 가지 확장 메서드를 제공합니다. 확장 메서드는 Microsoft.AspNetCore.Http 네임스페이스에 있습니다.

ISession 확장명 메서드:

다음 예제에서는 Razor Pages 페이지에서 IndexModel.SessionKeyName 키(샘플 앱의 _Name)의 세션 값을 검색합니다.

@page
@using Microsoft.AspNetCore.Http
@model IndexModel

...

Name: @HttpContext.Session.GetString(IndexModel.SessionKeyName)

다음 예제에서는 정수와 문자열을 설정하고 가져오는 방법을 보여 줍니다.

public class IndexModel : PageModel
{
    public const string SessionKeyName = "_Name";
    public const string SessionKeyAge = "_Age";
    const string SessionKeyTime = "_Time";

    public string SessionInfo_Name { get; private set; }
    public string SessionInfo_Age { get; private set; }
    public string SessionInfo_CurrentTime { get; private set; }
    public string SessionInfo_SessionTime { get; private set; }
    public string SessionInfo_MiddlewareValue { get; private set; }

    public void OnGet()
    {
        // Requires: using Microsoft.AspNetCore.Http;
        if (string.IsNullOrEmpty(HttpContext.Session.GetString(SessionKeyName)))
        {
            HttpContext.Session.SetString(SessionKeyName, "The Doctor");
            HttpContext.Session.SetInt32(SessionKeyAge, 773);
        }

        var name = HttpContext.Session.GetString(SessionKeyName);
        var age = HttpContext.Session.GetInt32(SessionKeyAge);

메모리 내 캐시를 사용하는 경우에도, 분산된 캐시 시나리오를 사용하려면 모든 세션 데이터를 직렬화해야 합니다. 문자열 및 정수 직렬 변환기는 ISession의 확장 메서드가 제공합니다. 복합 형식은 JSON과 같은 다른 메커니즘을 사용하여 사용자가 직렬화해야 합니다.

다음 샘플 코드를 사용하여 개체를 직렬화할 수 있습니다.

public static class SessionExtensions
{
    public static void Set<T>(this ISession session, string key, T value)
    {
        session.SetString(key, JsonSerializer.Serialize(value));
    }

    public static T Get<T>(this ISession session, string key)
    {
        var value = session.GetString(key);
        return value == null ? default : JsonSerializer.Deserialize<T>(value);
    }
}

다음 예제는 SessionExtensions 클래스를 사용하여 직렬화 가능 개체를 설정하고 가져오는 방법을 보여 줍니다.

// Requires SessionExtensions from sample download.
if (HttpContext.Session.Get<DateTime>(SessionKeyTime) == default)
{
    HttpContext.Session.Set<DateTime>(SessionKeyTime, currentTime);
}

TempData

ASP.NET Core는 Razor Pages TempData 또는 컨트롤러 TempData를 공개합니다. 이 속성은 다른 요청에서 읽혀질 때까지만 데이터를 저장합니다. Keep(문자열) 및 Peek(문자열) 메서드를 사용하면 요청이 끝날 때 삭제하지 않고 데이터를 검사할 수 있습니다. Keep은 사전의 모든 항목을 보존하도록 표시합니다. TempData인 경우 다음과 같습니다.

  • 데이터가 둘 이상의 요청에 필요한 경우 리디렉션에 유용합니다.
  • TempData 공급자가 cookie 또는 세션 상태를 사용하여 구현합니다.

TempData 샘플

고객을 만드는 다음 페이지를 살펴보겠습니다.

public class CreateModel : PageModel
{
    private readonly RazorPagesContactsContext _context;

    public CreateModel(RazorPagesContactsContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";

        return RedirectToPage("./IndexPeek");
    }
}

다음 페이지는 TempData["Message"]를 표시합니다.

@page
@model IndexModel

<h1>Peek Contacts</h1>

@{
    if (TempData.Peek("Message") != null)
    {
        <h3>Message: @TempData.Peek("Message")</h3>
    }
}

@*Content removed for brevity.*@

위 태그에서 Peek가 사용되기 때문에 요청 끝에서 TempData["Message"]가 삭제되지 않습니다. 페이지를 새로 고치면 TempData["Message"]의 내용이 표시됩니다.

다음 태그는 위 코드와 비슷하지만 Keep을 사용하여 요청 끝에서 데이터를 유지합니다.

@page
@model IndexModel

<h1>Contacts Keep</h1>

@{
    if (TempData["Message"] != null)
    {
        <h3>Message: @TempData["Message"]</h3>
    }
    TempData.Keep("Message");
}

@*Content removed for brevity.*@

IndexPeek 페이지와 IndexKeep 페이지 사이를 이동해도 TempData["Message"]가 삭제되지 않습니다.

다음 코드는 TempData["Message"]를 표시하지만 요청 끝에서 TempData["Message"]가 삭제됩니다.

@page
@model IndexModel

<h1>Index no Keep or Peek</h1>

@{
    if (TempData["Message"] != null)
    {
        <h3>Message: @TempData["Message"]</h3>
    }
}

@*Content removed for brevity.*@

TempData 공급자

cookie 기반 TempData 공급자는 TempData를 cookie에 저장하는 데 기본적으로 사용됩니다.

cookie 데이터는 IDataProtector를 사용하여 암호화되고, Base64UrlTextEncoder로 인코딩된 후 청크 분할됩니다. 암호화 및 청크로 인해 최대 cookie 크기는 4,096바이트 미만입니다. 암호화된 데이터를 압축하는 것은 CRIMEBREACH 공격과 같은 보안 문제를 일으킬 수 있으므로 cookie 데이터는 압축되지 않습니다. cookie 기반 TempData 공급자에 대한 자세한 내용은 CookieTempDataProvider를 참조하세요.

TempData 공급자 선택

TempData 공급자를 선택하는 데는 다음과 같은 몇 가지 고려 사항이 수반됩니다.

  • 앱이 이미 세션 상태를 사용합니까? 그런 경우, 데이터 크기를 제외하고 세션 상태 TempData 공급자 사용과 관련해서 앱에 부과되는 추가 비용은 없습니다.
  • 앱이 비교적 적은 양의 데이터(최대 500바이트)에만 TempData를 제한적으로 사용합니까? 그런 경우 cookie TempData 공급자는 TempData를 전달하는 각 요청에 적은 비용을 추가합니다. 그렇지 않은 경우 세션 상태 TempData 공급자는 TempData가 사용될 때까지 각 요청에서 많은 양의 데이터를 왕복 작업하지 않도록 하는 데 도움이 될 수 있습니다.
  • 앱이 여러 서버의 서버 팜에서 실행됩니까? 그렇다면 데이터 보호 외부에서 cookie TempData 공급자를 사용하는 데 필요한 추가 구성은 없습니다(ASP.NET Core 데이터 보호 개요키 스토리지 공급자 참조).

대부분의 웹 클라이언트(예: 웹 브라우저)는 각 cookie의 최대 크기와 cookie의 총 수에 제한을 적용합니다. cookie TempData 공급자를 사용하는 경우, 앱이 해당 제한을 넘지 않도록 확인합니다. 데이터의 총 크기를 고려합니다. 암호화 및 청크 분할로 인한 cookie 크기 증가를 고려합니다.

TempData 공급자 구성

cookie 기반 TempData 공급자는 기본적으로 활성화됩니다.

세션 기반 TempData 공급자를 사용하도록 설정하려면 AddSessionStateTempDataProvider 확장 메서드를 사용합니다. AddSessionStateTempDataProvider를 한 번만 호출하면 됩니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews()
        .AddSessionStateTempDataProvider();
    services.AddRazorPages()
        .AddSessionStateTempDataProvider();

    services.AddSession();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
        endpoints.MapRazorPages();
    });
}

쿼리 문자열

제한된 양의 데이터는 새 요청의 쿼리 문자열에 추가하여 한 요청에서 다른 요청으로 전달할 수 있습니다. 이는 이메일 또는 소셜 네트워크를 통해 공유되도록 포함된 상태가 있는 링크를 허용하는 영구적인 방식으로 상태를 캡처하는 데 유용합니다. URL 쿼리 문자열은 공용이므로 중요한 데이터에 쿼리 문자열을 사용하지 마세요.

의도하지 않은 공유 외에도, 쿼리 문자열에 데이터를 포함하면 앱이 CSRF(교차 사이트 요청 위조) 공격에 노출될 수 있습니다. 유지된 모든 세션 상태를 CSRF 공격으로부터 보호해야 합니다. 자세한 내용은 ASP.NET Core에서 교차 사이트 요청 위조(XSRF/CSRF) 공격 방지를 참조하세요.

숨겨진 필드

데이터는 숨겨진 양식 필드에 저장되고 다음 요청에서 다시 게시될 수 있습니다. 이는 다중 페이지 폼에서 일반적입니다. 클라이언트는 잠재적으로 데이터를 변조할 수 있으므로 앱은 항상 숨겨진 필드에 저장된 데이터의 유효성을 다시 검사해야 합니다.

HttpContext.Items

HttpContext.Items 컬렉션은 단일 요청을 처리하는 동안 데이터를 저장하는 데 사용됩니다. 컬렉션의 콘텐츠는 요청이 처리된 후 삭제됩니다. Items 컬렉션은 구성 요소 또는 미들웨어가 요청 중에 다른 시점에서 작동하고 매개 변수를 전달할 직접적인 방법이 없는 경우에 통신을 지원하기 위해 자주 사용됩니다.

다음 예제에서 미들웨어isVerifiedItems 컬렉션에 추가합니다.

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    app.UseRouting();

    app.Use(async (context, next) =>
    {
        logger.LogInformation($"Before setting: Verified: {context.Items["isVerified"]}");
        context.Items["isVerified"] = true;
        await next.Invoke();
    });

    app.Use(async (context, next) =>
    {
        logger.LogInformation($"Next: Verified: {context.Items["isVerified"]}");
        await next.Invoke();
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync($"Verified: {context.Items["isVerified"]}");
        });
    });
}

단일 앱에서만 사용되는 미들웨어의 경우 고정된 string 키가 허용됩니다. 앱 간에 공유되는 미들웨어는 키 충돌을 방지하기 위해 고유한 개체 키를 사용해야 합니다. 다음 예제에서는 미들웨어 클래스에 정의된 고유한 개체 키를 사용하는 방법을 보여 줍니다.

public class HttpContextItemsMiddleware
{
    private readonly RequestDelegate _next;
    public static readonly object HttpContextItemsMiddlewareKey = new Object();

    public HttpContextItemsMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        httpContext.Items[HttpContextItemsMiddlewareKey] = "K-9";

        await _next(httpContext);
    }
}

public static class HttpContextItemsMiddlewareExtensions
{
    public static IApplicationBuilder 
        UseHttpContextItemsMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<HttpContextItemsMiddleware>();
    }
}

다른 코드는 미들웨어 클래스에 의해 노출된 키를 사용하여 HttpContext.Items에 저장된 값에 액세스할 수 있습니다.

HttpContext.Items
    .TryGetValue(HttpContextItemsMiddleware.HttpContextItemsMiddlewareKey, 
        out var middlewareSetValue);
SessionInfo_MiddlewareValue = 
    middlewareSetValue?.ToString() ?? "Middleware value not set!";

이 방법은 또한 코드에서 키 문자열을 사용하지 않아도 된다는 이점이 있습니다.

캐시

캐싱은 데이터 저장 및 검색하는 효율적인 방법입니다. 앱은 캐시된 항목의 수명을 제어할 수 있습니다. 자세한 내용은 ASP.NET Core의 응답 캐싱을 참조하세요.

캐시된 데이터는 특정 요청, 사용자 또는 세션과 연관되지 않습니다. 다른 사용자 요청을 통해 검색될 수 있는 사용자별 데이터를 캐시하면 안 됩니다.

애플리케이션 전반의 데이터를 캐시하려면 ASP.NET Core 메모리 내 캐시를 참조하세요.

일반적인 오류

  • "'Microsoft.AspNetCore.Session.DistributedSessionStore'를 활성화하려고 시도하는 동안 'Microsoft.Extensions.Caching.Distributed.IDistributedCache' 형식에 대한 서비스를 확인할 수 없습니다."

    이 오류는 일반적으로 하나 이상의 IDistributedCache 구현을 구성하지 못한 경우에 발생합니다. 자세한 내용은 ASP.NET Core의 분산 캐싱ASP.NET Core 메모리 내 캐시를 참조하세요.

세션 미들웨어가 세션을 유지하지 못한 경우

  • 미들웨어가 예외를 기록하고 요청은 정상적으로 계속 진행됩니다.
  • 이로 인해 예기치 않은 동작이 발생합니다.

백업 저장소를 사용할 수 없는 경우 세션 미들웨어가 세션을 유지하지 못할 수 있습니다. 예를 들어 사용자는 세션에 쇼핑 카트를 저장합니다. 사용자가 카트에 항목을 추가하지만 커밋이 실패합니다. 앱은 실패에 대해 알지 못하므로 항목이 카트에 추가되었다고 보고하지만, 이는 사실이 아닙니다.

오류를 확인하는 권장 방법은 앱이 세션에 기록을 마쳤을 때 await feature.Session.CommitAsync를 호출하는 것입니다. 백업 저장소를 사용할 수 없는 경우 CommitAsync에서 예외를 throw합니다. CommitAsync가 실패하면 앱에서 예외를 처리할 수 있습니다. 데이터 저장소를 사용할 수 없는 경우에는 동일한 조건에서 LoadAsync가 throw합니다.

추가 자료

웹 팜에 ASP.NET Core 호스트