如何正确地将`TempDataDictionary`注入到我的类中?

7

我有一个用c#和ASP.NET MVC 5编写的应用程序。我也使用Unity.Mvc进行依赖注入。

除了许多其他类之外,MessageManager类也已经在IoC容器中注册了。 然而,MessageManager类依赖于一个TempDataDictionary实例来执行其工作。该类用于为视图编写临时数据。

为了解析MessageManager类的实例,我需要注册一个TempDataDictionary类实例。我需要能够从MessageManager类中添加值到TempDataDictionary类,并且我需要能够在视图中访问临时数据,因此我需要能够访问相同的TempDataDictionary实例以便于向用户输出信息。

此外,如果控制器将用户重定向到其他位置,则不希望丢失消息,我仍然希望能够在下一个视图上显示该消息。

我尝试了以下内容来注册TempDataDictionaryMessageManager

Container.RegisterType<TempDataDictionary>(new PerThreadLifetimeManager())
         .RegisterType<IMessageManager, MessageManager>();

在我的看法中,我需要将以下内容解析为 IMessageManager 的实例。

var manager = DependencyResolver.Current.GetService<IMessageManager>();

然而,由于某些原因,信息丢失了。也就是说,在解析“manager”时,“TempDataDictionary”不包含控制器中由“MessageManager”添加的任何消息。
我应该如何正确注册“TempDataDictionary”的实例,以便数据保留直到查看为止?
更新: 这是我的“IMessageManager”接口。
public interface IMessageManager
{
    void AddSuccess(string message, int? dismissAfter = null);
    void AddError(string message, int? dismissAfter = null);
    void AddInfo(string message, int? dismissAfter = null);
    void AddWarning(string message, int? dismissAfter = null);
    Dictionary<string, IEnumerable<FlashMessage>> GetAlerts();
}

你能分享一下IMessageManager的接口吗?就你现在提出的问题而言,我并没有看到需要在TempData周围包装的必要性,因为你可以直接从控制器和视图中使用它。 - Isma
@Isma 我更新了我的问题,加入了接口。 - Junior
4
这似乎是一个设计问题。您还需要提供“MessageManager”的实现。您似乎对框架中如何使用“TempDataDictionary”存在误解。它的唯一目的是在当前和下一个HTTP请求之间传递数据。阅读这篇文章可以更好地理解何时在ASP.NET MVC 3应用程序中使用ViewBag、ViewData或TempData: When to use ViewBag,ViewData或TempData in ASP.NET MVC 3 applications - Nkosi
@Nkosi,我有哪些选项可以将传输的Flash消息封装到视图中? - Junior
2个回答

6
TempDataDictionary是您的MessageManager实现中的固有部分,因此,应直接在该类中实现它,而不是在容器中注册它。
例如:
public class MessageManager : IMessageManager
{
    private TempDataDictionary _tempDataDictionary;

    [...]
}

然而,我认为在控制器上下文之外使用TempDataDictionary并不是一个好的实践方法,因此,你可以在每次添加或检索消息时将其传递:

void AddSuccess(IDictionary<string, object> tempData, string message);

你可以使用 PerThreadLifetimeManager 为每个请求创建一个 MessageManager 实例,然后就不需要使用 TempDataDictionary 了,你可以使用常规的列表或字典来实现这一点:
public class MessageManager : IMessageManager
{
    private List<string> _successMessages = new List<string>();
    private List<string> _errorMessages = new List<string>();
    private List<string> _warningMessage = new List<string>();
    private List<string> _infoMessage = new List<string>();

    public void AddSuccess(string message)
    {
        _successMessages.Add(message);
    }

    public void AddError(string message)
    {
        _errorMessages.Add(message);
    }

    public void AddWarning(string message)
    {
        _warningMessages.Add(message);
    }

    public void AddInfo(string message)
    {
        _infoMessages.Add(message);
    }

    public List<string> SuccessMessages
    {
        get { return _successMessages; }
    }

    public List<string> ErrorMessages
    {
        get { return _errorMessages; }
    }

    public List<string> WarningMessages
    {
        get { return _warningMessages; }
    }

    public List<string> InfoMessages
    {
        get { return _infoMessages; }
    }
}

然后,为每个线程注册它,以便在每个请求时清除所有内容:
Container.RegisterType.RegisterType<IMessageManager, MessageManager>
        (new PerThreadLifetimeManager());

更好的做法?

如果您想确保列表在被读取之前保持不变,即使它是在另一个请求中发生的,或者如果您正在使用异步操作或ajax请求,您可以创建自己的LifetimeManager实现,以会话为基础解析上述类的实例,例如:

public class SessionLifetimeManager : LifetimeManager
{
    private string _key = Guid.NewGuid().ToString();
    public override void RemoveValue(ILifetimeContainer container = null)
    {
        HttpContext.Current.Session.Remove(_key);
    }
    public override void SetValue(object newValue, ILifetimeContainer container = null)
    {
        HttpContext.Current.Session[_key] = newValue;
    }
    public override object GetValue(ILifetimeContainer container = null)
    {
        return HttpContext.Current.Session[_key];
    }
    protected override LifetimeManager OnCreateLifetimeManager()
    {
        return new PerSessionLifetimeManager();
    }
}

然后将上述 PerThreadLifetimeManager替换为 SessionLifetimeManager,每次访问时只需清除列表即可,例如:

public List<string> InfoMessages
{
    get 
    { 
         // Some view has accessed the data, clear the list before returning
         var tempInfoMessages = new List<string>(_infoMessages);
         _infoMessages.Clear();
         return tempInfoMessages; 
    }
}

参考资料:

SessionLifetimeManager 的实现代码来自于这里:https://gist.github.com/CrestApps/a246530e386b95d0a05d36bb13805259


你的建议很好。唯一的问题是,如果这个类在异步调用中使用PerThreadLifetimeManager,可能会导致问题。线程会改变,数据将无法正确传输。 - Junior
最后一个选项对你不起作用吗?不使用PerThreadLifetimeManager,只是在访问列表时清除它? - Isma
你是对的,抱歉!我忘记了Unity默认使用TransientLifetimeManager,我更新了我的答案以指定你应该使用ContainerControlledLifetimeManager,这样你就可以在整个应用程序中只有一个实例。 - Isma
啊!对不起,我又错过了。我再次更新了答案。希望每个会话管理器都能解决问题,并且比存储用户名更清晰(即使没有用户连接,您可能仍想显示错误消息)。 - Isma
1
生命周期管理的实现似乎已经过时了。这里是一个更新的实现,对我很有效 https://gist.github.com/CrestApps/a246530e386b95d0a05d36bb13805259 - Junior
显示剩余2条评论

2

这不是一个严格的答案,而是一个另外的建议:

如果控制器将用户重定向到其他地方,我不想失去消息,我仍然希望能够在下一个视图中显示消息。

仅仅查看上面的内容让我认为将您的消息存储在数据库中可能是一个更好的选择。这样,即使您想要回顾旧消息,也可以回溯时间。

// persist in EF db context
class Message {
    DateTime CreatedUtc { get; set; }
    DateTime SeenUtc { get; set; }
    string Text { get; set; }
    AspNetUser User { get; set; }
    // etc
}

你可以在请求之间保留此消息,管理何时将消息标记为已读,并且甚至可以让用户查看他的旧消息(如果你愿意这样做)。

谢谢您的建议。但是,这个想法是要存储 flash 消息,而不适合存储在数据库中。这些消息是临时的、一次性的,只用一次。 - Junior

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接