Reputation: 1277
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
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