ASP.NET Core 8: одноэлементный шаблон с HttpContextAccessor не работает вместе

Я разрабатываю приложение ASP.NET Core 8 и использую шаблон Singleton для управления сеансами, но когда я хочу прочитать значения, оно отображает нулевые значения!

Здесь вы можете увидеть код:

SessionSingleton.cs:

[Serializable]
public sealed class SessionSingleton : IDisposable
{
    #region Private Members
    private const string SessionSingletonName = "SessionKey_502E69E5-668B-E011-951F-00155DF26207";
    private static readonly object LockObject = new();
    #endregion

    #region Singleton
    private SessionSingleton()
    {
    }

    public static SessionSingleton Current
    {
        get
        {
            lock (LockObject)
            {
                string? value;
                SessionSingleton sessionSingleton;

                var session = CustomHttpContext.Current.Session;

                if (session == null)
                    return null;

                if (session.GetString(SessionSingletonName) is not null)
                {
                    value = session.GetString(SessionSingletonName);
                    sessionSingleton = JsonSerializer.Deserialize<SessionSingleton>(value!)!;
                }
                else
                {
                    sessionSingleton = new SessionSingleton();
                    var sessionText = JsonSerializer.Serialize(sessionSingleton);
                    session.SetString(SessionSingletonName, sessionText);
                }

                return sessionSingleton;
            }
        }
    }
    #endregion

    #region Public properties
    public string Name { get; set; }
    #endregion

    #region Dctor
    public void Dispose()
    {
         // HttpContext.Current.Session[SessionSingletonName] = null;
         // HttpContext.Current.Session.Remove(SessionSingletonName);
    }
    #endregion
}

CustomHttpContext:

public static class CustomHttpContext
{
    private static IHttpContextAccessor _httpContextAccessor = new HttpContextAccessor();

    public static HttpContext? Current
    {
        get
        {
            if (_httpContextAccessor == null)
                _httpContextAccessor = new HttpContextAccessor();

            return _httpContextAccessor.HttpContext;
        }
    }
}

HomeController:

public partial class HomeController : BaseController
{
    public IActionResult Index()
    {
        SessionSingleton.Current.Name = "Test";

        var value = SessionSingleton.Current.Name; // value is always null

        SessionSingleton.Current.Name = "Test2";

        value = SessionSingleton.Current.Name;// value is always null

        return Ok(true);
    }
}

Весь код, такой как сериализация и десериализация, работает нормально, но почему в HomeController значение всегда равно нулю?

Пожалуйста, предложите способ хранения информации пользователя и обеспечения доступа к этой информации просто во время выполнения программы пользователем.

Прежде всего, я не думаю, что вы должны создавать экземпляр HttpContextAccessor, вызывая new, а скорее получать его из DI. Во-вторых, почему вы не используете стандартный механизм внедрения зависимостей, который уже предоставляет ASP.NET Core? Эту ненужную часть о блокировке синглтона можно решить одной строкой. В-третьих: какова цель этого кода?

MxNbrt 21.07.2024 09:44

Потому что я хочу прочитать его значение из сеанса, а с помощью этого метода это невозможно.

Mohamad Ghafari 21.07.2024 13:08
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
65
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Прежде всего рассмотрите возможность использования инструментов, предоставляемых ASP.NET Core, для управления сеансом — см. документацию Управление сеансами и состояниями в ASP.NET Core.

Что касается использования IHttpContextAccessor, то это не так. Вам следует избегать использования его по умолчанию — он предназначен для весьма специфических случаев использования (и требует некоторых затрат производительности). IHttpContextAccessor обрабатывается DI и обрабатывается самим ASP.NET Core. Вам (возможно) потребуется зарегистрировать его в DI:

builder.Services.AddHttpContextAccessor();

А потом решать там, где это необходимо. См. раздел Доступ к HttpContext из пользовательских компонентов документации.

Но в случае действия контроллера это не требуется, просто используйте this.HttpContext для доступа к текущему контексту.

Я использую паттерн Singleton, по этой причине документация Microsoft у меня не работает. Кроме того, из-за использования шаблона Singleton и того, что это статический класс, невозможно внедрить HttpContextAccessor в класс SessionSingleton.

Mohamad Ghafari 21.07.2024 11:41

@MohamadGhafari «Я использую шаблон Singleton» 1) почему? Возможно, вам не следует 2) даже если вы хотите/нужно его использовать - используйте синглтоны через DI (т. е. через AddSingleton)

Guru Stron 21.07.2024 11:53

Потому что я хочу прочитать его значение из сеанса, а с помощью этого метода это невозможно.

Mohamad Ghafari 21.07.2024 12:57

@MohamadGhafari в целом вам не следует использовать шаблон Singleton в современном ASP.NET Core. Почему именно вам нужно/хотите использовать это здесь?

Guru Stron 21.07.2024 12:59

Потому что я хочу просто и понятно использовать свойства класса Singleton. Также участвует в сложностях использования HttpContext.Session.SetString() и HttpContext.Session.GetString(). Если у вас есть лучшее предложение, пожалуйста, помогите мне.

Mohamad Ghafari 21.07.2024 13:04
Ответ принят как подходящий

Наконец, после различных проверок я изменил свой код, как показано ниже, и это хорошо решило мою проблему.

После многих исследований я понял, что по какой-либо причине использование комбинации Singleton с HttpContextAccessor не приведет к желаемому результату. (Возможно, из-за логики внутреннего промежуточного программного обеспечения asp.net)

По этой причине я попытался удовлетворить свои потребности с помощью чистого и расширяемого кода.

Поэтому я использовал интерфейс и поместил в него свои поля, чтобы можно было легко его реализовать, а также использовать функцию внедрения зависимостей.

ISessionHandler

public interface ISessionHandler : IDisposable
{
    public int UserId { get; set; }
    public string Name { get; set; }
}

Менеджер сеансов

internal sealed class SessionManager : ISessionHandler
{
    #region Private Members

    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ISession _session;

    #endregion

    #region Ctor
    public SessionManager(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
        _session = _httpContextAccessor.HttpContext?.Session!;
    }

    #endregion

    #region Dctor

    public void Dispose()
    {
        _session.Clear();
    }

    #endregion


    public int UserId
    {
        get => _session.Get<int>();
        set => _session.Set(value);
    }
    public string Name
    {
        get => _session.Get<string>();
        set => _session.Set(value);
    }
}

Затем я создал статический вспомогательный класс для установки и получения информации о сеансе.

Расширение сеанса

internal static class SessionExtension
{
    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)!;
    }

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

    private static string GetKey()
    {
        var frame = new StackTrace().GetFrame(2);
        var instanceName = frame?.GetMethod()?.DeclaringType?.Name;

        var methodName = frame?.GetMethod()?.Name;
        var key = $"{instanceName}_{methodName}";
        return key.Replace("_set", "").Replace("_get", "");
    }
}

программа.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<ISessionHandler, SessionManager>();
builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
    options.Cookie.Name = ".Test.Session";
    options.IdleTimeout = TimeSpan.FromMinutes(30);
    options.Cookie.IsEssential = true;
});

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();

app.UseSession();

app.UseRequestLocalization();
app.MapDefaultControllerRoute();

app.Run();

И, наконец, как показано в коде ниже, я использовал его в своем контроллере и там, где это было необходимо.

Логинконтроллер

public partial class LoginController : BaseController
{
    public LoginController(ISessionHandler sessionHandler)
    {
    }
    public IActionResult Index()
    {
        SessionHandler.Name = "Test";

        var name = SessionHandler.Name; // It's OK


        return Ok();
    }
}

Благодарим вас за вклад в сообщество Stack Overflow. Возможно, это правильный ответ, но было бы очень полезно предоставить дополнительные пояснения к вашему коду, чтобы разработчики могли понять ваши рассуждения. Это особенно полезно для новых разработчиков, которые не так хорошо знакомы с синтаксисом или пытаются понять концепции. Не могли бы вы отредактировать свой ответ, включив в него дополнительную информацию на благо сообщества?

Jeremy Caney 22.07.2024 07:01

Другие вопросы по теме