diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/SqlServerBroker.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/SqlServerBroker.cs
new file mode 100644
index 000000000..6c4b00950
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/SqlServerBroker.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Security.Permissions;
+using System.Threading.Tasks;
+using System.Web;
+using Orchard.Data;
+using Orchard.Environment;
+using Orchard.Environment.Configuration;
+using Orchard.Environment.Extensions;
+using Orchard.Logging;
+using Orchard.MessageBus.Models;
+using Orchard.MessageBus.Services;
+using Orchard.Services;
+
+namespace Orchard.MessageBus.Brokers.SqlServer {
+ ///
+ /// A single connection is maintained, and each subscription will be triggered based on the channel it's listening to
+ ///
+ [OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")]
+ public class SqlServerBroker : IMessageBroker, IDisposable {
+
+ private IWorker _worker;
+ private bool _initialized;
+ private object _synLock = new object();
+
+ private readonly Work> _messageRecordRepository;
+ private readonly Work _clock;
+ private readonly Func _workerFactory;
+ private readonly ShellSettings _shellSettings;
+ private readonly Work _hostNameProvider;
+
+ public SqlServerBroker(
+ Work> messageRecordRepository,
+ Work clock,
+ Work hostNameProvider,
+ Func workerFactory,
+ ShellSettings shellSettings
+ ) {
+ _messageRecordRepository = messageRecordRepository;
+ _clock = clock;
+ _shellSettings = shellSettings;
+ _workerFactory = workerFactory;
+ _hostNameProvider = hostNameProvider;
+
+ Logger = NullLogger.Instance;
+ }
+
+ public ILogger Logger { get; set; }
+
+ public bool EnsureInitialized() {
+ lock (_synLock) {
+ if (!_initialized) {
+ try {
+ // call only once per connectionstring when appdomain starts up
+ Logger.Information("Starting SqlDependency.");
+ SqlDependency.Start(_shellSettings.DataConnectionString);
+
+ _worker = _workerFactory();
+ _worker.Work();
+
+ _initialized = true;
+ }
+ catch (Exception e) {
+ Logger.Error("The application doesn't have the permission to request notifications.", e);
+ }
+ }
+
+ return _initialized;
+ }
+ }
+
+ public void Subscribe(string channel, Action handler) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+
+ try {
+ lock (_synLock) {
+ _worker.RegisterHandler(channel, handler);
+ }
+ }
+ catch(Exception e) {
+ Logger.Error("An error occured while creating a Worker.", e);
+ }
+ }
+
+ public void Publish(string channel, string message) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+
+ // clear old messages on publish to get a single worker
+ var oldMessages = _messageRecordRepository.Value
+ .Table
+ .Where(x => x.CreatedUtc <= _clock.Value.UtcNow.AddHours(-1))
+ .ToList();
+
+ foreach (var messageRecord in oldMessages) {
+ _messageRecordRepository.Value.Delete(messageRecord);
+ }
+
+ _messageRecordRepository.Value.Create(
+ new MessageRecord {
+ Channel = channel,
+ Message = message,
+ Publisher = _hostNameProvider.Value.GetHostName(),
+ CreatedUtc = _clock.Value.UtcNow
+ }
+ );
+ }
+
+ public void Dispose() {
+ // call only once per connectionstring when appdomain shuts down
+ if (!String.IsNullOrWhiteSpace(_shellSettings.DataConnectionString)) {
+ SqlDependency.Stop(_shellSettings.DataConnectionString);
+ }
+ }
+
+ private string GetHostName() {
+ // use the current host and the process id as two servers could run on the same machine
+ return System.Net.Dns.GetHostName() + ":" + System.Diagnostics.Process.GetCurrentProcess().Id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/Worker.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/Worker.cs
new file mode 100644
index 000000000..4bac8d4ba
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/Worker.cs
@@ -0,0 +1,200 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Threading;
+using System.Web;
+using System.Web.Hosting;
+using Orchard.Environment.Configuration;
+using Orchard.Environment.Extensions;
+using Orchard.Logging;
+using Orchard.MessageBus.Models;
+using Orchard.MessageBus.Services;
+
+namespace Orchard.MessageBus.Brokers.SqlServer {
+ public interface IWorker : IDependency {
+ void Work();
+ void RegisterHandler(string channel, Action handler);
+ }
+
+ [OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")]
+ public class Worker : IWorker, IRegisteredObject {
+
+ private readonly ShellSettings _shellSettings;
+ private readonly IHostNameProvider _hostNameProvider;
+
+ private SqlDependency _dependency;
+
+ private static string commandText = "SELECT Id, Channel, Publisher, Message, CreatedUtc FROM dbo.{0}Orchard_MessageBus_MessageRecord WHERE Id > @Id";
+ private static int lastMessageId = 0;
+ private bool _stopped;
+
+ private Dictionary>> _handlers = new Dictionary>>();
+
+ public Worker(ShellSettings shellSettings, IHostNameProvider hostNameProvider) {
+ _hostNameProvider = hostNameProvider;
+ _shellSettings = shellSettings;
+
+ var tablePrefix = _shellSettings.DataTablePrefix;
+ if (!String.IsNullOrWhiteSpace(tablePrefix)) {
+ tablePrefix += "_";
+ }
+
+ commandText = String.Format(commandText, tablePrefix);
+
+ Logger = NullLogger.Instance;
+ }
+
+ public ILogger Logger { get; set; }
+
+ public void Work() {
+ // exit loop if stop notification as been triggered
+ if (_stopped) {
+ return;
+ }
+
+ try {
+ IEnumerable messages;
+
+ // load and process existing messages
+ using (var connection = new SqlConnection(_shellSettings.DataConnectionString)) {
+ connection.Open();
+
+ var command = CreateCommand(connection);
+ messages = GetMessages(command);
+ }
+
+ ProcessMessages(messages);
+
+ // wait for new messages to be available
+ WaitForWork();
+
+ }
+ catch (Exception e) {
+ Logger.Error("An unexpected error occured while monitoring sql dependencies.", e);
+ }
+ }
+
+ private void DoWork(object sender, SqlNotificationEventArgs eventArgs) {
+ Work();
+ }
+
+ private void WaitForWork() {
+
+ using (var connection = new SqlConnection(_shellSettings.DataConnectionString)) {
+ connection.Open();
+
+ using (var command = CreateCommand(connection)) {
+
+ // create a sql depdendency on the table we are monitoring
+ _dependency = new SqlDependency(command);
+
+ // when new records are present, continue the thread
+ _dependency.OnChange += DoWork;
+
+ // start monitoring the table
+ command.ExecuteNonQuery();
+ }
+ }
+ }
+
+ private void ProcessMessages(IEnumerable messages) {
+
+ // if this is the first time it's executed we just need to get the highest Id
+ if (lastMessageId == 0) {
+ lastMessageId = messages.Max(x => x.Id);
+ return;
+ }
+
+ // process the messages synchronously and in order of publication
+ foreach (var message in messages.OrderBy(x => x.Id)) {
+
+ // save the latest message id so that next time the table is monitored
+ // we get notified for new messages
+ lastMessageId = message.Id;
+
+ // only process handlers registered for the specific channel
+ List> channelHandlers;
+ if (_handlers.TryGetValue(message.Channel, out channelHandlers)) {
+
+ var hostName = _hostNameProvider.GetHostName();
+
+ // execute subscription
+ foreach (var handler in channelHandlers) {
+
+ // ignore messages sent by the current host
+ if (!message.Publisher.Equals(hostName, StringComparison.OrdinalIgnoreCase)) {
+ handler(message.Channel, message.Message);
+ }
+
+ // stop processing other messages if stop has been required
+ if (_stopped) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ public void Stop(bool immediate) {
+ _stopped = true;
+ }
+
+ public void RegisterHandler(string channel, Action handler) {
+ GetHandlersForChannel(channel).Add(handler);
+ }
+
+ private List> GetHandlersForChannel(string channel) {
+ List> channelHandlers;
+
+ if(!_handlers.TryGetValue(channel, out channelHandlers)) {
+ channelHandlers = new List>();
+ _handlers.Add(channel, channelHandlers);
+ }
+
+ return channelHandlers;
+ }
+
+ public SqlCommand CreateCommand(SqlConnection connection) {
+ SqlCommand command = new SqlCommand(commandText, connection);
+
+ SqlParameter param = new SqlParameter("@Id", SqlDbType.Int);
+ param.Direction = ParameterDirection.Input;
+ param.DbType = DbType.Int32;
+ param.Value = lastMessageId;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ public IEnumerable GetMessages(SqlCommand command) {
+ var result = new List();
+
+ try {
+
+ using (var reader = command.ExecuteReader()) {
+ if (reader.HasRows) {
+ while (reader.Read()) {
+ result.Add(new MessageRecord {
+ Id = reader.GetInt32(0),
+ Channel = reader.GetString(1),
+ Publisher = reader.GetString(2),
+ Message = reader.GetString(3),
+ CreatedUtc = reader.GetDateTime(4)
+ });
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ Logger.Error("Could not retreive Sql Broker messages.", e);
+ return Enumerable.Empty();
+ }
+
+ return result;
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Handler/MessageBusHandler.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Handler/MessageBusHandler.cs
new file mode 100644
index 000000000..296350c8d
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Handler/MessageBusHandler.cs
@@ -0,0 +1,9 @@
+using System.Linq;
+using JetBrains.Annotations;
+using Orchard.Data;
+using Orchard.ContentManagement.Handlers;
+
+namespace Orchard.MessageBus.Handlers {
+ public class MessageBusHandler : ContentHandler {
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Models/MessageRecord.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Models/MessageRecord.cs
new file mode 100644
index 000000000..bd6924662
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Models/MessageRecord.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using Orchard.Data.Conventions;
+using Orchard.Environment.Extensions;
+
+namespace Orchard.MessageBus.Models {
+
+ [OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")]
+ public class MessageRecord {
+
+ public virtual int Id { get; set; }
+ public virtual string Publisher { get; set; }
+ public virtual string Channel { get; set; }
+ public virtual DateTime CreatedUtc { get; set; }
+
+ [StringLengthMax]
+ public virtual string Message { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Module.txt b/src/Orchard.Web/Modules/Orchard.MessageBus/Module.txt
new file mode 100644
index 000000000..96dfd8582
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Module.txt
@@ -0,0 +1,27 @@
+Name: Orchard.MessageBus
+AntiForgery: enabled
+Author: The Orchard Team
+Website: http://orchardproject.net
+Version: 1.0
+OrchardVersion: 1.0
+Description: Provides communication APIs for server farms.
+Features:
+ Orchard.MessageBus:
+ Name: Message Bus
+ Description: Reusable API abstractions to communicate in a server farm.
+ Category: Hosting
+ Orchard.MessageBus.DistributedSignals:
+ Name: Distributed Signals
+ Description: Distribute signals cache invalidation calls.
+ Dependencies: Orchard.MessageBus
+ Category: Hosting
+ Orchard.MessageBus.SqlServerServiceBroker:
+ Name: SQL Server Service Broker
+ Description: A message bus implementation using SQL Server Service Broker.
+ Dependencies: Orchard.MessageBus
+ Category: Hosting
+ Orchard.MessageBus.DistributedShellRestart
+ Name: Distributed Shell Restart
+ Description: Distribute shell restarts.
+ Dependencies: Orchard.MessageBus
+ Category: Hosting
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Orchard.MessageBus.csproj b/src/Orchard.Web/Modules/Orchard.MessageBus/Orchard.MessageBus.csproj
new file mode 100644
index 000000000..600bf4d12
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Orchard.MessageBus.csproj
@@ -0,0 +1,154 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 9.0.30729
+ 2.0
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0}
+ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
+ Library
+ Properties
+ Orchard.MessageBus
+ Orchard.MessageBus
+ v4.5.1
+ false
+
+
+ 4.0
+
+
+ false
+
+
+
+
+
+
+ true
+ full
+ false
+ bin\
+ DEBUG;TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ false
+
+
+ pdbonly
+ true
+ bin\
+ TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ false
+
+
+
+
+ False
+ ..\..\..\..\lib\nhibernate\NHibernate.dll
+
+
+
+
+ 3.5
+
+
+
+ False
+ ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}
+ Orchard.Framework
+
+
+ {9916839C-39FC-4CEB-A5AF-89CA7E87119F}
+ Orchard.Core
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+
+
+
+ $(ProjectDir)\..\Manifests
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+ 45979
+ /
+
+
+ False
+ True
+ http://orchard.codeplex.com
+ False
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..220f9c927
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Properties/AssemblyInfo.cs
@@ -0,0 +1,37 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Orchard.MessageBus")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyProduct("Orchard")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("5b79fe08-1e5b-44ab-9323-2c08a7e461e5")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Scripts/Web.config b/src/Orchard.Web/Modules/Orchard.MessageBus/Scripts/Web.config
new file mode 100644
index 000000000..817198995
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Scripts/Web.config
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DefaultMessageBus.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DefaultMessageBus.cs
new file mode 100644
index 000000000..876b87d0a
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DefaultMessageBus.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Threading.Tasks;
+
+namespace Orchard.MessageBus.Services {
+ public class DefaultMessageBus : IMessageBus {
+ private readonly IMessageBroker _messageBroker;
+
+ public DefaultMessageBus(IEnumerable messageBrokers) {
+ _messageBroker = messageBrokers.FirstOrDefault();
+ }
+
+ public void Subscribe(string channel, Action handler) {
+ if (_messageBroker == null) {
+ return;
+ }
+
+ _messageBroker.Subscribe(channel, handler);
+ }
+
+ public void Publish(string channel, string message) {
+ if (_messageBroker == null) {
+ return;
+ }
+
+ _messageBroker.Publish(channel, message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellStarter.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellStarter.cs
new file mode 100644
index 000000000..97bef5f20
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellStarter.cs
@@ -0,0 +1,51 @@
+using System.Linq;
+using Orchard.Caching;
+using Orchard.Environment;
+using Orchard.Environment.Configuration;
+using Orchard.Environment.Extensions;
+
+namespace Orchard.MessageBus.Services {
+
+ public interface IDistributedShellStarter : ISingletonDependency {
+
+ }
+
+ [OrchardFeature("Orchard.MessageBus.DistributedShellRestart")]
+ public class DistributedShellStarter : IDistributedShellStarter, IOrchardShellEvents {
+ private readonly IWorkContextAccessor _workContextAccessor;
+
+ private readonly IMessageBus _messageBus;
+
+ public readonly static string Channel = "ShellChanged";
+
+ public DistributedShellStarter(IMessageBus messageBus, IWorkContextAccessor workContextAccessor) {
+ _messageBus = messageBus;
+ _workContextAccessor = workContextAccessor;
+ }
+
+ public void Activated() {
+ _messageBus.Subscribe(Channel, (channel, message) => {
+ // todo: this only handles changed tenants, we should consider handling started and stopped tenants
+
+ using (var scope = _workContextAccessor.CreateWorkContextScope()) {
+ var shellSettings = scope.Resolve();
+ if (shellSettings != null) {
+
+ // todo: this doesn't work as the new tenants list is lost right after
+ var shellSettingsManagerEventHandler = scope.Resolve();
+ shellSettingsManagerEventHandler.Saved(shellSettings);
+
+ var orchardHost = scope.Resolve() as DefaultOrchardHost;
+ if(orchardHost != null) {
+ var startUpdatedShellsMethod = typeof(DefaultOrchardHost).GetMethod("StartUpdatedShells", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ startUpdatedShellsMethod.Invoke(orchardHost, null);
+ }
+ }
+ }
+ });
+ }
+
+ public void Terminating() {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellTrigger.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellTrigger.cs
new file mode 100644
index 000000000..d5e888fe0
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellTrigger.cs
@@ -0,0 +1,24 @@
+using Orchard.Environment.Configuration;
+using Orchard.Environment.Descriptor;
+using Orchard.Environment.Descriptor.Models;
+using Orchard.Environment.Extensions;
+
+namespace Orchard.MessageBus.Services {
+ [OrchardFeature("Orchard.MessageBus.DistributedShellRestart")]
+ public class DistributedShellTrigger : IShellDescriptorManagerEventHandler, IShellSettingsManagerEventHandler {
+
+ private readonly IMessageBus _messageBus;
+
+ public DistributedShellTrigger(IShellSettingsManager shellSettingsManager, IMessageBus messageBus, IShellSettingsManagerEventHandler shellSettingsManagerEventHandler) {
+ _messageBus = messageBus;
+ }
+
+ void IShellDescriptorManagerEventHandler.Changed(ShellDescriptor descriptor, string tenant) {
+ _messageBus.Publish(DistributedShellStarter.Channel, tenant);
+ }
+
+ void IShellSettingsManagerEventHandler.Saved(ShellSettings settings) {
+ _messageBus.Publish(DistributedShellStarter.Channel, settings.Name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedSignals.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedSignals.cs
new file mode 100644
index 000000000..c24661d5a
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedSignals.cs
@@ -0,0 +1,35 @@
+using Orchard.Caching;
+using Orchard.Environment;
+using Orchard.Environment.Extensions;
+using Orchard.MessageBus.Services;
+using Orchard.Services;
+
+namespace Orchard.MessageBus.Services {
+ [OrchardFeature("Orchard.MessageBus.DistributedSignals")]
+ [OrchardSuppressDependency("Orchard.Caching.Signals")]
+ public class DistributedSignals : Signals, ISignals, IOrchardShellEvents {
+ private readonly IMessageBus _messageBus;
+
+ public DistributedSignals(IMessageBus messageBus) {
+ _messageBus = messageBus;
+ }
+
+ void ISignals.Trigger(T signal) {
+ base.Trigger(signal);
+ _messageBus.Publish("Signal", signal.ToString());
+ }
+
+ IVolatileToken ISignals.When(T signal) {
+ return base.When(signal);
+ }
+
+ public void Activated() {
+ _messageBus.Subscribe("Signal", (channel, message) => {
+ base.Trigger(message);
+ });
+ }
+
+ public void Terminating() {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/HostNameProvider.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/HostNameProvider.cs
new file mode 100644
index 000000000..af4aeb48c
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/HostNameProvider.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Orchard.MessageBus.Services {
+ public class HostNameProvider : IHostNameProvider {
+
+ public string GetHostName() {
+ // use the current host and the process id as two servers could run on the same machine
+ return System.Net.Dns.GetHostName() + ":" + System.Diagnostics.Process.GetCurrentProcess().Id;
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IHostNameProvider.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IHostNameProvider.cs
new file mode 100644
index 000000000..5e1583c0b
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IHostNameProvider.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Orchard.MessageBus.Services {
+ public interface IHostNameProvider : IDependency {
+ string GetHostName();
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBroker.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBroker.cs
new file mode 100644
index 000000000..d7d5a1711
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBroker.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Orchard.MessageBus.Services {
+ public interface IMessageBroker : ISingletonDependency {
+ void Subscribe(string channel, Action handler);
+ void Publish(string channel, string message);
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBus.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBus.cs
new file mode 100644
index 000000000..3e691babf
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBus.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Orchard.MessageBus.Models;
+
+namespace Orchard.MessageBus.Services {
+ public interface IMessageBus : ISingletonDependency {
+ void Subscribe(string channel, Action handler);
+ void Publish(string channel, string message);
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/MessageBusNotificationProvider.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/MessageBusNotificationProvider.cs
new file mode 100644
index 000000000..715e69a4d
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/MessageBusNotificationProvider.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using Orchard.Localization;
+using Orchard.UI.Admin.Notification;
+using Orchard.UI.Notify;
+using System.Linq;
+
+namespace Orchard.MessageBus.Services {
+ public class MessageBusNotificationProvider : INotificationProvider {
+ private readonly IEnumerable _messageBrokers;
+
+ public MessageBusNotificationProvider(IEnumerable messageBrokers) {
+ _messageBrokers = messageBrokers;
+ T = NullLocalizer.Instance;
+ }
+
+ public Localizer T { get; set; }
+
+ public IEnumerable GetNotifications() {
+
+ if (!_messageBrokers.Any()) {
+ yield return new NotifyEntry { Message = T("You need to enable an message bus broker implementation like SQL Server Service Broker."), Type = NotifyType.Warning };
+ }
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/SqlServerBrokerMigrations.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/SqlServerBrokerMigrations.cs
new file mode 100644
index 000000000..cc29857c2
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/SqlServerBrokerMigrations.cs
@@ -0,0 +1,25 @@
+using System;
+using Orchard.ContentManagement.MetaData;
+using Orchard.Data.Migration;
+using Orchard.Environment.Extensions;
+using Orchard.MessageBus.Models;
+
+namespace Orchard.MessageBus {
+ [OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")]
+ public class SqlServerBrokerMigrations : DataMigrationImpl {
+
+ public int Create() {
+
+ SchemaBuilder.CreateTable("MessageRecord",
+ table => table
+ .Column("Id", c => c.PrimaryKey().Identity())
+ .Column("Publisher", c => c.WithLength(255))
+ .Column("Channel", c => c.WithLength(255))
+ .Column("Message", c => c.Unlimited())
+ .Column("CreatedUtc")
+ );
+
+ return 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Styles/Web.config b/src/Orchard.Web/Modules/Orchard.MessageBus/Styles/Web.config
new file mode 100644
index 000000000..817198995
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Styles/Web.config
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Web.config b/src/Orchard.Web/Modules/Orchard.MessageBus/Web.config
new file mode 100644
index 000000000..6fc84bc30
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Web.config
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Orchard.sln b/src/Orchard.sln
index 1a9be3e7f..216369f8b 100644
--- a/src/Orchard.sln
+++ b/src/Orchard.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
-VisualStudioVersion = 12.0.30501.0
+VisualStudioVersion = 12.0.30723.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}"
EndProject
@@ -243,6 +243,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure.MediaServices
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.AuditTrail", "Orchard.Web\Modules\Orchard.AuditTrail\Orchard.AuditTrail.csproj", "{3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.MessageBus", "Orchard.Web\Modules\Orchard.MessageBus\Orchard.MessageBus.csproj", "{ED715544-E649-4F48-B8EE-9368C41C3AC0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeCoverage|Any CPU = CodeCoverage|Any CPU
@@ -1008,6 +1010,13 @@ Global
{3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F}.FxCop|Any CPU.ActiveCfg = Release|Any CPU
{3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Coverage|Any CPU.ActiveCfg = Release|Any CPU
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0}.FxCop|Any CPU.ActiveCfg = Release|Any CPU
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1087,5 +1096,6 @@ Global
{7528BF74-25C7-4ABE-883A-443B4EEC4776} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{14A96B1A-9DC9-44C8-A675-206329E15263} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
+ {ED715544-E649-4F48-B8EE-9368C41C3AC0} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
EndGlobalSection
EndGlobal