Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I correctly inject `TempDataDictionary` into my classes?

I have an app that is written using c# and ASP.NET MVC 5. I'm also using Unity.Mvc for dependency injection.

Along with many other classes, the class MessageManager is registered in the IoC container. However, the MessageManager class depends on an instance of TempDataDictionary to perform its work. This class is used to write temporary data for the views.

In order to resolve an instance of MessageManager I need to also register an instance of TempDataDictionary. I would need to be able to add values to TempDataDictionary from the MessageManager class and then I would need to access the temp data from the view. Therefore, I need to be able to access the same instance of TempDataDictionary in the views so I can write out the messages to the user.

Also, if the controller redirects the user elsewhere, I don't want to lose the message, I still want to be able to show the message on the next view.

I tried the following to register both TempDataDictionary and MessageManager:

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

Then in my view, I have the following to resolve to an instance of IMessageManager

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

However, the message gets lost for some reason. That is, when I resolve manager, TempDataDictionary doesn't contain any messages that were added by MessageManager from the controller.

How can I correctly register an instance of the TempDataDictionary so the data persists until it is viewed?

UPDATED Here is my IMessageManager interface

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();
}
like image 747
Junior Avatar asked Apr 30 '18 20:04

Junior


1 Answers

The TempDataDictionary is an intrinsic part of your MessageManager implementation, therefore, you should implement it inside that class directly instead of registering it in the container.

Something like:

public class MessageManager : IMessageManager
{
    private TempDataDictionary _tempDataDictionary;

    [...]
}

However, IMHO, I don't think is a good practice to use TempDataDictionary out of a controller context so instead of implementing it in your class, you could pass it every time you add or retrieve a message:

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

You could also create a MessageManager instance for each request using PerThreadLifetimeManager and then you don't need to use the TempDataDictionary at all, you can just implement this yourself with regular lists or dictionaries:

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

Then, register it per thread so everything is cleared out on each request:

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

A better aproach?

If you want to make sure that the list is kept until it has been read, even if it happens in another request, or if you are using async actions or ajax requests, you can create your own LifetimeManager implementation that resolves the instance of the above class per session, for example:

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

Then replace PerThreadLifetimeManager by SessionLifetimeManager above and simply clear the list each time you access it, for example:

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

Reference:

The SessionLifetimeManager was borrowed from here: https://gist.github.com/CrestApps/a246530e386b95d0a05d36bb13805259

like image 57
Isma Avatar answered Sep 30 '22 06:09

Isma