Sinjai
Sinjai

Reputation: 1277

Passing messages to the view

I use Alertify.js to display confirmation messages, etc. in my views. I've been struggling to find a good way to pass the message from controller to view.

I have a janky-feeling setup using TempData that works, but only if I'm returning a view, rather than a redirect, which is pretty useless.

I was initially going to add this capability to a base viewmodel, but RedirectToAction doesn't support passing viewmodels.

I either need to make the TempData stick around longer or use a different process entirely.

What I use now:

public class AlertifyMessages
{
    public List<AlertifyMessage> Messages { get; private set; } = new List<AlertifyMessage>();
    public void Add(AlertifyType type, string message, string callbackUrl = null)
    {
        Messages.Add(new AlertifyMessage(type, message, callbackUrl));
    }
}

public class AlertifyMessage
{
    public AlertifyType Type { get; set; }
    public string Message { get; set; }
    public string CallbackUrl { get; set; }
    public AlertifyMessage(AlertifyType type, string message, string callbackUrl)
    {
        Type = type;
        Message = message;
        CallbackUrl = callbackUrl;
    }
}

public enum AlertifyType
{
    Log,
    Error,
    Success
}

/// <summary>
/// Applies Alertify messages to TempData after action method runs
/// <para>Can only be used with <see cref="BaseController"/></para>
/// </summary>
public class AlertifyAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        BaseController bc = (BaseController)context.Controller;
        bc.TempData["Alertify"] = bc.AlertifyMessages;
    }
}

[Alertify]
public class BaseController : Controller
{
    public BaseController()
    {
        AlertifyMessages = new AlertifyMessages();
    }

    public AlertifyMessages AlertifyMessages { get; set; }
}

Usage:

public class MyController : BaseController
{
    public ActionResult Index()
    {
        AlertifyMessages.Add(AlertifyType.Success, "Yay!", Url.Action("Index"));
        return View(/*new ViewModel()*/);
    }
}
// Then loop through and display messages in view using TempData

Edit
The accepted answer is correct, but to avoid tons of refactoring, I just added another filter to my attribute, below:

private const string alertify = "Alertify";
public override void OnActionExecuting(ActionExecutingContext context)
{
    BaseController bc = (BaseController)context.Controller;
    bc.Messages = (bc.TempData[alertify] == null) ? new AlertifyMessages() : (AlertifyMessages)bc.TempData[alertify];
    // Faster? Better? Harder? Stronger?
    //bc.Messages = (AlertifyMessages)bc.TempData[alertify] ?? new AlertifyMessages();
}

Upvotes: 0

Views: 267

Answers (1)

Shyju
Shyju

Reputation: 218702

With your current code the OnResultExecuting will be executed 2 times in the course of your redirect.

Let's say you are doing redirect like this,

public ActionResult Save(SomeViewModel model)
{
    AlertifyMessages.Add(AlertifyType.Success, "Yay!", Url.Action("Index"));
    return RedirectToAction("Index");
}
public ActionResult Index()
{
    return View();
}

Here when you return a result from the Save action method, it will executes the OnResultExecuting method of your AlertifyMessages filter whichs read the AlertifyMessages property value of the controller and set that to TempData.

Now since it is a redirect response, your browser will issue a new GET request to Index action, And for that action method also, it will execute the code inside OnResultExecuting and it will try to read the AlertifyMessages property and set that to TempData. But now your AlertifyMessages does not have any value because you did not set that in your Index action method. Remember Http is stateless. Index action method call here is like a totally new call and that call will create a new instance of the controller.

The solution is to reinitialize the AlertifyMessages property value. You can read it from the TempData

public ActionResult Index()
{
    AlertifyMessages = TempData["Alertify"] as AlertifyMessages;
    return View();
}

Upvotes: 2

Related Questions