U4-5927 - Umbraco 7: Create Notifications from C# events - ClientTools.ShowSpeechBubble

Created by Warren Buckley (Personal) 03 Dec 2014, 09:22:29 Updated by Ronen 22 Mar 2018, 13:16:21

Is duplicated by: U4-5439

Is duplicated by: U4-5665

Relates to: U4-6872

In Umbraco 7 with the lovely new backoffice it has not been possible to push a notification from C# event such as BeforeSave on ContentService to notify the user of some other custom action or logic has been performed.

Previously this was done with something like this in an event:

BasePage.Current.ClientTools.ShowSpeechBubble(BasePage.speechBubbleIcon.info, "One moment please", "Busy sending notification mails");

There is a relatively large forum thread here of people trying to discuss workaround, all which seem to be a not ideal. With the WebAPI interception and modifying the JSON that gets pushed down the pipe to the browser.

http://our.umbraco.org/forum/umbraco-7/developing-umbraco-7-packages/53699-User-Message-(former-Speech-bubble)-in-custom-event?p=0#comment201081

Along with my suggestion I made with a POC with a GIST here trying to replicate a similar approach to how the upgrade checker works in Umbraco. https://gist.github.com/warrenbuckley/07d21b682c8e076cb5e2

Shannon said he was not aware of any issue relating to this, so hence this issue to discuss this & raise it with the core team to investigate.

3 Attachments

Comments

Warren Buckley (Personal) 03 Dec 2014, 09:59:31

Just putting down some of my thoughts from last night is that will something like WebSockets/SignalR be required to push JSON for notifications down from C# or is there a smarter way this will get implemented without that dependancy?


Shannon Deminick 03 Dec 2014, 10:14:01

haha, yes, there will be a better way. We don't need to hack the planet mate, we will just fix it in the core.


Warren Buckley (Personal) 03 Dec 2014, 10:26:10

Haha good good. Just thinking out loud :) HACK ALL THE THINGZZZZ !


Tim Payne 03 Dec 2014, 16:44:56

A related issue is that cancelling events no longer displays the speech bubble that it used to on the parts of the CMS that have been upgraded to Angular. E.g. if I cancel a delete on something I don't want the user to delete, it cancels the delete, but no message is displayed, and the tree still shows that the item is gone (even though it isn't, a refresh of the tree makes the item appear again).


Tim Payne 03 Dec 2014, 16:45:59

You could also use the ClientTools to do stuff like refresh trees etc from events, again, this is not possible on the newer angular sections of the site.


Shannon Deminick 03 Dec 2014, 22:07:50

Tim, we are aware of the canceling events issue.

As for using c# to make JS calls, i disagree with this concept. The ClientTools era is a webforms afterthought. We need to use the right tool for the right job. Currently we have a mix of angular and webforms in the back office, this is not ideal. Eventually (hopefully soonish), the whole back office will be angularized, so we'll be using JS for JS.

Syncing trees is 100% possible in angular sections of the back office using JS... this is exactly how it is done.


Shannon Deminick 03 Dec 2014, 22:09:32

If we are going to allow some sort of c# to control some sort of JS, it will be done in a completely different way than client tools, I don't want to be writing c# that writes JS, that is just totally ugly and unnecessary. There might be some c# indicator that allows you to execute some JS service... but you will have to write JS to run JS.


Warren Buckley (Personal) 11 Dec 2014, 22:53:15

@Shandem not sure why issue was marked just visible to me & HQ/Core team only. Let me know if it needs to be for a valid reason please.


Shannon Deminick 11 Dec 2014, 23:05:04

Hrm not sure, all users is good


Craig Noble 24 Mar 2015, 12:30:50

Hi @Shandem, is this something that will be actioned soon? This is quite a core part of extending Umbraco for clients - and almost a showstopper in my case. Or it will require me to hack a below par solution (if I had extra time), which I'm sure I will regret!


Shannon Deminick 24 Mar 2015, 21:50:31

@craignoble1989 This issue is tagged for release for 7.3.0


Shannon Deminick 15 Jun 2015, 10:21:37

Listing andy's PR here for reference: https://github.com/umbraco/Umbraco-CMS/pull/705

However we need to implement this in a consistent manner. I'll review and update


Shannon Deminick 24 Jul 2015, 10:01:43

This is implemented now for ContentService saving and publishing and MediaService saving events. I need to update this for all service events in due time but i need to get this out now.

So this is how you work with messages:

On any event handler, you can add messages to the event args. For example:

        Umbraco.Core.Services.ContentService.Saving += (sender, e) =>
        {
            e.Cancel = true;
            e.Messages.Add(new EventMessage("Mwahahahaha!", "I've cancelled all of your saving!", EventMessageType.Error));
        };

Then your message will appear in the UI, see attached image. This same concept will eventually work with all events. You can ONLY add messages to the queue, you cannot remove messages because you cannot tamper with other event handler messages.

This will only work with editors that follow the same design pattern as our core editors and return a model that implements Umbraco.Web.Models.ContentEditing.INotificationModel. A new attribute for controllers has been created: [AppendCurrentEventMessages], if you put this attribute on your custom editor controller and your return model is Umbraco.Web.Models.ContentEditing.INotificationModel then the messages pumped into the event message queue will be added to the outgoing model. There's also a new base controller class that has this attributed by default which looks like:

    [AppendCurrentEventMessages]
    public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController
    {


Søren Reinke 24 Jul 2015, 10:10:08

Excellent :)

Thanks a lot.


Andy Butland 27 Jul 2015, 15:49:50

Looks great.

From the [blog post|http://umbraco.com/follow-us/blog-archive/2015/7/27/umbraco-730-beta2-out-now] discussing this feature... "a little birdie just told me we can help you get rid of that orange bar as well, that's coming in the final release". Was ''just'' going to ask about that, but will instead watch this space!


Sebastiaan Janssen 27 Jul 2015, 15:52:23

Yup, that was in anticipation of the blog comments @abutland ;-)


Shannon Deminick 27 Jul 2015, 16:05:48

Fixed in rev: 926ea54c393dc80d1531224a5d0b1ac4c76e3c5c

If you cancel an event with the new event's CancelOperation method, the message you pass in to that method will be used as the 'default' method and will supersede Umbraco's default message.


Leszek 28 Jul 2015, 18:42:58

Don't forget about Unpublishing and Deleting events, please.


Shannon Deminick 28 Jul 2015, 18:48:33

Yes unfortunately a bit of work is going to be involved for every single event. So we'll see if we'll have time to get it all done for release. And this will only work for angular based views, it will not work for legacy views.


Chad Rosenthal 04 Jan 2016, 00:08:16

So I tried the e.CancelOperation() on the Savings event in 7.4 beta and it works...sort of. It looks like it displays the error message and then throws a 404. The edit panel goes white and I can no longer see the node that I was editing (see attached).

I used the nuget package to update from a 7.1.3 (I think) to 7.4 beta.

Thoughts?


Shannon Deminick 05 Jan 2016, 12:36:06

Which 'Saving' event? For what entity?


Sebastiaan Janssen 05 Jan 2016, 13:12:25

@Chad.Rosenthal I'm not sure what to look for, I got your site dropped in the code below and I get not 404 error. I couldn't find an eventhandler in your custom dll so maybe add some code?

using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Services;

namespace ClassLibrary2 { public class Class1 : ApplicationEventHandler { protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { ContentService.Saving += (sender, e) => { e.CancelOperation(new EventMessage("Mwahahahaha!", "I've cancelled all of your saving!")); }; } } }


Sebastiaan Janssen 05 Jan 2016, 16:41:44

@Chad.Rosenthal Okay, found it, this has nothing to do with cancelling the operation but with the fact that you're doing this on a completely new (not yet existing) node. So upon the first save of this node you can cancel it, but Umbraco doesn't know what node to go back to (look at the URL in your address bar: ends with: umbraco/#/content/content/edit/0, so ID 0), as you've cancelled the operation, the node never existed. Not sure how that can be handled though.

Looking at your code, it could just as well be handling the ContentService.Publising event, as your feedback to the editor says the node has been save anyway. Maybe @Shandem has a tip about cancelling the saving event when the node doesn't exist yet?


Jon Humphrey 25 Jan 2016, 03:49:16

@sebastiaan, @Shandem, et al!

First and foremost thanks for this, I stumbled here from @warrenbuckley's Our post when looking to notify my users of different results on actions!

The situation is that everyting works in the code below and resorts the content tree on their sort but I'd like to let the user know I've "stopped" them and why... when I try to use the code examples you've provided above I've ended up with a few issues in my 7.3.1 instance:

e.Cancel = true; throws an error with the message "StackOverflow: An unhandled exception of type 'System.StackOverflowException' occurred in System.Data.SqlServerCe.dll" which surprises the hell out of me

no messages are shown at all if I comment out the Cancel assignmentor or not

nothing happens triggered in saving or deleting events - breakpoints do not get hit in the e.Messages line

///

/// Check to make sure the Module Manager Always atays at the top of the child listing /// /// /// private static void CheckChildNodeOrder(IContentService sender, SaveEventArgs e) { foreach (var node in e.SavedEntities //Check if the content item type has a specific alias .Where(s => !s.ContentType.Alias.InvariantEquals("NG_USK__ModuleManager")) ) { //Check if it is a new item if (node.IsNewEntity()) continue; // get the module manager node information var moduleManager = node.Parent().Children().FirstOrDefault(c => c.ContentType.Alias.InvariantEquals("NG_USK__ModuleManager")); //if no moduleManagernode exit if (moduleManager.ToNullSafeString().Equals(string.Empty)) continue; // get the sibling nodes but not the ModuleManager, into a list var siblingContentItems = node.Parent().Children().Where(f => moduleManager != null && f.Id != moduleManager.Id).ToList(); // add the one being saved to the start of the list siblingContentItems.Insert(0, moduleManager); // reference the content service var contentService = ApplicationContext.Current.Services.ContentService; // call contentService Sort method on the list... contentService.Sort(siblingContentItems); //Tell them to shove off e.Cancel = true; e.Messages.Add(new EventMessage("Mwahahahaha!", "I've cancelled all of your sorting!", EventMessageType.Warning)); //e.CancelOperation(new EventMessage("Mwahahahaha!", "I've cancelled all of your sorting!")); } }

I'm happy to share full code with you but, as usual, it has to be in private unfortunately; please let me know if there's anything you need!

Also, out of curiosity, is there plans for a Confirm dialogue or not?

Cheers, Jon


Shannon Deminick 25 Jan 2016, 09:34:40

Hi, Some quick notes about your code:

  • You are calling this multiple times: node.Parent().Children(), this is going to cause more SQL calls that necessary. Any time you use any of these calls to get Parent or Children, etc... you are making database queries.
  • You shouldn't use the singleton: ApplicationContext.Current.Services.ContentService, you already have an instance of this service passed in to this method as a parameter
  • You can't perform an action like saving or sorting during a Saving event. Sorting Saves items, so you're creating an infinite loop.


Jon Humphrey 25 Jan 2016, 10:29:23

@Shandem, Thanks for the pointer on the Singleton, I never connected the sender as the same.

So how can I cancel a sort if the moduleManager is not the first child as I'm not sure on how to find the sibling of a node then if I can't use {{node.Parent().Children()}} and if I can't re-sort on the saving event to put it back as the first child?


Shannon Deminick 25 Jan 2016, 12:54:44

You can use node.Parent().Children(), just don't use it multiple times, store the result of it and use the result otherwise you're doing unnecessary SQL queries. The result of node.Parent().Children() is IEnumerable, not IQueryable, so performing a Linq query on the result of .Parent().Children() is still going to retrieve the parent and all of it's children from the db before running your Linq query on it. So you might as well store the children result in a variable to re-use if you need to.

I'm unsure as to why you need to run any queries or save commands in the first place. When a Sort operation executes and the Saving event is raised, the collection of entities in the event args is the items that will be saved with the sort order in which they appear in the collection. So you already know which items is going to be the first child without looking anything up. Further more, even if you perform lookups during this event, none of the entities will be saved yet so you're looking up the 'current' order of items, not the items that are about to be sorted.

I'm not really sure that you'll be able to do exactly what you want though, i don't really think it's possible to perform a Sort/Save operation during a Saving event, this would generally always end up in an infinite loop is not something I'd recommend. The Saving event exists so that you can modify the data before it is saved, however at first glance it is isn't really possible to do what you want since you cannot change the order of items within the IEnumerable passed to this event. But you can hack it by testing to see if the e.SavedEntities is actually a List<IContent> (which is should be in most cases). Then you can take this casted item and re-sort it however you like then when the method continues it will be saving the items with the sort order in the list.


Gordon Saxby 25 Jan 2016, 14:27:35

I am using "CancelOperation" within a ContentService.Saving handler.

That is working OK, however, I am not getting the "You have unsaved changes" message when I then go to another page. I get the message if I make a change and navigate away from the page without trying to save (and therefore triggering my validation code).

This means that changes will be lost if I get my "invalid" message and then navigate away from the page.


Jon Humphrey 25 Jan 2016, 14:47:45

@Shandem,

Thanks for taking the time, maybe if I explain the process it will become clear.

So, [I've already created the ModuleManger and added and sorted this programmatically to the page.|https://our.umbraco.org/forum/developers/api-questions/74109-umbraco-7-how-to-put-a-newly-created-node-on-top#comment-238549] What I need to do is stop anyone from moving/sorting this node - basically locking it into place unless deleted - to which we already have a confirmation dialogue - thereby succeeding in preventing the user from changing its location!

Does that help?


Juan David 16 Jun 2016, 21:50:42

Hi Guys,

I am working on Umbraco version 7.2.4 assembly: 1.0.5557.19139, I know that this issue was fixed on 7.3. But exist any way to get this small fix for my version. Can I extend this functionality? (exist any Github commit for that)

I have a really big site and upgrade is a complicate option for me.

Update: https://github.com/umbraco/Umbraco-CMS/pull/705/files

Thanks JD


Shannon Deminick 20 Jun 2016, 08:11:50

There are no plans to release any more 7.2.x versions, you will need to upgrade. This is a large change and not easily backported. Upgrading to the latest umbraco version is easy from your version and highly recommended there are hundreds of fixes (not sure why it is complicated?)


Juan David 20 Jun 2016, 15:01:37

Thanks for your answer, I have a really big big database, and servers broken with this upgrade, also I have to recode some functionalities because now are allowed. Exist any extra support to guide us to do this upgrade? I checked : https://our.umbraco.org/documentation/Getting-Started/Setup/Upgrading/general https://our.umbraco.org/documentation/Getting-Started/Setup/Upgrading/version-specific

But I am not sure why database server generate the big issue.

Thanks again for your answer


Fabian 04 Jul 2016, 10:54:17

@Shandem It's also implemented for the unpublishing event? On 28 Jul 2015, 20:48 you wrote, it will be implemented if you have some time ;)

Cheers fabian


Cristiano Martins 17 Oct 2016, 16:50:51

Hi @Shandem,

The solution you wrote earlier works for the ContentService events, but not if I use the MemberService events. I'm trying to add/change a notification when I save a member in the backoffice, I'm cancelling and adding a new message in the MemberService.Saving or MemberService.Saved events

	private void MemberService_Saved(IMemberService sender, Umbraco.Core.Events.SaveEventArgs<Umbraco.Core.Models.IMember> e)

{ e.Cancel = true; e.Messages.Add(new EventMessage("Mwahahahaha!", "I've cancelled all of your saving!", EventMessageType.Error)); }

But the message just doesn't appear, any ideas? Do you think there's a workaround for making this work also in the member events? Many thanks


Ronen 22 Mar 2018, 13:16:21

@cristianomm any solution? please share


Priority: Normal

Type: Bug

State: Fixed

Assignee: Shannon Deminick

Difficulty: Normal

Category:

Backwards Compatible: True

Fix Submitted:

Affected versions: 7.0.0, 7.1.0, 7.0.1, 7.0.2, 7.0.3, 7.0.4, 7.1.1, 7.1.2, 7.1.3, 7.1.4, 7.1.5, 7.1.6, 7.1.7, 7.1.9, 7.2.4

Due in version: 7.3.0

Sprint:

Story Points:

Cycle: