Creating Orchard.MessageBus

This commit is contained in:
Sebastien Ros
2014-09-24 18:16:08 -07:00
parent 6efbde45ac
commit 7a7528fa53
21 changed files with 900 additions and 1 deletions

View File

@@ -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 {
/// <summary>
/// A single connection is maintained, and each subscription will be triggered based on the channel it's listening to
/// </summary>
[OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")]
public class SqlServerBroker : IMessageBroker, IDisposable {
private IWorker _worker;
private bool _initialized;
private object _synLock = new object();
private readonly Work<IRepository<MessageRecord>> _messageRecordRepository;
private readonly Work<IClock> _clock;
private readonly Func<IWorker> _workerFactory;
private readonly ShellSettings _shellSettings;
private readonly Work<IHostNameProvider> _hostNameProvider;
public SqlServerBroker(
Work<IRepository<MessageRecord>> messageRecordRepository,
Work<IClock> clock,
Work<IHostNameProvider> hostNameProvider,
Func<IWorker> 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<string, string> 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;
}
}
}

View File

@@ -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<string, string> 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<string, List<Action<string, string>>> _handlers = new Dictionary<string,List<Action<string,string>>>();
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<MessageRecord> 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<MessageRecord> 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<Action<string, string>> 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<string, string> handler) {
GetHandlersForChannel(channel).Add(handler);
}
private List<Action<string, string>> GetHandlersForChannel(string channel) {
List<Action<string, string>> channelHandlers;
if(!_handlers.TryGetValue(channel, out channelHandlers)) {
channelHandlers = new List<Action<string,string>>();
_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<MessageRecord> GetMessages(SqlCommand command) {
var result = new List<MessageRecord>();
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<MessageRecord>();
}
return result;
}
}
}

View File

@@ -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 {
}
}

View File

@@ -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; }
}
}

View File

@@ -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

View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{ED715544-E649-4F48-B8EE-9368C41C3AC0}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Orchard.MessageBus</RootNamespace>
<AssemblyName>Orchard.MessageBus</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>4.0</OldToolsVersion>
<UpgradeBackupLocation />
<TargetFrameworkProfile />
<UseIISExpress>false</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="NHibernate, Version=4.0.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\nhibernate\NHibernate.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Mvc, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.Routing" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Content Include="Web.config" />
<Content Include="Scripts\Web.config" />
<Content Include="Styles\Web.config" />
<Content Include="Properties\AssemblyInfo.cs" />
<Content Include="Module.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
<Folder Include="Views\" />
</ItemGroup>
<ItemGroup>
<Compile Include="Brokers\SqlServer\SqlServerBroker.cs" />
<Compile Include="Brokers\SqlServer\Worker.cs" />
<Compile Include="Services\DistributedShellStarter.cs" />
<Compile Include="Services\DistributedShellTrigger.cs" />
<Compile Include="Services\HostNameProvider.cs" />
<Compile Include="Services\IHostNameProvider.cs" />
<Compile Include="Services\MessageBusNotificationProvider.cs" />
<Compile Include="Handler\MessageBusHandler.cs" />
<Compile Include="SqlServerBrokerMigrations.cs" />
<Compile Include="Models\MessageRecord.cs" />
<Compile Include="Services\DefaultMessageBus.cs" />
<Compile Include="Services\DistributedSignals.cs" />
<Compile Include="Services\IMessageBroker.cs" />
<Compile Include="Services\IMessageBus.cs" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target> -->
<Target Name="AfterBuild" DependsOnTargets="AfterBuildCompiler">
<PropertyGroup>
<AreasManifestDir>$(ProjectDir)\..\Manifests</AreasManifestDir>
</PropertyGroup>
<!-- If this is an area child project, uncomment the following line:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Child" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
-->
<!-- If this is an area parent project, uncomment the following lines:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Parent" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
<CopyAreaManifests ManifestPath="$(AreasManifestDir)" CrossCopy="false" RenameViews="true" />
-->
</Target>
<Target Name="AfterBuildCompiler" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
</Target>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>45979</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://orchard.codeplex.com</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@@ -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")]

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
</staticContent>
<handlers accessPolicy="Script,Read">
<!--
iis7 - for any request to a file exists on disk, return it via native http module.
accessPolicy 'Script' is to allow for a managed 404 page.
-->
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
</handlers>
</system.webServer>
</configuration>

View File

@@ -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<IMessageBroker> messageBrokers) {
_messageBroker = messageBrokers.FirstOrDefault();
}
public void Subscribe(string channel, Action<string, string> handler) {
if (_messageBroker == null) {
return;
}
_messageBroker.Subscribe(channel, handler);
}
public void Publish(string channel, string message) {
if (_messageBroker == null) {
return;
}
_messageBroker.Publish(channel, message);
}
}
}

View File

@@ -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<ShellSettings>();
if (shellSettings != null) {
// todo: this doesn't work as the new tenants list is lost right after
var shellSettingsManagerEventHandler = scope.Resolve<IShellSettingsManagerEventHandler>();
shellSettingsManagerEventHandler.Saved(shellSettings);
var orchardHost = scope.Resolve<IOrchardHost>() 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() {
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>(T signal) {
base.Trigger(signal);
_messageBus.Publish("Signal", signal.ToString());
}
IVolatileToken ISignals.When<T>(T signal) {
return base.When(signal);
}
public void Activated() {
_messageBus.Subscribe("Signal", (channel, message) => {
base.Trigger(message);
});
}
public void Terminating() {
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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<string, string> handler);
void Publish(string channel, string message);
}
}

View File

@@ -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<string, string> handler);
void Publish(string channel, string message);
}
}

View File

@@ -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<IMessageBroker> _messageBrokers;
public MessageBusNotificationProvider(IEnumerable<IMessageBroker> messageBrokers) {
_messageBrokers = messageBrokers;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public IEnumerable<NotifyEntry> 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 };
}
}
}
}

View File

@@ -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<int>("Id", c => c.PrimaryKey().Identity())
.Column<string>("Publisher", c => c.WithLength(255))
.Column<string>("Channel", c => c.WithLength(255))
.Column<string>("Message", c => c.Unlimited())
.Column<DateTime>("CreatedUtc")
);
return 1;
}
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
</staticContent>
<handlers accessPolicy="Script,Read">
<!--
iis7 - for any request to a file exists on disk, return it via native http module.
accessPolicy 'Script' is to allow for a managed 404 page.
-->
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
</handlers>
</system.webServer>
</configuration>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<remove name="host" />
<remove name="pages" />
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<pages pageBaseType="Orchard.Mvc.ViewEngines.Razor.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Web.WebPages" />
<add namespace="System.Linq"/>
<add namespace="System.Collections.Generic"/>
<add namespace="Orchard.Mvc.Html"/>
</namespaces>
</pages>
</system.web.webPages.razor>
<system.web>
<compilation targetFramework="4.5">
<assemblies>
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Web.Mvc, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add assembly="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</assemblies>
</compilation>
</system.web>
</configuration>

View File

@@ -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