3
\$\begingroup\$

I use a JavaScript library called Alertify to show simple, pretty messages, like "note created", or whatever. Often I want to send the user to a new page and then show an Alertify message.

I'm trying to find a way to do this on the fly, without tons of configuration.

Currently I do this by creating an AlertifyMessages object:

public class AlertifyMessages
{
    public string LogMessage { get; set; }
    public string LogCallback { get; set; }

    public string SuccessMessage { get; set; }
    public string SuccessCallback { get; set; }

    public string ErrorMessage { get; set; }
    public string ErrorCallback { get; set; }

    public AlertifyMessages() { }

    public AlertifyMessages(string logMessage = null, string logCallback = null,
                            string successMessage = null, string successCallback = null,
                            string errorMessage = null, string errorCallback = null)
    {
        LogMessage = logMessage;
        LogCallback = logCallback;
        SuccessMessage = successMessage;
        SuccessCallback = successCallback;
        ErrorMessage = errorMessage;
        ErrorCallback = errorCallback;
    }
}

and passing it to an action method:

public ActionResult Test()
{
    // RedirectToAction([action], [controller], [routeValues])
    return RedirectToAction("Details", "Notes",  
        new AlertifyMessages(logMessage: "Lorem ipsum"));
}

and in the destination action method:

public class NotesController : Controller
{
    // GET
    public ActionResult Details(AlertifyMessages alertify)
    {
        return View(new NoteDetailsViewModel(alertify));
    }
}

which, finally, displays the messages in the view:

@if (Model.Alertify.LogMessage != null)
{
    @:@Html.Alertify("log", Model.Alertify.LogMessage, Model.Alertify.LogCallback)
}
@if (Model.Alertify.SuccessMessage != null)
{
    @:@Html.Alertify("success", Model.Alertify.SuccessMessage, Model.Alertify.SuccessCallback)
}
@if (Model.Alertify.ErrorMessage != null)
{
    @:@Html.Alertify("error", Model.Alertify.ErrorMessage, Model.Alertify.ErrorCallback)
}

The helper method Alertify just adds the JavaScript to call alertify.js, but you can see it here


Besides being a nightmare in so many ways, this doesn't even work unless the AlertifyMessage is all I need to pass. I could make some kind of complicated ToString() override to resolve this, but otherwise attempting to pass, say, an ID as well as an AlertifyMessages like so

return RedirectToAction("Details", "Notes",  
    new { id = 1, alertify = new AlertifyMessages(logMessage: "Lorem ipsum")});

yields a URL like

https://localhost/Notes/Details/1?alertify=Project.Namespace.AlertifyMessages

This is way too complicated. There must be a better way.

Edit: I'd like a way to specify the message in the calling action method (the one performing the redirect), and nowhere else. Is there a way to do this other than adding code to every single method and view?

\$\endgroup\$
2
  • \$\begingroup\$ In order to pass a complex 'object' you need to pass its property values - return RedirectToAction("Details", "Notes", new { id = 1, LogMessage = "...", LogCallback = "...", ErrorCallback = "..." }); \$\endgroup\$ Commented Aug 30, 2017 at 12:39
  • \$\begingroup\$ Alternatively, it you do not want the large query string that would generate, then use TempData (although the message will not be displayed again if the user refreshes the browser, since TempData lasts only one request) \$\endgroup\$ Commented Aug 30, 2017 at 12:42

2 Answers 2

2
\$\begingroup\$

You may start making your model simpler:

public sealed class AlertifyMessageModel
{
    public string Message { get; set; }
    public string Callback { get; set; }
}

Note that you do not need all those ugly ctor parameters:

public ActionResult Test()
{
    return RedirectToAction("Details", "Notes",  
        new AlertifyMessageModel { Message = "Lorem ipsum" });
}

You may also want to use nameof(Notes.Details) instead of hard-coded "Details" string. Usually I also have an helper method GetControllerName(nameof(NotesController)) or a static field NotesController.Name but it's verbose and not everyone likes it.

Now let's go to your View. We need to add a property to the model to hold the type of notification:

public sealed class AlertifyMessageModel
{
    public string Message { get; set; }
    public string Callback { get; set; }
    public string Type { get; set; } = "log"
}

Then we can simply write:

if (Model.Alertify.Message != null)
{
    @:@Html.Alertify(Model.Alertify.Type, Model.Alertify.Message, Model.Alertify.Callback)
}

Next step is to change Type to enum:

public NotificationType Type { get; set; } = NotificationType.Log;

For your URL problem you should change something more, you may serialize the whole object as JSON and post it in the request body instead of in the URL. I don't see code around that then I can't really suggest a nice method. Note that you may also override ToString() to return a parseable representation of AlertifyMessageModel (think about type converters).

\$\endgroup\$
9
  • \$\begingroup\$ Could you elaborate on that last bit? What code would you need to see? \$\endgroup\$
    – Sinjai
    Commented Aug 30, 2017 at 13:55
  • \$\begingroup\$ The reason AlertifyMessages has so many properties is that there could theoretically be more than one message. Although that limits me to one of each, so if you have a better way, I'd love to hear it. Also, please see my edit. \$\endgroup\$
    – Sinjai
    Commented Aug 30, 2017 at 14:03
  • \$\begingroup\$ How you would use new { id = 1, alertify = new... thing. If alert messages are always present you may consider to drop that anonymous class in favor of a typed model class NotesDetailsModel : AlertifyMessageModel (BTW you should avoid anonymous models, if possible). If there might be many messages then simply use a collection of them! About the edit: that doesn't really fit CR if you do not already have some code. If you can make it clear (I can't understand the problem) it's a better question for SO. \$\endgroup\$ Commented Aug 30, 2017 at 14:10
  • \$\begingroup\$ If I understand your problem correctly (I'm not sure): if same code is present in many views/controllers then you should have a base controller class + _Layout.cshtml... \$\endgroup\$ Commented Aug 30, 2017 at 14:13
  • \$\begingroup\$ They're not always present, but I'd like there to always be an option to add one. There is a view model NoteDetailsViewmodel, but it is generated within the destination method by using the ID. Are you saying to pass that model to RedirectToAction? What's an anonymous model? \$\endgroup\$
    – Sinjai
    Commented Aug 30, 2017 at 14:14
1
\$\begingroup\$

One option is to use TempData, which is like ViewBag but not a part of the URL, and therefore only lasts for one request. A collection of AlertifyMessages can be added to a base controller class inherited by all other controllers. Then you can use action filters to transfer that property from the base class to TempData, and from TempData to the controller in the case of a redirect.

You end up with something like this:

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
}

public class AlertifyAttribute : ActionFilterAttribute
{
    private const string alertify = "Alertify";
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        BaseController bc = (BaseController)context.Controller;
        bc.TempData[alertify] = bc.AlertifyMessages;
    }
    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();
    }
}

[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

More information about the redirect problem can be found here.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ @t3chb0t You're right, it was a bad answer, added only for my own sanity. I added considerably more details in an edit. \$\endgroup\$
    – Sinjai
    Commented Dec 6, 2017 at 6:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.