Files
Umbraco-CMS/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs

340 lines
14 KiB
C#
Raw Normal View History

// Copyright (c) Umbraco.
// See LICENSE for more details.
Examine 2.0 integration (#10241) * Init commit for examine 2.0 work, most old umb examine tests working, probably a lot that doesn't * Gets Umbraco Examine tests passing and makes some sense out of them, fixes some underlying issues. * Large refactor, remove TaskHelper, rename Notifications to be consistent, Gets all examine/lucene indexes building and startup ordered in the correct way, removes old files, creates new IUmbracoIndexingHandler for abstracting out all index operations for umbraco data, abstracts out IIndexRebuilder, Fixes Stack overflow with LiveModelsProvider and loading assemblies, ports some changes from v8 for startup handling with cold boots, refactors out LastSyncedFileManager * fix up issues with rebuilding and management dashboard. * removes old files, removes NetworkHelper, fixes LastSyncedFileManager implementation to ensure the machine name is used, fix up logging with cold boot state. * Makes MainDom safer to use and makes PublishedSnapshotService lazily register with MainDom * lazily acquire application id (fix unit tests) * Fixes resource casing and missing test file * Ensures caches when requiring internal services for PublishedSnapshotService, UseNuCache is a separate call, shouldn't be buried in AddWebComponents, was also causing issues in integration tests since nucache was being used for the Id2Key service. * For UmbracoTestServerTestBase enable nucache services * Fixing tests * Fix another test * Fixes tests, use TestHostingEnvironment, make Tests.Common use net5, remove old Lucene.Net.Contrib ref. * Fixes up some review notes * Fixes issue with doubly registering PublishedSnapshotService meanig there could be 2x instances of it * Checks for parseexception when executing the query * Use application root instead of duplicating functionality. * Added Examine project to netcore only solution file * Fixed casing issue with LazyLoad, that is not lowercase. * uses cancellationToken instead of bool flag, fixes always reading lastId from the LastSyncedFileManager, fixes RecurringHostedServiceBase so that there isn't an overlapping thread for the same task type * Fix tests * remove legacy test project from solution file * Fix test Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-05-18 18:31:38 +10:00
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Cms.Infrastructure.Serialization;
using Umbraco.Cms.Infrastructure.Sync;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
2017-05-12 14:49:44 +02:00
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class ScopedRepositoryTests : UmbracoIntegrationTest
2017-05-12 14:49:44 +02:00
{
private IUserService UserService => GetRequiredService<IUserService>();
private ILanguageService LanguageService => GetRequiredService<ILanguageService>();
private IDictionaryItemService DictionaryItemService => GetRequiredService<IDictionaryItemService>();
protected override void ConfigureTestServices(IServiceCollection services)
{
// this is what's created core web runtime
var appCaches = new AppCaches(
new DeepCloneAppCache(new ObjectCacheAppCache()),
NoAppCache.Instance,
new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache())));
2017-06-23 18:54:42 +02:00
services.AddUnique(appCaches);
}
protected override void CustomTestSetup(IUmbracoBuilder builder)
{
V15: Remove Nucache (#17166) * Remove nucache reference from Web.Common * Get tests building-ish * Move ReservedFieldNamesService to the right project * Remove IPublishedSnapshotStatus * Added functionality to the INavigationQueryService to get root keys * Fixed issue with navigation * Remove IPublishedSnapshot from UmbracoContext * Begin removing usage of IPublishedSnapshot from PublishedContentExtensions * Fix PublishedContentExtensions.cs * Don't use snapshots in delivery media api * Use IPublishedMediaCache in QueryMediaApiController * Remove more usages of IPublishedSnapshotAccessor * Comment out tests * Remove more usages of PublishedSnapshotAccessor * Remove PublishedSnapshot from property * Fixed test build * Fix errors * Fix some tests * Delete NuCache 🎉 * Implement DatabaseCacheRebuilder * Remove usage of IPublishedSnapshotService * Remove IPublishedSnapshotService * Remove TestPublishedSnapshotAccessor and make tests build * Don't test Snapshot cachelevel It's no longer supported * Fix BlockEditorConverter Element != Element document type * Remember to set cachemanager * Fix RichTextParserTests * Implement TryGetLevel on INavigationQueryService * Fake level and obsolete it in PublishedContent * Remove ChildrenForAllCultures * Hack Path property on PublishedContent * Remove usages of IPublishedSnapshot in tests * More ConvertersTests * Add hybrid cache to integration tests We can actually do this now because we no longer save files on disk * Rename IPublishedSnapshotRebuilder to ICacheRebuilder * Comment out tests * V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125) * Fix .Parent references in PublishedContentExtensions * Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params) * Fix references from the extension methods * Fix dependencies in tests * Replace IPublishedSnapshotAccessor with the content cache in tests * Resolving more .Parent references * Fix unit tests * Obsolete and use extension methods * Remove private method and use extension instead * Moving code around * Fix tests * Fix more references * Cleanup * Fix more usages * Resolve merge conflict * Fix tests * Cleanup * Fix more tests * Fixed unit tests * Cleanup * Replace last usages --------- Co-authored-by: Bjarke Berg <mail@bergmania.dk> * Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider * Post merge fixup * Remo IPublishedSnapshot * Add HasAny to IDocumentUrlService * Fix TextBuilder * Fix modelsbuilder tests * Use explicit types * Implement GetByContentType * Support element types in PublishedContentTypeCache * Run enlistments before publishing notifications * Fix elements cache refreshing * Implement GetByUdi * Implement GetAtRoot * Implement GetByRoute * Reimplement GetRouteById * Fix blocks unit tests * Initialize domain cache on boot * Only return routes with domains on non default lanauges * V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159) * Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media * Introduce GetParent() which uses the right services * Fix obsolete message on .Parent * Obsolete .Children * Fix usages of Children for ApiMediaQueryService * Fix usage in internal * Fix usages in views * Fix indentation * Fix issue with delete language * Update nuget pacakges * Clear elements cache when content is deleted instead of trying to update it * Reset publishedModelFactory * Fixed publishing --------- Co-authored-by: Bjarke Berg <mail@bergmania.dk> Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Co-authored-by: kjac <kja@umbraco.dk>
2024-10-01 15:03:02 +02:00
builder.AddUmbracoHybridCache();
builder.Services.AddUnique<IServerMessenger, LocalServerMessenger>();
builder
.AddNotificationHandler<DictionaryItemDeletedNotification, DictionaryItemDeletedDistributedCacheNotificationHandler>()
.AddNotificationHandler<DictionaryItemSavedNotification, DictionaryItemSavedDistributedCacheNotificationHandler>()
.AddNotificationHandler<LanguageSavedNotification, LanguageSavedDistributedCacheNotificationHandler>()
.AddNotificationHandler<LanguageDeletedNotification, LanguageDeletedDistributedCacheNotificationHandler>()
.AddNotificationHandler<UserSavedNotification, UserSavedDistributedCacheNotificationHandler>()
.AddNotificationHandler<LanguageDeletedNotification, LanguageDeletedDistributedCacheNotificationHandler>()
.AddNotificationHandler<MemberGroupDeletedNotification, MemberGroupDeletedDistributedCacheNotificationHandler>()
.AddNotificationHandler<MemberGroupSavedNotification, MemberGroupSavedDistributedCacheNotificationHandler>();
V15: Remove Nucache (#17166) * Remove nucache reference from Web.Common * Get tests building-ish * Move ReservedFieldNamesService to the right project * Remove IPublishedSnapshotStatus * Added functionality to the INavigationQueryService to get root keys * Fixed issue with navigation * Remove IPublishedSnapshot from UmbracoContext * Begin removing usage of IPublishedSnapshot from PublishedContentExtensions * Fix PublishedContentExtensions.cs * Don't use snapshots in delivery media api * Use IPublishedMediaCache in QueryMediaApiController * Remove more usages of IPublishedSnapshotAccessor * Comment out tests * Remove more usages of PublishedSnapshotAccessor * Remove PublishedSnapshot from property * Fixed test build * Fix errors * Fix some tests * Delete NuCache 🎉 * Implement DatabaseCacheRebuilder * Remove usage of IPublishedSnapshotService * Remove IPublishedSnapshotService * Remove TestPublishedSnapshotAccessor and make tests build * Don't test Snapshot cachelevel It's no longer supported * Fix BlockEditorConverter Element != Element document type * Remember to set cachemanager * Fix RichTextParserTests * Implement TryGetLevel on INavigationQueryService * Fake level and obsolete it in PublishedContent * Remove ChildrenForAllCultures * Hack Path property on PublishedContent * Remove usages of IPublishedSnapshot in tests * More ConvertersTests * Add hybrid cache to integration tests We can actually do this now because we no longer save files on disk * Rename IPublishedSnapshotRebuilder to ICacheRebuilder * Comment out tests * V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125) * Fix .Parent references in PublishedContentExtensions * Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params) * Fix references from the extension methods * Fix dependencies in tests * Replace IPublishedSnapshotAccessor with the content cache in tests * Resolving more .Parent references * Fix unit tests * Obsolete and use extension methods * Remove private method and use extension instead * Moving code around * Fix tests * Fix more references * Cleanup * Fix more usages * Resolve merge conflict * Fix tests * Cleanup * Fix more tests * Fixed unit tests * Cleanup * Replace last usages --------- Co-authored-by: Bjarke Berg <mail@bergmania.dk> * Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider * Post merge fixup * Remo IPublishedSnapshot * Add HasAny to IDocumentUrlService * Fix TextBuilder * Fix modelsbuilder tests * Use explicit types * Implement GetByContentType * Support element types in PublishedContentTypeCache * Run enlistments before publishing notifications * Fix elements cache refreshing * Implement GetByUdi * Implement GetAtRoot * Implement GetByRoute * Reimplement GetRouteById * Fix blocks unit tests * Initialize domain cache on boot * Only return routes with domains on non default lanauges * V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159) * Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media * Introduce GetParent() which uses the right services * Fix obsolete message on .Parent * Obsolete .Children * Fix usages of Children for ApiMediaQueryService * Fix usage in internal * Fix usages in views * Fix indentation * Fix issue with delete language * Update nuget pacakges * Clear elements cache when content is deleted instead of trying to update it * Reset publishedModelFactory * Fixed publishing --------- Co-authored-by: Bjarke Berg <mail@bergmania.dk> Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Co-authored-by: kjac <kja@umbraco.dk>
2024-10-01 15:03:02 +02:00
// builder.AddNotificationHandler<LanguageSavedNotification, PublishedSnapshotServiceEventHandler>();
}
2021-03-25 14:51:00 +01:00
[TestCase(true)]
[TestCase(false)]
public async Task DefaultRepositoryCachePolicy(bool complete)
{
var scopeProvider = (ScopeProvider)ScopeProvider;
var service = (UserService)UserService;
var globalCache = AppCaches.IsolatedCaches.GetOrCreate(typeof(IUser));
var user = (IUser)new User(GlobalSettings, "name", "email", "username", "rawPassword");
service.Save(user);
// User has been saved so the cache has been cleared of it
var globalCached = (IUser)globalCache.Get(GetCacheIdKey<IUser>(user.Key), () => null);
Assert.IsNull(globalCached);
// Get user again to load it into the cache again, this also ensure we don't modify the one that's in the cache.
user = await service.GetAsync(user.Key);
// global cache contains the entity
globalCached = (IUser)globalCache.Get(GetCacheIdKey<IUser>(user.Key), () => null);
Assert.IsNotNull(globalCached);
Assert.AreEqual(user.Id, globalCached.Id);
Assert.AreEqual("name", globalCached.Name);
Assert.IsNull(scopeProvider.AmbientScope);
using (var scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
2017-05-12 14:49:44 +02:00
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
// scope has its own isolated cache
var scopedCache = scope.IsolatedCaches.GetOrCreate(typeof(IUser));
Assert.AreNotSame(globalCache, scopedCache);
user.Name = "changed";
2017-05-12 14:49:44 +02:00
service.Save(user);
// scoped cache contains the "new" entity
var scopeCached = (IUser)scopedCache.Get(GetCacheIdKey<IUser>(user.Id), () => null);
Assert.IsNotNull(scopeCached);
Assert.AreEqual(user.Id, scopeCached.Id);
Assert.AreEqual("changed", scopeCached.Name);
2021-03-29 10:42:42 +02:00
// global cache is unchanged
globalCached = (IUser)globalCache.Get(GetCacheIdKey<IUser>(user.Key), () => null);
2017-05-12 14:49:44 +02:00
Assert.IsNotNull(globalCached);
Assert.AreEqual(user.Id, globalCached.Id);
Assert.AreEqual("name", globalCached.Name);
if (complete)
{
scope.Complete();
2017-05-12 14:49:44 +02:00
}
}
2017-05-12 14:49:44 +02:00
Assert.IsNull(scopeProvider.AmbientScope);
2017-05-12 14:49:44 +02:00
globalCached = (IUser)globalCache.Get(GetCacheIdKey<IUser>(user.Key), () => null);
if (complete)
{
// global cache has been cleared
Assert.IsNull(globalCached);
}
else
{
// global cache has *not* been cleared
2017-05-12 14:49:44 +02:00
Assert.IsNotNull(globalCached);
}
// get again, updated if completed
user = await service.GetAsync(user.Key);
Assert.AreEqual(complete ? "changed" : "name", user.Name);
// global cache contains the entity again
globalCached = (IUser)globalCache.Get(GetCacheIdKey<IUser>(user.Key), () => null);
Assert.IsNotNull(globalCached);
Assert.AreEqual(user.Id, globalCached.Id);
Assert.AreEqual(complete ? "changed" : "name", globalCached.Name);
}
[TestCase(true)]
[TestCase(false)]
public async Task FullDataSetRepositoryCachePolicy(bool complete)
{
var scopeProvider = (ScopeProvider)ScopeProvider;
var service = LanguageService;
var globalCache = AppCaches.IsolatedCaches.GetOrCreate(typeof(ILanguage));
ILanguage lang = new Language("fr-FR", "French (France)");
await service.CreateAsync(lang, Constants.Security.SuperUserKey);
// global cache has been flushed, reload
var globalFullCached = (IEnumerable<ILanguage>)globalCache.Get(GetCacheTypeKey<ILanguage>(), () => null);
Assert.IsNull(globalFullCached);
var reload = await service.GetAsync(lang.IsoCode);
// global cache contains the entity
globalFullCached = (IEnumerable<ILanguage>)globalCache.Get(GetCacheTypeKey<ILanguage>(), () => null);
Assert.IsNotNull(globalFullCached);
var globalCached = globalFullCached.First(x => x.Id == lang.Id);
Assert.IsNotNull(globalCached);
Assert.AreEqual(lang.Id, globalCached.Id);
Assert.AreEqual("fr-FR", globalCached.IsoCode);
Assert.IsNull(scopeProvider.AmbientScope);
using (var scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
2017-05-12 14:49:44 +02:00
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
2017-05-12 14:49:44 +02:00
// scope has its own isolated cache
var scopedCache = scope.IsolatedCaches.GetOrCreate(typeof(ILanguage));
Assert.AreNotSame(globalCache, scopedCache);
// Use IsMandatory of isocode to ensure publishedContent cache is not also rebuild
lang.IsMandatory = true;
await service.UpdateAsync(lang, Constants.Security.SuperUserKey);
2017-05-12 14:49:44 +02:00
// scoped cache has been flushed, reload
var scopeFullCached = (IEnumerable<ILanguage>)scopedCache.Get(GetCacheTypeKey<ILanguage>(), () => null);
Assert.IsNull(scopeFullCached);
reload = await service.GetAsync(lang.IsoCode);
2017-05-12 14:49:44 +02:00
// scoped cache contains the "new" entity
scopeFullCached = (IEnumerable<ILanguage>)scopedCache.Get(GetCacheTypeKey<ILanguage>(), () => null);
Assert.IsNotNull(scopeFullCached);
var scopeCached = scopeFullCached.First(x => x.Id == lang.Id);
Assert.IsNotNull(scopeCached);
Assert.AreEqual(lang.Id, scopeCached.Id);
Assert.AreEqual(true, scopeCached.IsMandatory);
// global cache is unchanged
globalFullCached = (IEnumerable<ILanguage>)globalCache.Get(GetCacheTypeKey<ILanguage>(), () => null);
2017-05-12 14:49:44 +02:00
Assert.IsNotNull(globalFullCached);
globalCached = globalFullCached.First(x => x.Id == lang.Id);
2017-05-12 14:49:44 +02:00
Assert.IsNotNull(globalCached);
Assert.AreEqual(lang.Id, globalCached.Id);
Assert.AreEqual(false, globalCached.IsMandatory);
2017-05-12 14:49:44 +02:00
if (complete)
{
scope.Complete();
2017-05-12 14:49:44 +02:00
}
}
2017-05-12 14:49:44 +02:00
Assert.IsNull(scopeProvider.AmbientScope);
2017-05-12 14:49:44 +02:00
globalFullCached = (IEnumerable<ILanguage>)globalCache.Get(GetCacheTypeKey<ILanguage>(), () => null);
if (complete)
{
// global cache has been cleared
Assert.IsNull(globalFullCached);
}
else
{
// global cache has *not* been cleared
2017-05-12 14:49:44 +02:00
Assert.IsNotNull(globalFullCached);
}
// get again, updated if completed
lang = await service.GetAsync(lang.IsoCode);
Assert.AreEqual(complete, lang.IsMandatory);
// global cache contains the entity again
globalFullCached = (IEnumerable<ILanguage>)globalCache.Get(GetCacheTypeKey<ILanguage>(), () => null);
Assert.IsNotNull(globalFullCached);
globalCached = globalFullCached.First(x => x.Id == lang.Id);
Assert.IsNotNull(globalCached);
Assert.AreEqual(lang.Id, globalCached.Id);
Assert.AreEqual(complete, lang.IsMandatory);
}
[TestCase(true)]
[TestCase(false)]
public async Task SingleItemsOnlyRepositoryCachePolicy(bool complete)
{
var scopeProvider = (ScopeProvider)ScopeProvider;
var languageService = LanguageService;
var dictionaryItemService = DictionaryItemService;
var globalCache = AppCaches.IsolatedCaches.GetOrCreate(typeof(IDictionaryItem));
var lang = new Language("fr-FR", "French (France)");
await languageService.CreateAsync(lang, Constants.Security.SuperUserKey);
var item = (await dictionaryItemService.CreateAsync(
new DictionaryItem("item-key")
{
Translations = new IDictionaryTranslation[] { new DictionaryTranslation(lang, "item-value") }
},
Constants.Security.SuperUserKey)).Result;
// Refresh the keyed cache manually
await dictionaryItemService.GetAsync(item.Key);
await languageService.GetAsync(lang.IsoCode);
// global cache contains the entity
var globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey<IDictionaryItem>(item.Key), () => null);
Assert.IsNotNull(globalCached);
Assert.AreEqual(item.Id, globalCached.Id);
Assert.AreEqual(item.Key, globalCached.Key);
Assert.AreEqual("item-key", globalCached.ItemKey);
Assert.IsNull(scopeProvider.AmbientScope);
using (var scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
2017-05-12 14:49:44 +02:00
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
2017-05-12 14:49:44 +02:00
// scope has its own isolated cache
var scopedCache = scope.IsolatedCaches.GetOrCreate(typeof(IDictionaryItem));
Assert.AreNotSame(globalCache, scopedCache);
2017-05-12 14:49:44 +02:00
item.ItemKey = "item-changed";
await dictionaryItemService.UpdateAsync(item, Constants.Security.SuperUserKey);
2017-05-12 14:49:44 +02:00
// scoped cache contains the "new" entity
// Refresh the keyed cache manually
await dictionaryItemService.GetAsync(item.Key);
var scopeCached = (IDictionaryItem)scopedCache.Get(GetCacheIdKey<IDictionaryItem>(item.Key), () => null);
Assert.IsNotNull(scopeCached);
Assert.AreEqual(item.Id, scopeCached.Id);
Assert.AreEqual("item-changed", scopeCached.ItemKey);
// global cache is unchanged
globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey<IDictionaryItem>(item.Key), () => null);
2017-05-12 14:49:44 +02:00
Assert.IsNotNull(globalCached);
Assert.AreEqual(item.Id, globalCached.Id);
Assert.AreEqual("item-key", globalCached.ItemKey);
if (complete)
{
scope.Complete();
2017-05-12 14:49:44 +02:00
}
}
2017-05-12 14:49:44 +02:00
Assert.IsNull(scopeProvider.AmbientScope);
2017-05-12 14:49:44 +02:00
globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey<IDictionaryItem>(item.Key), () => null);
if (complete)
{
// global cache has been cleared
Assert.IsNull(globalCached);
}
else
{
// global cache has *not* been cleared
2017-05-12 14:49:44 +02:00
Assert.IsNotNull(globalCached);
}
// get again, updated if completed
item = await dictionaryItemService.GetAsync(item.Key);
Assert.AreEqual(complete ? "item-changed" : "item-key", item.ItemKey);
// global cache contains the entity again
globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey<IDictionaryItem>(item.Key), () => null);
Assert.IsNotNull(globalCached);
Assert.AreEqual(item.Id, globalCached.Id);
Assert.AreEqual(complete ? "item-changed" : "item-key", globalCached.ItemKey);
}
public static string GetCacheIdKey<T>(object id) => $"{GetCacheTypeKey<T>()}{id}";
public static string GetCacheTypeKey<T>() => $"uRepo_{typeof(T).Name}_";
2017-05-12 14:49:44 +02:00
public class LocalServerMessenger : ServerMessengerBase
{
public LocalServerMessenger()
: base(false, new SystemTextJsonSerializer())
2017-06-23 18:54:42 +02:00
{
}
2017-06-23 18:54:42 +02:00
public override void SendMessages() { }
public override void Sync() { }
protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable<object> ids = null, string json = null)
{
2017-06-23 18:54:42 +02:00
}
2017-05-12 14:49:44 +02:00
}
}