Created strongly typed ICacheRefresher<T> so that now we can have cache refreshers execute against a

real instance object like IContent, this will dramatically increase performance for bulk publishing when not
in a load balanced environment. Updated methods on DistributedCache to refresh many instances at one time, this also
means we're not re-looking it back up again. Need to update media, users, templates, members and macros to use this new feature too.
This commit is contained in:
Shannon Deminick
2013-02-12 07:35:47 +06:00
parent cb3ac17f43
commit 7f51e01be7
12 changed files with 348 additions and 76 deletions

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Web.Script.Serialization;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using umbraco.interfaces;
@@ -44,7 +45,12 @@ namespace Umbraco.Core.Sync
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
instances.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RefreshById, getNumericId(x)));
//copy local
var idGetter = getNumericId;
MessageSeversForManyObjects(servers, refresher, MessageType.RefreshById,
x => idGetter(x),
instances);
}
public void PerformRefresh<T>(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, Func<T, Guid> getGuidId, params T[] instances)
@@ -52,7 +58,12 @@ namespace Umbraco.Core.Sync
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
instances.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RefreshById, getGuidId(x)));
//copy local
var idGetter = getGuidId;
MessageSeversForManyObjects(servers, refresher, MessageType.RefreshById,
x => idGetter(x),
instances);
}
public void PerformRemove<T>(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, Func<T, int> getNumericId, params T[] instances)
@@ -60,7 +71,12 @@ namespace Umbraco.Core.Sync
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
instances.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RemoveById, getNumericId(x)));
//copy local
var idGetter = getNumericId;
MessageSeversForManyObjects(servers, refresher, MessageType.RemoveById,
x => idGetter(x),
instances);
}
public void PerformRemove(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params int[] numericIds)
@@ -68,7 +84,7 @@ namespace Umbraco.Core.Sync
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
numericIds.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RemoveById, x));
MessageSeversForManyIds(servers, refresher, MessageType.RemoveById, numericIds.Cast<object>());
}
public void PerformRefresh(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params int[] numericIds)
@@ -76,7 +92,7 @@ namespace Umbraco.Core.Sync
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
numericIds.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RefreshById, x));
MessageSeversForManyIds(servers, refresher, MessageType.RefreshById, numericIds.Cast<object>());
}
public void PerformRefresh(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params Guid[] guidIds)
@@ -84,62 +100,155 @@ namespace Umbraco.Core.Sync
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
guidIds.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RefreshById, x));
MessageSeversForManyIds(servers, refresher, MessageType.RefreshById, guidIds.Cast<object>());
}
public void PerformRefreshAll(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher)
{
InvokeDispatchMethod(servers, refresher, MessageType.RefreshAll, null);
MessageSeversForManyIds(servers, refresher, MessageType.RefreshAll, Enumerable.Empty<object>().ToArray());
}
private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, MessageType dispatchType, object id)
private void InvokeMethodOnRefresherInstance<T>(ICacheRefresher refresher, MessageType dispatchType, Func<T, object> getId, IEnumerable<T> instances)
{
if (refresher == null) throw new ArgumentNullException("refresher");
//if we are not, then just invoke the call on the cache refresher
switch (dispatchType)
var stronglyTypedRefresher = refresher as ICacheRefresher<T>;
foreach (var instance in instances)
{
case MessageType.RefreshAll:
refresher.RefreshAll();
break;
case MessageType.RefreshById:
if (id is int)
{
refresher.Refresh((int)id);
}
else if (id is Guid)
{
refresher.Refresh((Guid) id);
}
else
{
throw new InvalidOperationException("The id must be either an int or a Guid");
}
break;
case MessageType.RemoveById:
refresher.Remove((int)id);
break;
//if we are not, then just invoke the call on the cache refresher
switch (dispatchType)
{
case MessageType.RefreshAll:
refresher.RefreshAll();
break;
case MessageType.RefreshById:
if (stronglyTypedRefresher != null)
{
stronglyTypedRefresher.Refresh(instance);
}
else
{
var id = getId(instance);
if (id is int)
{
refresher.Refresh((int)id);
}
else if (id is Guid)
{
refresher.Refresh((Guid)id);
}
else
{
throw new InvalidOperationException("The id must be either an int or a Guid");
}
}
break;
case MessageType.RemoveById:
if (stronglyTypedRefresher != null)
{
stronglyTypedRefresher.Remove(instance);
}
else
{
var id = getId(instance);
refresher.Refresh((int)id);
}
break;
}
}
}
private void InvokeDispatchMethod(
IEnumerable<IServerRegistration> servers,
ICacheRefresher refresher,
MessageType dispatchType,
object id)
private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, MessageType dispatchType, IEnumerable<object> ids)
{
if (refresher == null) throw new ArgumentNullException("refresher");
//if it is a refresh all we'll do it here since ids will be null or empty
if (dispatchType == MessageType.RefreshAll)
{
refresher.RefreshAll();
}
else
{
foreach (var id in ids)
{
//if we are not, then just invoke the call on the cache refresher
switch (dispatchType)
{
case MessageType.RefreshById:
if (id is int)
{
refresher.Refresh((int)id);
}
else if (id is Guid)
{
refresher.Refresh((Guid)id);
}
else
{
throw new InvalidOperationException("The id must be either an int or a Guid");
}
break;
case MessageType.RemoveById:
refresher.Remove((int)id);
break;
}
}
}
}
private void MessageSeversForManyObjects<T>(
IEnumerable<IServerRegistration> servers,
ICacheRefresher refresher,
MessageType dispatchType,
Func<T, object> getId,
IEnumerable<T> instances)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
if (!(id is int) && (!(id is Guid))) throw new ArgumentException("The id must be either an int or a Guid");
//Now, check if we are using Distrubuted calls. If there are no servers in the list then we
// can definitely not distribute.
if (!_useDistributedCalls || !servers.Any())
{
//if we are not, then just invoke the call on the cache refresher
InvokeMethodOnRefresherInstance(refresher, dispatchType, id);
InvokeMethodOnRefresherInstance(refresher, dispatchType, getId, instances);
return;
}
//if we are distributing calls then we'll need to do it by id
MessageSeversForManyIds(servers, refresher, dispatchType, instances.Select(getId));
}
private void MessageSeversForManyIds(
IEnumerable<IServerRegistration> servers,
ICacheRefresher refresher,
MessageType dispatchType,
IEnumerable<object> ids)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
Type arrayType = null;
foreach (var id in ids)
{
if (!(id is int) && (!(id is Guid)))
throw new ArgumentException("The id must be either an int or a Guid");
if (arrayType == null)
arrayType = id.GetType();
if (arrayType != id.GetType())
throw new ArgumentException("The array must contain the same type of " + arrayType);
}
//Now, check if we are using Distrubuted calls. If there are no servers in the list then we
// can definitely not distribute.
if (!_useDistributedCalls || !servers.Any())
{
//if we are not, then just invoke the call on the cache refresher
InvokeMethodOnRefresherInstance(refresher, dispatchType, ids);
return;
}
@@ -152,9 +261,8 @@ namespace Umbraco.Core.Sync
LogStartDispatch();
var nodes = servers;
// Go through each configured node submitting a request asynchronously
foreach (var n in nodes)
foreach (var n in servers)
{
//set the server address
cacheRefresher.Url = n.ServerAddress;
@@ -168,21 +276,29 @@ namespace Umbraco.Core.Sync
refresher.UniqueIdentifier, _login, _password, null, null));
break;
case MessageType.RefreshById:
IAsyncResult result;
if (id is int)
if (arrayType == typeof(int))
{
result = cacheRefresher.BeginRefreshById(refresher.UniqueIdentifier, (int) id, _login, _password, null, null);
var serializer = new JavaScriptSerializer();
var jsonIds = serializer.Serialize(ids.Cast<int>().ToArray());
//we support bulk loading of Integers
var result = cacheRefresher.BeginRefreshByIds(refresher.UniqueIdentifier, jsonIds, _login, _password, null, null);
asyncResultsList.Add(result);
}
else
{
result = cacheRefresher.BeginRefreshByGuid(refresher.UniqueIdentifier, (Guid)id, _login, _password, null, null);
//we don't currently support bulk loading of GUIDs (not even sure if we have any Guid ICacheRefreshers)
//so we'll just iterate
asyncResultsList.AddRange(
ids.Select(i => cacheRefresher.BeginRefreshByGuid(
refresher.UniqueIdentifier, (Guid) i, _login, _password, null, null)));
}
asyncResultsList.Add(result);
break;
break;
case MessageType.RemoveById:
asyncResultsList.Add(
cacheRefresher.BeginRemoveById(
refresher.UniqueIdentifier, (int)id, _login, _password, null, null));
//we don't currently support bulk removing so we'll iterate
asyncResultsList.AddRange(
ids.Select(i => cacheRefresher.BeginRemoveById(
refresher.UniqueIdentifier, (int)i, _login, _password, null, null)));
break;
}
}
@@ -207,7 +323,7 @@ namespace Umbraco.Core.Sync
cacheRefresher.EndRefreshAll(t);
break;
case MessageType.RefreshById:
if (id is int)
if (arrayType == typeof(int))
{
cacheRefresher.EndRefreshById(t);
}

View File

@@ -0,0 +1,18 @@
using umbraco.interfaces;
namespace Umbraco.Core.Sync
{
/// <summary>
/// Strongly type cache refresher that is able to refresh cache of real instances of objects as well as IDs
/// </summary>
/// <typeparam name="T"></typeparam>
/// <remarks>
/// This is much better for performance when we're not running in a load balanced environment so we can refresh the cache
/// against a already resolved object instead of looking the object back up by id.
/// </remarks>
interface ICacheRefresher<T> : ICacheRefresher
{
void Refresh(T instance);
void Remove(T instance);
}
}

View File

@@ -1,4 +1,5 @@
using System.Web.Services;
using System.Collections.Generic;
using System.Web.Services;
using Umbraco.Core.IO;
namespace Umbraco.Core.Sync
@@ -73,7 +74,11 @@ namespace Umbraco.Core.Sync
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshById", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshById",
RequestNamespace = "http://umbraco.org/webservices/",
ResponseNamespace = "http://umbraco.org/webservices/",
Use = System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void RefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password)
{
this.Invoke("RefreshById", new object[] {
@@ -99,6 +104,40 @@ namespace Umbraco.Core.Sync
this.EndInvoke(asyncResult);
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByIds",
RequestNamespace = "http://umbraco.org/webservices/",
ResponseNamespace = "http://umbraco.org/webservices/",
Use = System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void RefreshByIds(System.Guid uniqueIdentifier, string jsonIds, string Login, string Password)
{
this.Invoke("RefreshByIds", new object[] {
uniqueIdentifier,
jsonIds,
Login,
Password});
}
/// <remarks/>
public System.IAsyncResult BeginRefreshByIds(System.Guid uniqueIdentifier, string jsonIds, string Login, string Password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("RefreshByIds", new object[] {
uniqueIdentifier,
jsonIds,
Login,
Password}, callback, asyncState);
}
/// <remarks/>
public void EndRefreshByIds(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveById", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void RemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password)

View File

@@ -61,6 +61,7 @@
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Transactions" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Helpers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll</HintPath>
@@ -669,6 +670,7 @@
<Compile Include="Services\ServiceContext.cs" />
<Compile Include="Services\UserService.cs" />
<Compile Include="Sync\DefaultServerMessenger.cs" />
<Compile Include="Sync\ICacheRefresher.cs" />
<Compile Include="Sync\ServerSyncWebServiceClient.cs">
<SubType>Component</SubType>
</Compile>

View File

@@ -45,6 +45,19 @@ namespace Umbraco.Tests.Sync
Assert.AreEqual(10, ((TestServerMessenger)ServerMessengerResolver.Current.Messenger).IntIdsRefreshed.Count);
}
[Test]
public void RefreshIntIdFromObject()
{
for (var i = 0; i < 10; i++)
{
DistributedCache.Instance.Refresh(
Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"),
x => x.Id,
new TestObjectWithId{Id = i});
}
Assert.AreEqual(10, ((TestServerMessenger)ServerMessengerResolver.Current.Messenger).IntIdsRefreshed.Count);
}
[Test]
public void RefreshGuidId()
{
@@ -77,6 +90,11 @@ namespace Umbraco.Tests.Sync
#region internal test classes
internal class TestObjectWithId
{
public int Id { get; set; }
}
internal class TestCacheRefresher : ICacheRefresher
{
public Guid UniqueIdentifier

View File

@@ -102,28 +102,22 @@ namespace Umbraco.Web.Cache
static void MediaServiceTrashing(IMediaService sender, Core.Events.MoveEventArgs<Core.Models.IMedia> e)
{
DistributedCache.Instance.RemoveMediaCache(e.Entity.Id);
DistributedCache.Instance.RemoveMediaCache(e.Entity);
}
static void MediaServiceMoving(IMediaService sender, Core.Events.MoveEventArgs<Core.Models.IMedia> e)
{
DistributedCache.Instance.RefreshMediaCache(e.Entity.Id);
DistributedCache.Instance.RefreshMediaCache(e.Entity);
}
static void MediaServiceDeleting(IMediaService sender, Core.Events.DeleteEventArgs<Core.Models.IMedia> e)
{
foreach (var item in e.DeletedEntities)
{
DistributedCache.Instance.RemoveMediaCache(item.Id);
}
DistributedCache.Instance.RemoveMediaCache(e.DeletedEntities.ToArray());
}
static void MediaServiceSaved(IMediaService sender, Core.Events.SaveEventArgs<Core.Models.IMedia> e)
{
foreach (var item in e.SavedEntities)
{
DistributedCache.Instance.RefreshMediaCache(item.Id);
}
DistributedCache.Instance.RefreshMediaCache(e.SavedEntities.ToArray());
}
static void MemberBeforeDelete(Member sender, DeleteEventArgs e)

View File

@@ -132,6 +132,23 @@ namespace Umbraco.Web.Cache
GetRefresherById(factoryGuid),
id);
}
/// <summary>
/// Sends a request to all registered load-balanced servers to remove the node specified
/// using the specified ICacheRefresher with the guid factoryGuid.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="factoryGuid"></param>
/// <param name="getNumericId"></param>
/// <param name="instances"></param>
public void Remove<T>(Guid factoryGuid, Func<T, int> getNumericId, params T[] instances)
{
ServerMessengerResolver.Current.Messenger.PerformRemove<T>(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
getNumericId,
instances);
}
private static ICacheRefresher GetRefresherById(Guid uniqueIdentifier)
{

View File

@@ -53,30 +53,40 @@ namespace Umbraco.Web.Cache
/// Refreshes the cache amongst servers for a page
/// </summary>
/// <param name="dc"></param>
/// <param name="pageId"></param>
public static void RefreshPageCache(this DistributedCache dc, int pageId)
/// <param name="documentId"></param>
public static void RefreshPageCache(this DistributedCache dc, int documentId)
{
dc.Refresh(new Guid(DistributedCache.PageCacheRefresherId), pageId);
}
dc.Refresh(new Guid(DistributedCache.PageCacheRefresherId), documentId);
}
/// <summary>
/// Refreshes page cache for all instances passed in
/// </summary>
/// <param name="dc"></param>
/// <param name="content"></param>
public static void RefreshPageCache(this DistributedCache dc, IEnumerable<IContent> content)
public static void RefreshPageCache(this DistributedCache dc, params IContent[] content)
{
dc.Refresh(new Guid(DistributedCache.PageCacheRefresherId), x => x.Id, content.ToArray());
dc.Refresh(new Guid(DistributedCache.PageCacheRefresherId), x => x.Id, content);
}
/// <summary>
/// Removes the cache amongst servers for a page
/// </summary>
/// <param name="dc"></param>
/// <param name="pageId"></param>
public static void RemovePageCache(this DistributedCache dc, int pageId)
/// <param name="content"></param>
public static void RemovePageCache(this DistributedCache dc, params IContent[] content)
{
dc.Remove(new Guid(DistributedCache.PageCacheRefresherId), pageId);
dc.Remove(new Guid(DistributedCache.PageCacheRefresherId), x => x.Id, content);
}
/// <summary>
/// Removes the cache amongst servers for a page
/// </summary>
/// <param name="dc"></param>
/// <param name="documentId"></param>
public static void RemovePageCache(this DistributedCache dc, int documentId)
{
dc.Remove(new Guid(DistributedCache.PageCacheRefresherId), documentId);
}
/// <summary>
@@ -109,6 +119,16 @@ namespace Umbraco.Web.Cache
dc.Refresh(new Guid(DistributedCache.MediaCacheRefresherId), mediaId);
}
/// <summary>
/// Refreshes the cache amongst servers for a media item
/// </summary>
/// <param name="dc"></param>
/// <param name="media"></param>
public static void RefreshMediaCache(this DistributedCache dc, params IMedia[] media)
{
dc.Refresh(new Guid(DistributedCache.MediaCacheRefresherId), x => x.Id, media);
}
/// <summary>
/// Removes the cache amongst servers for a media item
/// </summary>
@@ -119,6 +139,16 @@ namespace Umbraco.Web.Cache
dc.Remove(new Guid(DistributedCache.MediaCacheRefresherId), mediaId);
}
/// <summary>
/// Removes the cache amongst servers for media items
/// </summary>
/// <param name="dc"></param>
/// <param name="media"></param>
public static void RemoveMediaCache(this DistributedCache dc, params IMedia[] media)
{
dc.Remove(new Guid(DistributedCache.MediaCacheRefresherId), x => x.Id, media);
}
/// <summary>
/// Refreshes the cache amongst servers for a macro item
/// </summary>

View File

@@ -1,5 +1,8 @@
using System;
using Umbraco.Core.Models;
using Umbraco.Core.Sync;
using umbraco;
using umbraco.cms.businesslogic.web;
using umbraco.interfaces;
using umbraco.presentation.cache;
@@ -12,7 +15,7 @@ namespace Umbraco.Web.Cache
/// If Load balancing is enabled (by default disabled, is set in umbracoSettings.config) PageCacheRefresher will be called
/// everytime content is added/updated/removed to ensure that the content cache is identical on all load balanced servers
/// </remarks>
public class PageCacheRefresher : ICacheRefresher
public class PageCacheRefresher : ICacheRefresher<IContent>
{
/// <summary>
/// Gets the unique identifier of the CacheRefresher.
@@ -69,5 +72,15 @@ namespace Umbraco.Web.Cache
{
content.Instance.ClearDocumentCache(id);
}
public void Refresh(IContent instance)
{
content.Instance.UpdateDocumentCache(new Document(instance));
}
public void Remove(IContent instance)
{
content.Instance.ClearDocumentCache(new Document(instance));
}
}
}

View File

@@ -66,7 +66,7 @@ namespace Umbraco.Web.Strategies.Publishing
/// </summary>
private void UpdateMultipleContentCache(IEnumerable<IContent> content)
{
DistributedCache.Instance.RefreshPageCache(content);
DistributedCache.Instance.RefreshPageCache(content.ToArray());
}
/// <summary>
@@ -74,7 +74,7 @@ namespace Umbraco.Web.Strategies.Publishing
/// </summary>
private void UpdateSingleContentCache(IContent content)
{
DistributedCache.Instance.RefreshPageCache(content.Id);
DistributedCache.Instance.RefreshPageCache(content);
}
}
}

View File

@@ -51,7 +51,7 @@ namespace Umbraco.Web.Strategies.Publishing
/// </summary>
private void UnPublishSingle(IContent content)
{
DistributedCache.Instance.RemovePageCache(content.Id);
DistributedCache.Instance.RemovePageCache(content);
}
}
}

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.Services;
using System.Xml;
using Umbraco.Core;
@@ -82,6 +84,29 @@ namespace umbraco.presentation.webservices
}
}
/// <summary>
/// Refreshes objects for all Ids matched in the json string
/// </summary>
/// <param name="uniqueIdentifier"></param>
/// <param name="jsonIds">A JSON Serialized string of ids to match</param>
/// <param name="Login"></param>
/// <param name="Password"></param>
[WebMethod]
public void RefreshByIds(Guid uniqueIdentifier, string jsonIds, string Login, string Password)
{
var serializer = new JavaScriptSerializer();
var ids = serializer.Deserialize<int[]>(jsonIds);
if (BusinessLogic.User.validateCredentials(Login, Password))
{
var cr = CacheRefreshersResolver.Current.GetById(uniqueIdentifier);
foreach (var i in ids)
{
cr.Refresh(i);
}
}
}
[WebMethod]
public void RemoveById(Guid uniqueIdentifier, int Id, string Login, string Password) {