Silverlight Printing string value - string

Currently i am working with silverlight 4, we are converting windows based application into silverlight. In windows based application to print string is easy using COM, but client side application like Silverlight is it possible to print string value ?
I have to convert this to Silverlight :
Dim PD As New PrintDialog
PD.UseEXDialog = True
PD.PrinterSettings = New System.Drawing.Printing.PrinterSettings
If (Windows.Forms.DialogResult.OK = PD.ShowDialog()) Then
ZPLZebra.RawPrinterHelper.SendStringToPrinter(PD.PrinterSettings.PrinterName, *******.ToString())
End If
Edit 1:
public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, int dwCount)
{
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool flag = false;
di.pDocName = "My C#.NET RAW Document";
di.pDataType = "RAW";
if (OpenPrinter(szPrinterName, out hPrinter, 0L))
{
if (StartDocPrinter(hPrinter, 1, di))
{
if (StartPagePrinter(hPrinter))
{
int dwWritten = 0;
flag = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
if (!flag)
{
Marshal.GetLastWin32Error();
}
return flag;
}
public static bool SendStringToPrinter(string szPrinterName, string szString)
{
int length = szString.Length;
IntPtr pBytes = Marshal.StringToCoTaskMemAnsi(szString);
SendBytesToPrinter(szPrinterName, pBytes, length);
Marshal.FreeCoTaskMem(pBytes);
return true;
}
Ref:(this is my exact requirement)- Printing "raw text" (ZPL) from Web

You can use PrintDocument.Print method and in PageVisual send a grid with text block ?
Sample code in C#
/// <summary>
/// Text to print.
/// </summary>
private string _textToPrint;
/// <summary>
/// Method that print text.
/// </summary>
/// <param name="textToPrint">Text to print</param>
/// <param name="documentName">Optional document name.</param>
public void SendStringToPrinter(string textToPrint, string documentName = "My Document")
{
// Set print content in private property
_textToPrint = textToPrint;
// Create a new print dialog
PrintDocument pd = new PrintDocument();
// Catch print event
pd.PrintPage += onPrintPage;
// Start print request
pd.Print(documentName);
}
/// <summary>
/// Called during printing.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void onPrintPage(object sender, PrintPageEventArgs e)
{
// Create a UI element (simple grid with textblock inside).
var grid = new Grid();
grid.Children.Add(new TextBlock()
{
Text = _textToPrint
});
// Add the grid to print visual.
e.PageVisual = grid;
}
You can call :
SendStringToPrinter(*******.ToString());

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

FileSystemWatcher activated only once

I'm a beginner in C# and I'm stuck at this problem.
I'm creating a service, that watches for incoming files, and when the file comes, I will call a .bat file to do some processing.
The problem with the code below is, I copy the file into the watched folder for the first time it works, the second time I copy the file into the watched folder, it does not react anymore. It is running as a Windows service and it is running all the time.
It used to work, I'm not sure what I'm missing.
Hope someone can help me with this. Thank you.
namespace TestCSWinWatcherService
{
using System;
using System.IO;
using System.Configuration;
partial class SAPFileService
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.FSWatcherTest = new System.IO.FileSystemWatcher();
((System.ComponentModel.ISupportInitialize)(this.FSWatcherTest)).BeginInit();
//
// FSWatcherTest
//
this.FSWatcherTest.InternalBufferSize = 32768;
this.FSWatcherTest.EnableRaisingEvents = true;
this.FSWatcherTest.NotifyFilter = ((System.IO.NotifyFilters)((((((((System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName)
| System.IO.NotifyFilters.Attributes)
| System.IO.NotifyFilters.Size)
| System.IO.NotifyFilters.LastWrite)
| System.IO.NotifyFilters.LastAccess)
| System.IO.NotifyFilters.CreationTime)
| System.IO.NotifyFilters.Security)));
this.FSWatcherTest.Changed += new System.IO.FileSystemEventHandler(this.FSWatcherTest_Changed);
this.FSWatcherTest.Created += new System.IO.FileSystemEventHandler(this.FSWatcherTest_Created);
this.FSWatcherTest.Deleted += new System.IO.FileSystemEventHandler(this.FSWatcherTest_Deleted);
this.FSWatcherTest.Renamed += new System.IO.RenamedEventHandler(this.FSWatcherTest_Renamed);
//
// SAPFileService
//
this.ServiceName = "Service1";
((System.ComponentModel.ISupportInitialize)(this.FSWatcherTest)).EndInit();
}
#endregion
private System.IO.FileSystemWatcher FSWatcherTest;
/* DEFINE WATCHER EVENTS... */
/// <summary>
/// Event occurs when the contents of a File or Directory are changed
/// </summary>
private void FSWatcherTest_Changed(object sender,
System.IO.FileSystemEventArgs e)
{
//code here for newly changed file or directory
String SAPInboundPath = ConfigurationManager.AppSettings["WatchPath"];
String SAPArchivePath = ConfigurationManager.AppSettings["SAPArchivePath"];
String SAPLogPath = ConfigurationManager.AppSettings["SAPLogPath"];
String SAPDBCDFileName = ConfigurationManager.AppSettings["SAPDBCDFileName"];
Boolean SAPDBCDExists = false;
string[] files = System.IO.Directory.GetFiles(SAPInboundPath, SAPDBCDFileName, System.IO.SearchOption.TopDirectoryOnly);
if (files.Length > 0)
{
//file exist
SAPDBCDExists = true;
}
if (SAPDBCDExists)
{
String BatPath = ConfigurationManager.AppSettings["SAPBatPath"];
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(BatPath);
psi.RedirectStandardOutput = true;
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
psi.UseShellExecute = false;
System.Diagnostics.Process listFiles;
listFiles = System.Diagnostics.Process.Start(psi);
System.IO.StreamReader myOutput = listFiles.StandardOutput;
listFiles.WaitForExit();
//System.IO.File.Move(e.FullPath, SAPArchivePath + e.Name + DateTime.Now.ToString("dMMyyyyHHmmss"));
}
}
I've tried:
1) Your sleep hack
2) Increasing Buffer
3) Checking the GC does not clear
Nothing works... :(

What is the proper way to deserialize a RedisMessage through BookSleeve?

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

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