Adding Orchard.Redis module

This commit is contained in:
Sebastien Ros
2014-09-30 16:25:49 -07:00
parent 7a7528fa53
commit d0c7091964
15 changed files with 537 additions and 184 deletions

View File

@@ -17,11 +17,6 @@ Features:
Description: Activates an Orchard output cache provider that targets Windows Azure Cache.
Dependencies: Orchard.Azure, Orchard.OutputCache
Category: Performance
Orchard.Azure.RedisOutputCache:
Name: Microsoft Azure Redis Output Cache
Description: Activates an Orchard output cache provider that targets Windows Azure Redis Cache.
Dependencies: Orchard.Azure, Orchard.OutputCache
Category: Performance
Orchard.Azure.DatabaseCache:
Name: Microsoft Azure Database Cache
Description: Activates an NHibernate second-level cache provider that targets Microsoft Azure Cache.

View File

@@ -93,9 +93,6 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\nhibernate\NHibernate.dll</HintPath>
</Reference>
<Reference Include="StackExchange.Redis">
<HintPath>..\..\..\..\lib\redis\StackExchange.Redis.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -111,7 +108,6 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="Services\Caching\Output\AzureRedisOutputCacheStorageProvider.cs" />
<Compile Include="Services\Environment\Configuration\DefaultPlatformConfigurationAccessor.cs" />
<Compile Include="Services\Environment\Configuration\IPlatformConfigurationAccessor.cs" />
<Compile Include="Services\TaskLease\AzureMachineNameProvider.cs" />

View File

@@ -1,166 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.ApplicationServer.Caching;
using Newtonsoft.Json;
using Orchard.Azure.Services.Environment.Configuration;
using Orchard.Caching;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.Logging;
using Orchard.OutputCache.Models;
using Orchard.OutputCache.Services;
using StackExchange.Redis;
namespace Orchard.Azure.Services.Caching.Output {
[OrchardFeature("Orchard.Azure.RedisOutputCache")]
[OrchardSuppressDependency("Orchard.OutputCache.Services.DefaultCacheStorageProvider")]
public class AzureRedisCacheStorageProvider : Component, IOutputCacheStorageProvider {
public const string DataCacheKey = "DataCache";
public const string ClientConfigurationKey = "CacheClientConfiguration";
private CacheClientConfiguration _cacheClientConfiguration;
private static ConcurrentDictionary<CacheClientConfiguration, ConnectionMultiplexer> _connectionMultiplexers = new ConcurrentDictionary<CacheClientConfiguration, ConnectionMultiplexer>();
private readonly ICacheManager _cacheManager;
private readonly ShellSettings _shellSettings;
private readonly IPlatformConfigurationAccessor _pca;
private HashSet<string> _keysCache;
public AzureRedisCacheStorageProvider(
ShellSettings shellSettings,
IPlatformConfigurationAccessor pca,
ICacheManager cacheManager) {
_cacheManager = cacheManager;
_shellSettings = shellSettings;
_pca = pca;
Logger = NullLogger.Instance;
}
public CacheClientConfiguration CacheConfiguration {
get {
// the configuration is backed by a field so that we don't call the cacheManager multiple times in the same request
// cache configurations are stored in the cacheManager so that we don't read the config on each request
if (_cacheClientConfiguration == null) {
_cacheClientConfiguration = _cacheManager.Get(ClientConfigurationKey, ctx => {
CacheClientConfiguration cacheConfig;
try {
cacheConfig = CacheClientConfiguration.FromPlatformConfiguration(
_shellSettings.Name,
Constants.OutputCacheSettingNamePrefix,
_pca);
cacheConfig.Validate();
return cacheConfig;
}
catch (Exception ex) {
throw new Exception(String.Format("The {0} configuration settings are missing or invalid.", Constants.OutputCacheFeatureName), ex);
}
});
if (_cacheClientConfiguration == null) {
throw new InvalidOperationException("Could not create a valid cache configuration");
}
}
return _cacheClientConfiguration;
}
}
public IDatabase Cache {
get {
return GetConnection().GetDatabase();
}
}
private ConnectionMultiplexer GetConnection() {
var connectionMultiplexer = _connectionMultiplexers.GetOrAdd(CacheConfiguration, cfg => {
Logger.Debug("Creating a new cache client ({0})", CacheConfiguration.GetHashCode());
var connectionString = cfg.HostIdentifier + ",password=" + cfg.AuthorizationToken;
return ConnectionMultiplexer.Connect(connectionString);
});
return connectionMultiplexer;
}
public void Set(string key, CacheItem cacheItem) {
if (cacheItem.ValidFor <= 0) {
return;
}
var value = JsonConvert.SerializeObject(cacheItem);
Cache.StringSet(GetLocalizedKey(key), value, TimeSpan.FromSeconds(cacheItem.ValidFor));
}
public void Remove(string key) {
Cache.KeyDelete(GetLocalizedKey(key));
}
public void RemoveAll() {
foreach (var key in GetAllKeys()) {
Remove(key);
}
}
public CacheItem GetCacheItem(string key) {
string value = Cache.StringGet(GetLocalizedKey(key));
if(String.IsNullOrEmpty(value)) {
return null;
}
return JsonConvert.DeserializeObject<CacheItem>(value);
}
public IEnumerable<CacheItem> GetCacheItems(int skip, int count) {
foreach (var key in GetAllKeys().Skip(skip).Take(count)) {
var cacheItem = GetCacheItem(key);
// the item could have expired in the meantime
if (cacheItem != null) {
yield return cacheItem;
}
}
}
public int GetCacheItemsCount() {
return GetAllKeys().Count();
}
/// <summary>
/// Creates a namespaced key to support multiple tenants on top of a single Redis connection.
/// </summary>
/// <param name="key">The key to localized.</param>
/// <returns>A localized key based on the tenant name.</returns>
private string GetLocalizedKey(string key) {
return _shellSettings.Name + ":" + key;
}
/// <summary>
/// Returns all the keys for the current tenant.
/// </summary>
/// <returns>The keys for the current tenant.</returns>
private IEnumerable<string> GetAllKeys() {
// prevent the same request from computing the list twice (count + list)
if (_keysCache == null) {
_keysCache = new HashSet<string>();
var prefix = GetLocalizedKey("");
var connection = GetConnection();
foreach (var endPoint in connection.GetEndPoints()) {
var server = GetConnection().GetServer(endPoint);
foreach (var key in server.Keys(pattern: GetLocalizedKey("*"))) {
_keysCache.Add(key.ToString().Substring(prefix.Length));
}
}
}
return _keysCache;
}
}
}

View File

@@ -145,16 +145,16 @@ namespace Orchard.MessageBus.Brokers.SqlServer {
GetHandlersForChannel(channel).Add(handler);
}
private List<Action<string, string>> GetHandlersForChannel(string channel) {
List<Action<string, string>> channelHandlers;
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);
}
if(!_handlers.TryGetValue(channel, out channelHandlers)) {
channelHandlers = new List<Action<string,string>>();
_handlers.Add(channel, channelHandlers);
}
return channelHandlers;
}
return channelHandlers;
}
public SqlCommand CreateCommand(SqlConnection connection) {
SqlCommand command = new SqlCommand(commandText, connection);

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading.Tasks;
using Orchard.Logging;
namespace Orchard.MessageBus.Services {
public class DefaultMessageBus : IMessageBus {
@@ -10,14 +11,19 @@ namespace Orchard.MessageBus.Services {
public DefaultMessageBus(IEnumerable<IMessageBroker> messageBrokers) {
_messageBroker = messageBrokers.FirstOrDefault();
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void Subscribe(string channel, Action<string, string> handler) {
if (_messageBroker == null) {
return;
}
_messageBroker.Subscribe(channel, handler);
Logger.Debug("{0} subscribed to {1}", GetHostName(), channel);
}
public void Publish(string channel, string message) {
@@ -26,6 +32,12 @@ namespace Orchard.MessageBus.Services {
}
_messageBroker.Publish(channel, message);
Logger.Debug("{0} published {1}", GetHostName(), channel);
}
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

@@ -12,4 +12,4 @@ Features:
Orchard.OutputCache.Database:
Description: Stores output cache data in the database.
Category: Performance
Dependencies: Orchard.OutputCache
Dependencies: Orchard.OutputCache

View File

@@ -0,0 +1,10 @@
using StackExchange.Redis;
namespace Orchard.Redis.Configuration {
public interface IRedisConnectionProvider : ISingletonDependency {
ConnectionMultiplexer GetConnection(string connectionString);
string GetConnectionString(string service);
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Concurrent;
using System.Configuration;
using Orchard.Environment.Configuration;
using Orchard.Logging;
using StackExchange.Redis;
namespace Orchard.Redis.Configuration {
public class RedisConnectionProvider : IRedisConnectionProvider {
private static ConcurrentDictionary<string, ConnectionMultiplexer> _connectionMultiplexers = new ConcurrentDictionary<string, ConnectionMultiplexer>();
private readonly ShellSettings _shellSettings;
public RedisConnectionProvider(ShellSettings shellSettings) {
_shellSettings = shellSettings;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public string GetConnectionString(string service) {
var _tenantSettingsKey = _shellSettings.Name + ":" + service;
var _defaultSettingsKey = service;
var connectionStringSettings = ConfigurationManager.ConnectionStrings[_tenantSettingsKey] ?? ConfigurationManager.ConnectionStrings[_defaultSettingsKey];
if (connectionStringSettings == null) {
throw new ConfigurationErrorsException("A connection string is expected for " + service);
}
return connectionStringSettings.ConnectionString;
}
public ConnectionMultiplexer GetConnection(string connectionString) {
if (String.IsNullOrWhiteSpace(connectionString)) {
throw new ArgumentNullException("connectionString");
}
var connectionMultiplexer = _connectionMultiplexers.GetOrAdd(connectionString, cfg => {
Logger.Debug("Creating a new cache client for: {0}", connectionString);
return ConnectionMultiplexer.Connect(connectionString);
});
return connectionMultiplexer;
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.Logging;
using Orchard.MessageBus.Services;
using Orchard.Redis.Configuration;
using StackExchange.Redis;
namespace Orchard.Redis.MessageBus {
[OrchardFeature("Orchard.Redis.MessageBus")]
public class RedisMessageBusBroker : Component, IMessageBroker {
private readonly IRedisConnectionProvider _redisConnectionProvider;
public const string ConnectionStringKey = "Orchard.Redis.MessageBus";
private readonly string _connectionString;
private ConcurrentDictionary<string, ConcurrentBag<Action<string, string>>> _handlers = new ConcurrentDictionary<string, ConcurrentBag<Action<string, string>>>();
public RedisMessageBusBroker(ShellSettings shellSettings, IRedisConnectionProvider redisConnectionProvider) {
_redisConnectionProvider = redisConnectionProvider;
_connectionString = _redisConnectionProvider.GetConnectionString(ConnectionStringKey);
}
public IDatabase Database {
get {
return _redisConnectionProvider.GetConnection(_connectionString).GetDatabase();
}
}
public void Subscribe(string channel, Action<string, string> handler) {
try {
var channelHandlers = _handlers.GetOrAdd(channel, c => {
return new ConcurrentBag<Action<string, string>>();
});
channelHandlers.Add(handler);
var sub = _redisConnectionProvider.GetConnection(_connectionString).GetSubscriber();
sub.Subscribe(channel, (c, m) => {
// the message contains the publisher before the first '/'
var messageTokens = m.ToString().Split('/');
var publisher = messageTokens.FirstOrDefault();
var message = messageTokens.Skip(1).FirstOrDefault();
if (String.IsNullOrWhiteSpace(publisher)) {
return;
}
// ignore self sent messages
if (GetHostName().Equals(publisher, StringComparison.OrdinalIgnoreCase)) {
return;
}
Logger.Debug("Processing {0}", message);
handler(c, message);
});
}
catch (Exception e) {
Logger.Error("An error occured while subscribing to " + channel, e);
}
}
public void Publish(string channel, string message) {
Database.Publish(channel, GetHostName() + "/" + message);
}
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,22 @@
Name: Orchard.Redis
AntiForgery: enabled
Author: The Orchard Team
Website: http://orchardproject.net
Version: 1.0
OrchardVersion: 1.0
Description: Provides Redis integration with Orchard.
Features:
Orchard.Redis
Name: Redis
Description: Used to provide Redis related functionalities.
Category: Hosting
Orchard.Redis.MessageBus:
Name: Redis Message Bus
Description: A message bus implementation using Redis pub/sub.
Category: Hosting
Dependencies: Orchard.MessageBus, Orchard.Redis
Orchard.Redis.OutputCache:
Name: Redis Output Cache
Description: An output cache storage provider using Redis.
Category: Performance
Dependencies: Orchard.OutputCache, Orchard.Redis

View File

@@ -0,0 +1,153 @@
<?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>{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}</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="Newtonsoft.Json">
<HintPath>..\..\..\..\lib\newtonsoft.json\Newtonsoft.Json.dll</HintPath>
</Reference>
<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="StackExchange.Redis">
<HintPath>..\..\..\..\lib\redis\StackExchange.Redis.dll</HintPath>
<Private>True</Private>
</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="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>
<ProjectReference Include="..\Orchard.MessageBus\Orchard.MessageBus.csproj">
<Project>{ed715544-e649-4f48-b8ee-9368c41c3ac0}</Project>
<Name>Orchard.MessageBus</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.OutputCache\Orchard.OutputCache.csproj">
<Project>{6e444ff1-a47c-4cf6-bb3f-507c8ebd776d}</Project>
<Name>Orchard.OutputCache</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Configuration\IRedisConnectionProvider.cs" />
<Compile Include="Configuration\RedisConnectionProvider.cs" />
<Compile Include="MessageBus\RedisMessageBusBroker.cs" />
<Compile Include="OutputCache\RedisOutputCacheStorageProvider.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,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.Logging;
using Orchard.Redis.Configuration;
using Orchard.OutputCache.Models;
using Orchard.OutputCache.Services;
using StackExchange.Redis;
namespace Orchard.Redis.OutputCache {
[OrchardFeature("Orchard.Redis.OutputCache")]
[OrchardSuppressDependency("Orchard.OutputCache.Services.DefaultCacheStorageProvider")]
public class RedisOutputCacheStorageProvider : Component, IOutputCacheStorageProvider {
private readonly ShellSettings _shellSettings;
private readonly IRedisConnectionProvider _redisConnectionProvider;
private HashSet<string> _keysCache;
public const string ConnectionStringKey = "Orchard.Redis.OutputCache";
private readonly string _connectionString;
public RedisOutputCacheStorageProvider(ShellSettings shellSettings, IRedisConnectionProvider redisConnectionProvider) {
_shellSettings = shellSettings;
_redisConnectionProvider = redisConnectionProvider;
_connectionString = _redisConnectionProvider.GetConnectionString(ConnectionStringKey);
Logger = NullLogger.Instance;
}
public IDatabase Database {
get {
return _redisConnectionProvider.GetConnection(_connectionString).GetDatabase();
}
}
public void Set(string key, CacheItem cacheItem) {
if (cacheItem.ValidFor <= 0) {
return;
}
var value = JsonConvert.SerializeObject(cacheItem);
Database.StringSet(GetLocalizedKey(key), value, TimeSpan.FromSeconds(cacheItem.ValidFor));
}
public void Remove(string key) {
Database.KeyDelete(GetLocalizedKey(key));
}
public void RemoveAll() {
foreach (var key in GetAllKeys()) {
Remove(key);
}
}
public CacheItem GetCacheItem(string key) {
string value = Database.StringGet(GetLocalizedKey(key));
if(String.IsNullOrEmpty(value)) {
return null;
}
return JsonConvert.DeserializeObject<CacheItem>(value);
}
public IEnumerable<CacheItem> GetCacheItems(int skip, int count) {
foreach (var key in GetAllKeys().Skip(skip).Take(count)) {
var cacheItem = GetCacheItem(key);
// the item could have expired in the meantime
if (cacheItem != null) {
yield return cacheItem;
}
}
}
public int GetCacheItemsCount() {
return GetAllKeys().Count();
}
/// <summary>
/// Creates a namespaced key to support multiple tenants on top of a single Redis connection.
/// </summary>
/// <param name="key">The key to localized.</param>
/// <returns>A localized key based on the tenant name.</returns>
private string GetLocalizedKey(string key) {
return _shellSettings.Name + ":" + key;
}
/// <summary>
/// Returns all the keys for the current tenant.
/// </summary>
/// <returns>The keys for the current tenant.</returns>
private IEnumerable<string> GetAllKeys() {
// prevent the same request from computing the list twice (count + list)
if (_keysCache == null) {
_keysCache = new HashSet<string>();
var prefix = GetLocalizedKey("");
var connection = _redisConnectionProvider.GetConnection(_connectionString);
foreach (var endPoint in connection.GetEndPoints()) {
var server = connection.GetServer(endPoint);
foreach (var key in server.Keys(pattern: GetLocalizedKey("*"))) {
_keysCache.Add(key.ToString().Substring(prefix.Length));
}
}
}
return _keysCache;
}
}
}

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.Redis")]
[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("f4511dd5-71d3-438d-b5a2-42f0461833be")]
// 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,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

@@ -245,6 +245,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.AuditTrail", "Orcha
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.MessageBus", "Orchard.Web\Modules\Orchard.MessageBus\Orchard.MessageBus.csproj", "{ED715544-E649-4F48-B8EE-9368C41C3AC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Redis", "Orchard.Web\Modules\Orchard.Redis\Orchard.Redis.csproj", "{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeCoverage|Any CPU = CodeCoverage|Any CPU
@@ -1017,6 +1019,16 @@ Global
{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
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.Coverage|Any CPU.ActiveCfg = Release|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.Coverage|Any CPU.Build.0 = Release|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.FxCop|Any CPU.ActiveCfg = Release|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.FxCop|Any CPU.Build.0 = Release|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1097,5 +1109,6 @@ Global
{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}
{2C5EB8B3-A313-413D-BAA0-5C21D2A6EC6E} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
EndGlobalSection
EndGlobal