What is the proper way to deserialize a RedisMessage through BookSleeve? - c#-4.0

I have created the following singleton class to handle a Redis connection, and expose BookSleeve functionality:
public class RedisConnection
{
private static RedisConnection _instance = null;
private BookSleeve.RedisSubscriberConnection _channel;
private readonly int _db;
private readonly string[] _keys; // represent channel name
public BookSleeve.RedisConnection _connection;
/// <summary>
/// Initialize all class parameters
/// </summary>
private RedisConnection(string serverList, int db, IEnumerable<string> keys)
{
_connection = ConnectionUtils.Connect(serverList);
_db = db;
_keys = keys.ToArray();
_connection.Closed += OnConnectionClosed;
_connection.Error += OnConnectionError;
// Create a subscription channel in redis
_channel = _connection.GetOpenSubscriberChannel();
// Subscribe to the registered connections
_channel.Subscribe(_keys, OnMessage);
// Dirty hack but it seems like subscribe returns before the actual
// subscription is properly setup in some cases
while (_channel.SubscriptionCount == 0)
{
Thread.Sleep(500);
}
}
/// <summary>
/// Do something when a message is received
/// </summary>
/// <param name="key"></param>
/// <param name="data"></param>
private void OnMessage(string key, byte[] data)
{
// since we are just interested in pub/sub, no data persistence is active
// however, if the persistence flag is enabled, here is where we can save the data
// The key is the stream id (channel)
//var message = RedisMessage.Deserialize(data);
var message = Helpers.BytesToString(data);
if (true) ;
//_publishQueue.Enqueue(() => OnReceived(key, (ulong)message.Id, message.Messages));
}
public static RedisConnection GetInstance(string serverList, int db, IEnumerable<string> keys)
{
if (_instance == null)
{
// could include some sort of lock for thread safety
_instance = new RedisConnection(serverList, db, keys);
}
return _instance;
}
private static void OnConnectionClosed(object sender, EventArgs e)
{
// Should we auto reconnect?
if (true)
{
;
}
}
private static void OnConnectionError(object sender, BookSleeve.ErrorEventArgs e)
{
// How do we bubble errors?
if (true)
{
;
}
}
}
In OnMessage(), var message = RedisMessage.Deserialize(data); is commented out due to the following error:
RedisMessage is inaccessible due to its protection level.
RedisMessage is an abstract class in BookSleeve, and I'm a little stuck on why I cannot use this.
I ran into this issue because as I send messages to a channel (pub/sub) I may want to do something with them in OnMessage() - for example, if a persistence flag is set, I may choose to begin recording data. The issue is that the data is serialized at this point, and I wish to deserialize it (to string) and and persist it in Redis.
Here is my test method:
[TestMethod]
public void TestRedisConnection()
{
// setup parameters
string serverList = "dbcache1.local:6379";
int db = 0;
List<string> eventKeys = new List<string>();
eventKeys.Add("Testing.FaucetChannel");
BookSleeve.RedisConnection redisConnection = Faucet.Services.RedisConnection.GetInstance(serverList, db, eventKeys)._connection;
// broadcast to a channel
redisConnection.Publish("Testing.FaucetChannel", "a published value!!!");
}
Since I wasn't able to make use of the Deserialize() method, I created a static helper class:
public static class Helpers
{
/// <summary>
/// Serializes a string to bytes
/// </summary>
/// <param name="val"></param>
/// <returns></returns>
public static byte[] StringToBytes(string str)
{
try
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
catch (Exception ex)
{
/* handle exception omitted */
return null;
}
}
/// <summary>
/// Deserializes bytes to string
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string BytesToString(byte[] bytes)
{
string set;
try
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
catch (Exception ex)
{
// removed error handling logic!
return null;
}
}
}
Unfortunately, this is not properly deserializing the string back to its original form, and what I'm getting is something like this: ⁡異汢獩敨⁤慶畬㩥ㄠ, rather than the actual original text.
Suggestions?

RedisMessage represents a pending request that is about to be sent to the server; there are a few concrete implementations of this, typically relating to the nature and quantity of the parameters to be sent. It makes no sense to "deserialize" (or even "serialize") a RedisMessage - that is not their purpose. The only thing it is sensible to do is to Write(...) them to a Stream.
If you want information about a RedisMessage, then .ToString() has an overview, but this is not round-trippable and is frankly intended for debugging.
RedisMessage is an internal class; an implementation detail. Unless you're working on a pull request to the core code, you should never need to interact with a RedisMessage.
At a similar level, there is RedisResult which represents a response coming back from the server. If you want a quick way of getting data from that, fortunately that is much simpler:
object val = result.Parse(true);
(the true means "speculatively test to see if the data looks like a string"). But again, this is an internal implementation detail that you should not have to work with.

Obviously this was an encoding type issue, and in the meantime, with a little glance at this link, I simply added the encoding type of UTF8, and the output looks fine:
#region EncodingType enum
/// <summary>
/// Encoding Types.
/// </summary>
public enum EncodingType
{
ASCII,
Unicode,
UTF7,
UTF8
}
#endregion
#region ByteArrayToString
/// <summary>
/// Converts a byte array to a string using Unicode encoding.
/// </summary>
/// <param name="bytes">Array of bytes to be converted.</param>
/// <returns>string</returns>
public static string ByteArrayToString(byte[] bytes)
{
return ByteArrayToString(bytes, EncodingType.Unicode);
}
/// <summary>
/// Converts a byte array to a string using specified encoding.
/// </summary>
/// <param name="bytes">Array of bytes to be converted.</param>
/// <param name="encodingType">EncodingType enum.</param>
/// <returns>string</returns>
public static string ByteArrayToString(byte[] bytes, EncodingType encodingType)
{
System.Text.Encoding encoding=null;
switch (encodingType)
{
case EncodingType.ASCII:
encoding=new System.Text.ASCIIEncoding();
break;
case EncodingType.Unicode:
encoding=new System.Text.UnicodeEncoding();
break;
case EncodingType.UTF7:
encoding=new System.Text.UTF7Encoding();
break;
case EncodingType.UTF8:
encoding=new System.Text.UTF8Encoding();
break;
}
return encoding.GetString(bytes);
}
#endregion
--UPDATE--
Even simpler: var message = Encoding.UTF8.GetString(data);

Related

How can I get a custom processing status screen to show progress or records processed

I have a custom process screen which works pretty much as expected, except that the status / monitor screen that pops up during processing doesn't show the progress, or the records processed as it does on other, stock processing screens.
Here's what a stock processing popup looks like:
And here's what my custom popup looks like. It doesn't show 'Remaining' and pretty much just looks like this until it's finished:
And then shows this when finished:
Is there something I need to add to my processing screen to get this functionality?
Thanks much...
In your processing method are you using PXProcessing.SetCurrentItem(object) to set the current processing item and then the PXProcessing.SetProcessed() or PXProcessing.SetError(ExceptionObject) to set the status of the current processing item.
Example:
public PXProcessing<SOLine> linesToBeProcessed;
protected virtual void ProcessOrderLines(List<SOLines> lines)
{
foreach (SOLine line in lines)
{
PXProcessing<SOLine>.SetCurrentItem(line);
try
{
//logic
PXProcessing<SOLine>.SetProcessed();
}
catch (Exception ex)
{
PXProcessing<SOLine>.SetError(ex);
}
}
}
Note: The DACNAME to use in these methods is the main DAC used in the PXProcessing view that was declared.
This is a chunk of code from our Acumatica Surveys project that depicts how we control the messaging for various types.
Notice the SetInfo, SetWarning, and SetError
Let me know if this helps?
/// <summary>
/// This method will create a new collector record and invoke a notification on it.
/// </summary>
/// <param name="surveyUser"></param>
/// <param name="graph"></param>
/// <param name="surveyCurrent"></param>
/// <param name="surveyUserList"></param>
/// <param name="filter"></param>
/// <remarks>
/// </remarks>
/// <returns>
/// Whether or not an error has occured within the process which is used by the main calling process to throw a final exception at the end of the process
/// </returns>
private static bool SendNew(SurveyUser surveyUser, SurveyCollectorMaint graph, Survey surveyCurrent, List<SurveyUser> surveyUserList, SurveyFilter filter) {
bool errorOccurred = false;
try {
string sCollectorStatus = (surveyUser.UsingMobileApp.GetValueOrDefault(false)) ?
SurveyResponseStatus.CollectorSent : SurveyResponseStatus.CollectorNew;
graph.Clear();
SurveyCollector surveyCollector = new SurveyCollector {
CollectorName =
$"{surveyCurrent.SurveyName} {PXTimeZoneInfo.Now:yyyy-MM-dd hh:mm:ss}",
SurveyID = surveyUser.SurveyID,
UserID = surveyUser.UserID,
CollectedDate = null,
ExpirationDate = CalculateExpirationDate(filter.DurationTimeSpan),
CollectorStatus = sCollectorStatus
};
surveyCollector = graph.Collector.Insert(surveyCollector);
graph.Persist();
SendNotification(surveyUser, surveyCollector);
if (sCollectorStatus == SurveyResponseStatus.CollectorSent) {
PXProcessing<SurveyUser>.SetInfo(surveyUserList.IndexOf(surveyUser), Messages.SurveySent);
} else {
PXProcessing<SurveyUser>.SetWarning(surveyUserList.IndexOf(surveyUser), Messages.NoDeviceError);
}
} catch (AggregateException ex) {
errorOccurred = true;
var message = string.Join(";", ex.InnerExceptions.Select(e => e.Message));
PXProcessing<SurveyUser>.SetError(surveyUserList.IndexOf(surveyUser), message);
} catch (Exception e) {
errorOccurred = true;
PXProcessing<SurveyUser>.SetError(surveyUserList.IndexOf(surveyUser), e);
}
return errorOccurred;
}

UIActivityIndicatorView.StartAnimating overriding UIActivityIndicatorView.Hidden binding

I am creating a UIActivityIndicatorView in my Controller.ViewDidLoad
UIActivityIndicatorView spinner = new UIActivityIndicatorView();
spinner.StartAnimating();
spinner.Hidden = true;
this.Add(spinner);
Then I am binding it with MVVMCross
var set = this.CreateBindingSet<TipView, TipViewModel>();
set.Bind(spinner).For(v => v.Hidden).To(vm => vm.IsBusy).WithConversion("Inverse");
When the View initially loads the UIActivityIndicatorView is spinning and visible. This is incorrect as the IsBusy property is being explicitly set to False in the ViewModel's Init(). I can see this happening and I can see the Converter invert the value.
I know the binding is properly connected because if I fire a command that updates the IsBusy property the Indicator is shown and hidden as I would expect. It is just the initial state that is incorrect.
The StartAnimating method seems to cause the Hidden flag to be overridden. If I do not call StartAnimating the Indicator hides and shows as expected. Of course that means I have a non animating
Indicator.
I could get a WeakReference to the VM, listen to PropertyChanged and call StartAnimating but that seems a bit rubbish.
Does anyone have any better ideas?
Some options you can do:
Subscribe to PropertyChanged changes and write custom code in the event handler (as you suggest in your question)
Inherit from UIActivityIndicatorView and write a public get;set; property which provides the composite functionality (calling Start and Hidden) in the set handler
public class MyIndicatorView : UIActivityIndicatorView {
// ctors
private bool _superHidden;
public bool SuperHidden {
get { return _supperHidden; }
set { _superHidden = value; if (!value) StartAnimating() else StopAnimating(); Hidden = value; }
}
}
Provide a View public get;set; property and put the composite functionality in that (e.g. set.Bind(this).For(v => v.MyAdvanced)...
private bool _myAdvanced;
public bool MyAdvanced {
get { return myAdvanced; }
set { myAdvanced = value; if (!value) _spinner.StartAnimating() else _spinner.StopAnimating(); _spinner.Hidden = value; }
}
Write a custom binding for Hidden which replaces the default functionality and contains the combined Start and Hidden calls (for more on custom bindings, there's a couple of N+1 tutorials)
After reading #slodge's reply I went down the road of Weak Event Listener and ran the code to Hide and StartAnimating in the View. Having copy and pasted that approach 3 times I realised something had to change so I implemented his 4th suggestion and wrote a Custom Binding. FWIW here is that custom binding
/// <summary>
/// Custom Binding for UIActivityIndicator Hidden.
/// This binding will ensure the indicator animates when shown and stops when hidden
/// </summary>
public class ActivityIndicatorViewHiddenTargetBinding : MvxConvertingTargetBinding
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivityIndicatorViewHiddenTargetBinding"/> class.
/// </summary>
/// <param name="target">The target.</param>
public ActivityIndicatorViewHiddenTargetBinding(UIActivityIndicatorView target)
: base(target)
{
if (target == null)
{
MvxBindingTrace.Trace(
MvxTraceLevel.Error,
"Error - UIActivityIndicatorView is null in ActivityIndicatorViewHiddenTargetBinding");
}
}
/// <summary>
/// Gets the default binding mode.
/// </summary>
/// <value>
/// The default mode.
/// </value>
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
/// <summary>
/// Gets the type of the target.
/// </summary>
/// <value>
/// The type of the target.
/// </value>
public override System.Type TargetType
{
get { return typeof(bool); }
}
/// <summary>
/// Gets the view.
/// </summary>
/// <value>
/// The view.
/// </value>
protected UIActivityIndicatorView View
{
get { return Target as UIActivityIndicatorView; }
}
/// <summary>
/// Sets the value.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="value">The value.</param>
protected override void SetValueImpl(object target, object value)
{
var view = (UIActivityIndicatorView)target;
if (view == null)
{
return;
}
view.Hidden = (bool)value;
if (view.Hidden)
{
view.StopAnimating();
}
else
{
view.StartAnimating();
}
}
}

Generic code for handling Azure Tables concurrency conflicts?

I'm looking at doing some updates into Azure Storage Tables. I want to use the optimistic concurrency mechanism properly. It seems like you'd need to do something like:
Load row to update, possibly retrying failures
Apply updates to row
Save row, possibly retrying network errors
If there is a concurrency conflict, reload the data (possibly retrying failures) and attempt to save again (possible retrying failures)
Is there some generic class or code sample that handles this? I can code it up, but I have to imagine someone has already invented this particular wheel.
If someone invented this wheel they're not talking, so I went off and (re)invented it myself. This is intentionally very generic, more of a skeleton than a finished product. It's basically just the algorithm I outlined above. The caller has to wire in delegates to do the actual loading, updating and saving of the data. There is basic retry logic built in, but I would recommend overriding those functions with something more robust.
I believe this will work with tables or BLOBs, and single entities or batches, though I've only actually tried it with single-entity table updates.
Any comments, suggestions, improvements, etc would be appreciated.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Data.Services.Client;
using Microsoft.WindowsAzure.StorageClient;
using System.Net;
namespace SepiaLabs.Azure
{
/// <summary>
/// Attempt to write an update to storage while using optimistic concurrency.
/// Implements a basic state machine. Data will be fetched (with retries), then mutated, then updated (with retries, and possibly refetching & remutating).
/// Clients may pass in a state object with relevant information. eg, a TableServiceContext object.
/// </summary>
/// <remarks>
/// This object natively implements a very basic retry strategy.
/// Clients may want to subclass it and override the ShouldRetryRetrieval() and ShouldRetryPersist() functions to implement more advanced retry strategies.
///
/// This class intentionally avoids checking if the row is present before updating it. This is so callers may throw custom exceptions, or attempt to insert the row instead ("upsert" style interaction)
/// </remarks>
/// <typeparam name="RowType">The type of data that will be read and updated. Though it is called RowType for clarity, you could manipulate a collection of rows.</typeparam>
/// <typeparam name="StateObjectType">The type of the user-supplied state object</typeparam>
public class AzureDataUpdate<RowType, StateObjectType>
where RowType : class
{
/// <summary>
/// Function to retrieve the data that will be updated.
/// This function will be called at least once. It will also be called any time a concurrency update conflict occurs.
/// </summary>
public delegate RowType DataRetriever(StateObjectType stateObj);
/// <summary>
/// Function to apply the desired changes to the data.
/// This will be called after each time the DataRetriever function is called.
/// If you are using a TableServiceContext with MergeOption.PreserveChanges set, this function can be a no-op after the first call
/// </summary>
public delegate void DataMutator(RowType data, StateObjectType stateObj);
/// <summary>
/// Function to persist the modified data. The may be called multiple times.
/// </summary>
/// <param name="data"></param>
/// <param name="stateObj"></param>
public delegate void DataPersister(RowType data, StateObjectType stateObj);
public DataRetriever RetrieverFunction { get; set; }
public DataMutator MutatorFunction { get; set; }
public DataPersister PersisterFunction { get; set; }
public AzureDataUpdate()
{
}
public AzureDataUpdate(DataRetriever retrievalFunc, DataMutator mutatorFunc, DataPersister persisterFunc)
{
this.RetrieverFunction = retrievalFunc;
this.MutatorFunction = mutatorFunc;
this.PersisterFunction = persisterFunc;
}
public RowType Execute(StateObjectType userState)
{
if (RetrieverFunction == null)
{
throw new InvalidOperationException("Must provide a data retriever function before executing");
}
else if (MutatorFunction == null)
{
throw new InvalidOperationException("Must provide a data mutator function before executing");
}
else if (PersisterFunction == null)
{
throw new InvalidOperationException("Must provide a data persister function before executing");
}
//Retrieve and modify data
RowType data = this.DoRetrieve(userState);
//Call the mutator function.
MutatorFunction(data, userState);
//persist changes
int attemptNumber = 1;
while (true)
{
bool isPreconditionFailedResponse = false;
try
{
PersisterFunction(data, userState);
return data; //return the mutated data
}
catch (DataServiceRequestException dsre)
{
DataServiceResponse resp = dsre.Response;
int statusCode = -1;
if (resp.IsBatchResponse)
{
statusCode = resp.BatchStatusCode;
}
else if (resp.Any())
{
statusCode = resp.First().StatusCode;
}
isPreconditionFailedResponse = (statusCode == (int)HttpStatusCode.PreconditionFailed);
if (!ShouldRetryPersist(attemptNumber, dsre, isPreconditionFailedResponse, userState))
{
throw;
}
}
catch (DataServiceClientException dsce)
{
isPreconditionFailedResponse = (dsce.StatusCode == (int)HttpStatusCode.PreconditionFailed);
if (!ShouldRetryPersist(attemptNumber, dsce, isPreconditionFailedResponse, userState))
{
throw;
}
}
catch (StorageClientException sce)
{
isPreconditionFailedResponse = (sce.StatusCode == HttpStatusCode.PreconditionFailed);
if (!ShouldRetryPersist(attemptNumber, sce, isPreconditionFailedResponse, userState))
{
throw;
}
}
catch (Exception ex)
{
if (!ShouldRetryPersist(attemptNumber, ex, false, userState))
{
throw;
}
}
if (isPreconditionFailedResponse)
{
//Refetch the data, re-apply the mutator
data = DoRetrieve(userState);
MutatorFunction(data, userState);
}
attemptNumber++;
}
}
/// <summary>
/// Retrieve the data to be updated, possibly with retries
/// </summary>
/// <param name="userState">The UserState for this operation</param>
private RowType DoRetrieve(StateObjectType userState)
{
int attemptNumber = 1;
while (true)
{
try
{
return RetrieverFunction(userState);
}
catch (Exception ex)
{
if (!ShouldRetryRetrieval(attemptNumber, ex, userState))
{
throw;
}
}
attemptNumber++;
}
}
/// <summary>
/// Determine whether a data retrieval should be retried.
/// Implements a simplistic, constant wait time strategy. Users may override to provide a more complex implementation.
/// </summary>
/// <param name="attemptNumber">What number attempt is this. </param>
/// <param name="ex">The exception that was caught</param>
/// <param name="userState">The user-supplied state object for this operation</param>
/// <returns>True to attempt the retrieval again, false to abort the retrieval and fail the update attempt</returns>
protected virtual bool ShouldRetryRetrieval(int attemptNumber, Exception ex, StateObjectType userState)
{
//Simple, basic retry strategy - try 3 times, sleep for 1000msec each time
if (attemptNumber < 3)
{
Thread.Sleep(1000);
return true;
}
else
{
return false;
}
}
/// <summary>
/// Determine whether a data update should be retried. If the <paramref name="isPreconditionFailed"/> param is true,
/// then the retrieval and mutation process will be repeated as well
/// Implements a simplistic, constant wait time strategy. Users may override to provide a more complex implementation.
/// </summary>
/// <param name="attemptNumber">What number attempt is this. </param>
/// <param name="ex">The exception that was caught</param>
/// <param name="userState">The user-supplied state object for this operation</param>
/// <param name="isPreconditionFailedResponse">Indicates whether the exception is a PreconditionFailed response. ie, an optimistic concurrency failure</param>
/// <returns>True to attempt the update again, false to abort the retrieval and fail the update attempt</returns>
protected virtual bool ShouldRetryPersist(int attemptNumber, Exception ex, bool isPreconditionFailedResponse, StateObjectType userState)
{
if (isPreconditionFailedResponse)
{
return true; //retry immediately
}
else
{
//For other failures, wait to retry
//Simple, basic retry strategy - try 3 times, sleep for 1000msec each time
if (attemptNumber < 3)
{
Thread.Sleep(1000);
return true;
}
else
{
return false;
}
}
}
}
}

Tracking WeakReference to objects from multiple threads

I am designing a static message bus that would allow subscribing to and publishing of messages of an arbitrary type. To avoid requiring observers to unsubscribe explicitly, I would like to keep track of WeakReference objects that point to delegates instead of tracking delegates themselves. I ended up coding something similar to what Paul Stovell described in his blog http://www.paulstovell.com/weakevents.
My problem is this: as opposed to Paul's code, my observers subscribe to messages on one thread, but messages may be published on another. In this case, I observe that by the time I need to notify observers, my WeakReference.Target values are null indicating that targets have been collected, even though I know for certain they weren't. The problem persists for both short and long weak references.
Conversely, when subscribing and publishing is done from the same thread, the code works fine. The latter is true even if I actually end up enumerating over targets on a new thread from ThreadPool, for as long as the request initially comes from the same thread I subscribe to messages on.
I understand that this is a very specific case, so any help is greatly appreciated.
My question is: should I not be able to reliably access WeakReference objects from multiple threads provided proper thread synchronization is in place? It appears that I cannot, which does not make much sense to me. So, what am I not doing right?
It looks like after reducing my code to a simpler form (see below), it now works fine. This means, the problem that caused weak reference targets to be collected too early must reside elsewhere in my code. So, to answer my own question, it appears that weak references can be securely accessed from multiple threads.
Here is my test code:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting the app");
Test test = new Test();
// uncomment these lines to cause automatic unsubscription from Message1
// test = null;
// GC.Collect();
// GC.WaitForPendingFinalizers();
// publish Message1 on this thread
// MessageBus.Publish<Message1>(new Message1());
// publish Message1 on another thread
ThreadPool.QueueUserWorkItem(delegate
{
MessageBus.Publish<Message1>(new Message1());
});
while (!MessageBus.IamDone)
{
Thread.Sleep(100);
}
Console.WriteLine("Exiting the app");
Console.WriteLine("Press <ENTER> to terminate program.");
Console.WriteLine();
Console.ReadLine();
}
}
public class Test
{
public Test()
{
Console.WriteLine("Subscribing to message 1.");
MessageBus.Subscribe<Message1>(OnMessage1);
Console.WriteLine("Subscribing to message 2.");
MessageBus.Subscribe<Message2>(OnMessage2);
}
public void OnMessage1(Message1 message)
{
Console.WriteLine("Got message 1. Publishing message 2");
MessageBus.Publish<Message2>(new Message2());
}
public void OnMessage2(Message2 message)
{
Console.WriteLine("Got message 2. Closing the app");
MessageBus.IamDone = true;
}
}
public abstract class MessageBase
{
public string Message;
}
public class Message1 : MessageBase
{
}
public class Message2 : MessageBase
{
}
public static class MessageBus
{
// This is here purely for this test
public static bool IamDone = false;
/////////////////////////////////////
/// <summary>
/// A dictionary of lists of handlers of messages by message type
/// </summary>
private static ConcurrentDictionary<string, List<WeakReference>> handlersDict = new ConcurrentDictionary<string, List<WeakReference>>();
/// <summary>
/// Thread synchronization object to use with Publish calls
/// </summary>
private static object _lockPublishing = new object();
/// <summary>
/// Thread synchronization object to use with Subscribe calls
/// </summary>
private static object _lockSubscribing = new object();
/// <summary>
/// Creates a work queue item that encapsulates the provided parameterized message
/// and dispatches it.
/// </summary>
/// <typeparam name="TMessage">Message argument type</typeparam>
/// <param name="message">Message argument</param>
public static void Publish<TMessage>(TMessage message)
where TMessage : MessageBase
{
// create the dictionary key
string key = String.Empty;
key = typeof(TMessage).ToString();
// initialize a queue work item argument as a tuple of the dictionary type key and the message argument
Tuple<string, TMessage, Exception> argument = new Tuple<string, TMessage, Exception>(key, message, null);
// push the message on the worker queue
ThreadPool.QueueUserWorkItem(new WaitCallback(_PublishMessage<TMessage>), argument);
}
/// <summary>
/// Publishes a message to the bus, causing observers to be invoked if appropriate.
/// </summary>
/// <typeparam name="TArg">Message argument type</typeparam>
/// <param name="stateInfo">Queue work item argument</param>
private static void _PublishMessage<TArg>(Object stateInfo)
where TArg : class
{
try
{
// translate the queue work item argument to extract the message type info and
// any arguments
Tuple<string, TArg, Exception> arg = (Tuple<string, TArg, Exception>)stateInfo;
// call all observers that have registered to receive this message type in parallel
Parallel.ForEach(handlersDict.Keys
// find the right dictionary list entry by message type identifier
.Where(handlerKey => handlerKey == arg.Item1)
// dereference the list entry by message type identifier to get a reference to the observer
.Select(handlerKey => handlersDict[handlerKey]), (handlerList, state) =>
{
lock (_lockPublishing)
{
List<int> descopedRefIndexes = new List<int>(handlerList.Count);
// search the list of references and invoke registered observers
foreach (WeakReference weakRef in handlerList)
{
// try to obtain a strong reference to the target
Delegate dlgRef = (weakRef.Target as Delegate);
// check if the underlying delegate reference is still valid
if (dlgRef != null)
{
// yes it is, get the delegate reference via Target property, convert it to Action and invoke the observer
try
{
(dlgRef as Action<TArg>).Invoke(arg.Item2);
}
catch (Exception e)
{
// trouble invoking the target observer's reference, mark it for deletion
descopedRefIndexes.Add(handlerList.IndexOf(weakRef));
Console.WriteLine(String.Format("Error looking up target reference: {0}", e.Message));
}
}
else
{
// the target observer's reference has been descoped, mark it for deletion
descopedRefIndexes.Add(handlerList.IndexOf(weakRef));
Console.WriteLine(String.Format("Message type \"{0}\" has been unsubscribed from.", arg.Item1));
MessageBus.IamDone = true;
}
}
// remove any descoped references
descopedRefIndexes.ForEach(index => handlerList.RemoveAt(index));
}
});
}
// catch all Exceptions
catch (AggregateException e)
{
Console.WriteLine(String.Format("Error dispatching messages: {0}", e.Message));
}
}
/// <summary>
/// Subscribes the specified delegate to handle messages of type TMessage
/// </summary>
/// <typeparam name="TArg">Message argument type</typeparam>
/// <param name="action">WeakReference that represents the handler for this message type to be registered with the bus</param>
public static void Subscribe<TArg>(Action<TArg> action)
where TArg : class
{
// validate input
if (action == null)
throw new ArgumentNullException(String.Format("Error subscribing to message type \"{0}\": Specified action reference is null.", typeof(TArg)));
// build the queue work item key identifier
string key = typeof(TArg).ToString();
// check if a message of this type was already added to the bus
if (!handlersDict.ContainsKey(key))
{
// no, it was not, create a new dictionary entry and add the new observer's reference to it
List<WeakReference> newHandlerList = new List<WeakReference>();
handlersDict.TryAdd(key, newHandlerList);
}
lock (_lockSubscribing)
{
// append this new observer's reference to the list, if it does not exist already
if (!handlersDict[key].Any(existing => (existing.Target as Delegate) != null && (existing.Target as Delegate).Equals(action)))
{
// append the new reference
handlersDict[key].Add(new WeakReference(action, true));
}
}
}
}
}
This is an amendment to my previous answer. I have discovered why my original code did not work and this information may be useful for others. In my original code MessageBus was instantiated as Singleton:
public class MessageBus : Singleton<MessageBus> // Singleton<> is my library class
In the example above, it was declared as static:
public static class MessageBus
Once I converted my code to use a static, things started working. Having said that, I could not yet figure out why the singleton did not work.

Azure/web-farm ready SecurityTokenCache

Our site uses ADFS for auth. To reduce the cookie payload on every request we're turning IsSessionMode on (see Your fedauth cookies on a diet).
The last thing we need to do to get this working in our load balanced environment is to implement a farm ready SecurityTokenCache. The implementation seems pretty straightforward, I'm mainly interested in finding out if there are any gotchas we should consider when dealing with SecurityTokenCacheKey and the TryGetAllEntries and TryRemoveAllEntries methods (SecurityTokenCacheKey has a custom implementation of the Equals and GetHashCode methods).
Does anyone have an example of this? We're planning on using AppFabric as the backing store but an example using any persistent store would be helpful- database table, Azure table-storage, etc.
Here are some places I've searched:
In Hervey Wilson's PDC09
session he uses a
DatabaseSecurityTokenCache. I haven't been able to find the sample
code for his session.
On page 192 of Vittorio Bertocci's excellent
book, "Programming Windows Identity Foundation" he mentions uploading
a sample implementation of an Azure ready SecurityTokenCache to the
book's website. I haven't been able to find this sample either.
Thanks!
jd
3/16/2012 UPDATE
Vittorio's blog links to a sample using the new .net 4.5 stuff:
ClaimsAwareWebFarm
This sample is an answer to the feedback we got from many of you guys: you wanted a sample showing a farm ready session cache (as opposed to a tokenreplycache) so that you can use sessions by reference instead of exchanging big cookies; and you asked for an easier way of securing cookies in a farm.
To come up with a working implementation we ultimately had to use reflector to analyze the different SessionSecurityToken related classes in Microsoft.IdentityModel. Below is what we came up with. This implementation is deployed on our dev and qa environments, seems to be working fine, it's resiliant to app pool recycles etc.
In global.asax:
protected void Application_Start(object sender, EventArgs e)
{
FederatedAuthentication.ServiceConfigurationCreated += this.OnServiceConfigurationCreated;
}
private void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
var sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(
e.ServiceConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(
e.ServiceConfiguration.ServiceCertificate)
});
// following line is pseudo code. use your own durable cache implementation.
var durableCache = new AppFabricCacheWrapper();
var tokenCache = new DurableSecurityTokenCache(durableCache, 5000);
var sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly(),
tokenCache,
TimeSpan.FromDays(1));
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
}
private void WSFederationAuthenticationModule_SecurityTokenValidated(object sender, SecurityTokenValidatedEventArgs e)
{
FederatedAuthentication.SessionAuthenticationModule.IsSessionMode = true;
}
DurableSecurityTokenCache.cs:
/// <summary>
/// Two level durable security token cache (level 1: in memory MRU, level 2: out of process cache).
/// </summary>
public class DurableSecurityTokenCache : SecurityTokenCache
{
private ICache<string, byte[]> durableCache;
private readonly MruCache<SecurityTokenCacheKey, SecurityToken> mruCache;
/// <summary>
/// The constructor.
/// </summary>
/// <param name="durableCache">The durable second level cache (should be out of process ie sql server, azure table, app fabric, etc).</param>
/// <param name="mruCapacity">Capacity of the internal first level cache (in-memory MRU cache).</param>
public DurableSecurityTokenCache(ICache<string, byte[]> durableCache, int mruCapacity)
{
this.durableCache = durableCache;
this.mruCache = new MruCache<SecurityTokenCacheKey, SecurityToken>(mruCapacity, mruCapacity / 4);
}
public override bool TryAddEntry(object key, SecurityToken value)
{
var cacheKey = (SecurityTokenCacheKey)key;
// add the entry to the mru cache.
this.mruCache.Add(cacheKey, value);
// add the entry to the durable cache.
var keyString = GetKeyString(cacheKey);
var buffer = this.GetSerializer().Serialize((SessionSecurityToken)value);
this.durableCache.Add(keyString, buffer);
return true;
}
public override bool TryGetEntry(object key, out SecurityToken value)
{
var cacheKey = (SecurityTokenCacheKey)key;
// attempt to retrieve the entry from the mru cache.
value = this.mruCache.Get(cacheKey);
if (value != null)
return true;
// entry wasn't in the mru cache, retrieve it from the app fabric cache.
var keyString = GetKeyString(cacheKey);
var buffer = this.durableCache.Get(keyString);
var result = buffer != null;
if (result)
{
// we had a cache miss in the mru cache but found the item in the durable cache...
// deserialize the value retrieved from the durable cache.
value = this.GetSerializer().Deserialize(buffer);
// push this item into the mru cache.
this.mruCache.Add(cacheKey, value);
}
return result;
}
public override bool TryRemoveEntry(object key)
{
var cacheKey = (SecurityTokenCacheKey)key;
// remove the entry from the mru cache.
this.mruCache.Remove(cacheKey);
// remove the entry from the durable cache.
var keyString = GetKeyString(cacheKey);
this.durableCache.Remove(keyString);
return true;
}
public override bool TryReplaceEntry(object key, SecurityToken newValue)
{
var cacheKey = (SecurityTokenCacheKey)key;
// remove the entry in the mru cache.
this.mruCache.Remove(cacheKey);
// remove the entry in the durable cache.
var keyString = GetKeyString(cacheKey);
// add the new value.
return this.TryAddEntry(key, newValue);
}
public override bool TryGetAllEntries(object key, out IList<SecurityToken> tokens)
{
// not implemented... haven't been able to find how/when this method is used.
tokens = new List<SecurityToken>();
return true;
//throw new NotImplementedException();
}
public override bool TryRemoveAllEntries(object key)
{
// not implemented... haven't been able to find how/when this method is used.
return true;
//throw new NotImplementedException();
}
public override void ClearEntries()
{
// not implemented... haven't been able to find how/when this method is used.
//throw new NotImplementedException();
}
/// <summary>
/// Gets the string representation of the specified SecurityTokenCacheKey.
/// </summary>
private string GetKeyString(SecurityTokenCacheKey key)
{
return string.Format("{0}; {1}; {2}", key.ContextId, key.KeyGeneration, key.EndpointId);
}
/// <summary>
/// Gets a new instance of the token serializer.
/// </summary>
private SessionSecurityTokenCookieSerializer GetSerializer()
{
return new SessionSecurityTokenCookieSerializer(); // may need to do something about handling bootstrap tokens.
}
}
MruCache.cs:
/// <summary>
/// Most recently used (MRU) cache.
/// </summary>
/// <typeparam name="TKey">The key type.</typeparam>
/// <typeparam name="TValue">The value type.</typeparam>
public class MruCache<TKey, TValue> : ICache<TKey, TValue>
{
private Dictionary<TKey, TValue> mruCache;
private LinkedList<TKey> mruList;
private object syncRoot;
private int capacity;
private int sizeAfterPurge;
/// <summary>
/// The constructor.
/// </summary>
/// <param name="capacity">The capacity.</param>
/// <param name="sizeAfterPurge">Size to make the cache after purging because it's reached capacity.</param>
public MruCache(int capacity, int sizeAfterPurge)
{
this.mruList = new LinkedList<TKey>();
this.mruCache = new Dictionary<TKey, TValue>(capacity);
this.capacity = capacity;
this.sizeAfterPurge = sizeAfterPurge;
this.syncRoot = new object();
}
/// <summary>
/// Adds an item if it doesn't already exist.
/// </summary>
public void Add(TKey key, TValue value)
{
lock (this.syncRoot)
{
if (mruCache.ContainsKey(key))
return;
if (mruCache.Count + 1 >= this.capacity)
{
while (mruCache.Count > this.sizeAfterPurge)
{
var lru = mruList.Last.Value;
mruCache.Remove(lru);
mruList.RemoveLast();
}
}
mruCache.Add(key, value);
mruList.AddFirst(key);
}
}
/// <summary>
/// Removes an item if it exists.
/// </summary>
public void Remove(TKey key)
{
lock (this.syncRoot)
{
if (!mruCache.ContainsKey(key))
return;
mruCache.Remove(key);
mruList.Remove(key);
}
}
/// <summary>
/// Gets an item. If a matching item doesn't exist null is returned.
/// </summary>
public TValue Get(TKey key)
{
lock (this.syncRoot)
{
if (!mruCache.ContainsKey(key))
return default(TValue);
mruList.Remove(key);
mruList.AddFirst(key);
return mruCache[key];
}
}
/// <summary>
/// Gets whether a key is contained in the cache.
/// </summary>
public bool ContainsKey(TKey key)
{
lock (this.syncRoot)
return mruCache.ContainsKey(key);
}
}
ICache.cs:
/// <summary>
/// A cache.
/// </summary>
/// <typeparam name="TKey">The key type.</typeparam>
/// <typeparam name="TValue">The value type.</typeparam>
public interface ICache<TKey, TValue>
{
void Add(TKey key, TValue value);
void Remove(TKey key);
TValue Get(TKey key);
}
Here is a sample that I wrote. I use Windows Azure to store the tokens forever, defeating any possible replay.
http://tokenreplaycache.codeplex.com/releases/view/76652
You will need to place this in your web.config:
<service>
<securityTokenHandlers>
<securityTokenHandlerConfiguration saveBootstrapTokens="true">
<tokenReplayDetection enabled="true" expirationPeriod="50" purgeInterval="1">
<replayCache type="LC.Security.AzureTokenReplayCache.ACSTokenReplayCache,LC.Security.AzureTokenReplayCache, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</tokenReplayDetection>
</securityTokenHandlerConfiguration>
</securityTokenHandlers>
</service>

Resources