The way I have decided to support multiple card readers is through a couple design and technology decisions. MEF will be used for plug-in composition, and a centralized management class will be used to provide a single communication for reader notifications. The Microsoft Extensibility Framework (MEF) is a new to Silverlight and .NET 4.0. It provides a standardized plugin and discovery approach to disparate components across a system. If you are interested in learning more see the MEF CodePlex site http://mef.codeplex.com/. The code snippet below is from the DefaultFrameworkCardReaderManager and shows how we will aggregate and find a set of readers using MEF.
1 /// <summary> 2 /// reader discovery modules 3 /// </summary> 4 [ImportMany(AllowRecomposition = false)] 5 private IEnumerable<ISmartCardReaderDiscovery> _readerDiscoveryModules = null; 6 … 7 … construction code here, that calls compose, and find reader 8 … 9 private void Compose() 10 { 11 // TODO: use a different catalog to acquire data. and we also probably 12 // use a composition model that allows for multiple catalogs 13 // ... save that for later though. 14 AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); 15 var container = new CompositionContainer(catalog); 16 container.ComposeParts(this); 17 }
1 private List<IsmartCardReader> FindReadersToManage() 2 { 3 var readersToManage = new List<ISmartCardReader>(); 4 foreach (var discoveryProcess in _readerDiscoveryModules) 5 { 6 var discoveredReaders = discoveryProcess.Discover(); 7 readersToManage.AddRange(discoveredReaders); 8 } 9 10 return readersToManage; 11 } 12 13 private void AttachToReaderNotifications() 14 { 15 foreach (var managedReader in _managedReaders) 16 { 17 managedReader.CardInserted += managedReader_CardInserted; 18 managedReader.CardRemoved += managedReader_CardRemoved; 19 managedReader.ReaderError += managedReader_ReaderError; 20 } 21 }
PC/SC is comprised of numerous functions, but in order to detect card insertion and the set of readers that exist on the system there are only a couple API calls of importance.
- SCardListReaders – This API call provides a listing of a set of PC/SC compliant smart card readers by name. This is what our PC/SC discovery process uses to find all reader in the system.
- SCardGetStatusChange – This API call provides the current state of a set of readers, and we use a polling mechanism in our code track potential changes in reader state.
The reader discovery process for PC/SC is less than 100 lines in total (including comments). The manager class will find this implementation using MEF and will call discove adding the discovered readers to the managed collection.
1 internal class PcscSmartCardReaderDiscoveryProcess : ISmartCardReaderDiscovery 2 { 3 public IEnumerable<ISmartCardReader> Discover() 4 { 5 var readerNames = GetInstalledReaders(); 6 var readers = new LinkedList<ISmartCardReader>(); 7 foreach (var readerName in readerNames) 8 { readers.AddLast(CreateReader(readerName)); } 9 return readers; 10 } 11 12 private PcscSmartCardReader CreateReader(string readerName) 13 { 14 // TODO: revisit how we will determine 15 // the reader type. (name inference, injection, etc) 16 return new PcscSmartCardReader(readerName, eReaderType.Unknown); 17 } 18 19 /// <summary> 20 /// Returns the string names of all of the 21 /// smart card readers currently available on the system. 22 /// </summary> 23 /// <returns></returns> 24 private IEnumerable<string> GetInstalledReaders() 25 { 26 // get the length of buffer needed for all string names 27 UInt32 bufferLength = 0; 28 int retVal = 0; 29 30 // get the buffer size required for getting all readers. 31 retVal = PcscInvokes.SCardListReaders(0, IntPtr.Zero, null, ref bufferLength); 32 if ((ePcscErrors)retVal != ePcscErrors.Success) 33 { throw new PcscException("", (ePcscErrors)retVal); } 34 35 // now load the buffer up 36 byte[] mszReaders = new byte[bufferLength]; 37 retVal = PcscInvokes.SCardListReaders(0, IntPtr.Zero, mszReaders, ref bufferLength); 38 if ((ePcscErrors)retVal != ePcscErrors.Success) 39 { throw new PcscException("", (ePcscErrors)retVal); } 40 41 ASCIIEncoding encoding = new ASCIIEncoding(); 42 string encoded = encoding.GetString(mszReaders); 43 string[] split = encoded.Split('\0'); 44 return split.Where((x) => !string.IsNullOrWhiteSpace(x)); 45 } 46 }
Notice the PcscSmartCardReaderDiscoveryProcess class is declared as internal, yet it can still be referenced in the unit tests? Add the below line to a AssemblyInfo.cs file and you can keep your assembly external interface clean while still providing tests for some internal code. [assembly: InternalsVisibleTo(“SmartCard.Framework.UnitTests”)] in your assembly info file then the assembly referenced will have access to assembly internals. |
No comments:
Post a Comment