- Ability to handle derived types that were not known during compilation but rather at run-time (basically IoC style). So the KnownTypeAttribute could not be used in these cases as the base class would not know of the dervived types. This also applies to the scenario where you are derviving from a closed package.
- Ability to encrypt the data being exchanged.
As a first draft and to explore the updated DataContractSerializers, I built a data exchange provider using the DataContractJsonSerializer and DataContractSerializer serializers. As part of the provider I also encrypted the package after it was serialized (see my blog post entitled Silverlight Encryption). This worked well as you are directly accessing the serializers and can easily pass in an IEnumerable
The code handles fetching data, requesting commands be executed, etc. on the server including encrypting the data. But how does the server send messages back to the Silverlight? With my data exchange provider, I would need to setup a thread in the Silverlight client that sent a command to the server to check for incoming messages. Not really an issue and works fairly well as you can specify how often you want messages checked.
However, I also needed to have messages sent and dealt with immediately (a good example is hooking into a phone exchange system and signalling the Silverlight client that a new call has been received). I could do this with my system by polling with a fine grain invertal (say 5 to 15 seconds instead of 30 to 60 seconds as an example). Or I could use either PollingDuplexHttpBinding with WCF. There is also the possibility of using an asynchronous TCP/IP connection.
WCF uses the DataContractSerializer under the covers, so it supports known types via the KnownTypeAttribute. However, it does not expose the serializers so how do you a list of known types to feed to the service? Well you could use the NetDataContractSerializer which decorates the serialized stream with relavent data. However, the NetDataContractSerializer is not supported by Silverlight. Instead we can use the ServiceKnownType attribute on the service. It can be applied either to the interface or to the concrete class itself. Essentially the ServiceKnownType a method name and a type of class that exposes that method as a static method. The method has the signature of
public static Type[] methodname();
So if you have a Service1, you'd decorate as follows:
ServiceContract(Namespace = "")]
[ServiceKnownType("methodname", typeof(myclass))]
public class MyService
Below is an implementation of a class that can be used with the ServiceKnownType attribute. It is called whenever you create an instance of your MyService service. It will scan through all the assemblies in the domain looking for specific classes to add as known types. You can use the RegisterAssemblyExclusion to exclude certain assemblies from being scanned and you can use the RegisterValidatorType to include concrete IDataContractKnownTypeValidator validators to determine if an object should be included.
public sealed class DataContractKnownTypesRegister
{
public DataContractKnownTypesRegister()
{
_exclusions.Add("mscorlib");
_exclusions.Add("system");
_exclusions.Add("microsoft");
RegisterValidatorType();
}
#region Public Methods
public static Type[] GetKnownTypes(ICustomAttributeProvider provider)
{
try
{
DataContractKnownTypesRegister instance = new DataContractKnownTypesRegister();
return instance.GetKnownTypesEx(provider);
}
catch (Exception ex)
{
throw;
}
}
#region Registration
public static void RegisterAssemblyExclusion(string name)
{
try
{
if (!_exclusions.Contains(name))
_exclusions.Add(name);
}
catch (Exception ex)
{
throw;
}
}
public static void RegisterValidatorType(Type type)
{
try
{
if (!typeof(IDataContractKnownTypeValidator).IsAssignableFrom(type))
return;
if (!_validators.ContainsKey(type))
{
IDataContractKnownTypeValidator validator = (IDataContractKnownTypeValidator)Activator.CreateInstance(type);
_validators.Add(type, validator);
}
}
catch (Exception ex)
{
throw;
}
}
public static void RegisterValidatorType() where T : IDataContractKnownTypeValidator
{
RegisterValidatorType(typeof(T));
}
#endregion
#endregion
#region Internal Methods
internal Type[] GetKnownTypesEx(ICustomAttributeProvider provider)
{
try
{
lock (_synchLock)
{
foreach (Type type in Utilities.DataContractKnownTypes)
ScanType(type);
foreach (Assembly assembly in Utilities.DataContractRegisteredAssemblies)
ScanAssembly(assembly);
return new List(_knownTypes.Values).ToArray();
}
}
catch (Exception ex)
{
throw;
}
}
#endregion
#region Private Methods
private bool IsExcluded(string fullName)
{
if (_exclusionsEx == null)
{
StringBuilder exclusions = new StringBuilder();
foreach (string exclusion in _exclusions)
{
if (exclusions.Length > 0)
exclusions.Append("");
exclusions.Append("^").Append(exclusion);
}
_exclusionsEx = new Regex(exclusions.ToString(),
RegexOptions.CultureInvariant
RegexOptions.IgnoreCase);
}
return _exclusionsEx.IsMatch(fullName);
}
private void ScanAssembly(Assembly assembly)
{
try
{
if (assembly == null)
return;
// If we already scanned the assembly...
if (_assembliesScanned.Contains(assembly.FullName))
return;
// Is this an excluded assembly?
if (IsExcluded(assembly.FullName))
return;
// Scan the types in the assembly...
foreach (Type type in assembly.GetTypes())
ScanType(type);
// Mark the assembly as scanned...
_assembliesScanned.Add(assembly.FullName);
}
catch (Exception ex)
{
throw;
}
}
private void ScanType(Type type)
{
try
{
if (type.GetCustomAttributes(typeof(DataContractAttribute), false).Length <= 0) return; if (_knownTypes.ContainsKey(type.FullName)) return; bool valid = true; foreach (KeyValuePairpair in _validators)
{
valid = pair.Value.IsValid(type);
if (valid == true)
break;
}
_knownTypes[type.FullName] = type;
}
catch (Exception ex)
{
throw;
}
}
#endregion
#region Fields
private IList_assembliesScanned = new List ();
private static IList_exclusions = new List ();
private Regex _exclusionsEx;
private static IDictionary_knownTypes = new Dictionary ();
private static IDictionary_validators = new Dictionary ();
private static readonly object _synchLock = new object();
#endregion
#region Constants
public const string MethodName = "GetKnownTypes";
#endregion
}
public interface IDataContractKnownTypeValidator
{
bool IsValid(Type type);
}
Our Silverlight version follows the same basic concept as our normal .NET version, but it does not explicitly scan assemblies in the domain. Rather it works on an externally registered lists of types (List
public sealed class DataContractKnownTypesRegister
{
public DataContractKnownTypesRegister()
{
_exclusions.Add("mscorlib");
_exclusions.Add("system");
_exclusions.Add("microsoft");
RegisterValidatorType();
}
#region Public Methods
public static Type[] GetKnownTypes(ICustomAttributeProvider provider)
{
try
{
DataContractKnownTypesRegister instance = new DataContractKnownTypesRegister();
return instance.GetKnownTypesEx(provider);
}
catch (Exception ex)
{
throw;
}
}
#region Registration
public static void RegisterAssemblyExclusion(string name)
{
try
{
if (!_exclusions.Contains(name))
_exclusions.Add(name);
}
catch (Exception ex)
{
throw;
}
}
public static void RegisterValidatorType(Type type)
{
try
{
if (!typeof(IDataContractKnownTypeValidator).IsAssignableFrom(type))
return;
if (!_validators.ContainsKey(type))
{
IDataContractKnownTypeValidator validator = (IDataContractKnownTypeValidator)Activator.CreateInstance(type);
_validators.Add(type, validator);
}
}
catch (Exception ex)
{
throw;
}
}
public static void RegisterValidatorType() where T : IDataContractKnownTypeValidator
{
RegisterValidatorType(typeof(T));
}
#endregion
#endregion
#region Internal Methods
internal Type[] GetKnownTypesEx(ICustomAttributeProvider provider)
{
try
{
lock (_synchLock)
{
foreach (Type type in Utilities.DataContractKnownTypes)
ScanType(type);
foreach (Assembly assembly in Utilities.DataContractRegisteredAssemblies)
ScanAssembly(assembly);
return new List(_knownTypes.Values).ToArray();
}
}
catch (Exception ex)
{
throw;
}
}
#endregion
#region Private Methods
private bool IsExcluded(string fullName)
{
if (_exclusionsEx == null)
{
StringBuilder exclusions = new StringBuilder();
foreach (string exclusion in _exclusions)
{
if (exclusions.Length > 0)
exclusions.Append("");
exclusions.Append("^").Append(exclusion);
}
_exclusionsEx = new Regex(exclusions.ToString(),
RegexOptions.CultureInvariant
RegexOptions.IgnoreCase);
}
return _exclusionsEx.IsMatch(fullName);
}
private void ScanAssembly(Assembly assembly)
{
try
{
if (assembly == null)
return;
// If we already scanned the assembly...
if (_assembliesScanned.Contains(assembly.FullName))
return;
// Is this an excluded assembly?
if (IsExcluded(assembly.FullName))
return;
// Scan the types in the assembly...
foreach (Type type in assembly.GetTypes())
ScanType(type);
// Mark the assembly as scanned...
_assembliesScanned.Add(assembly.FullName);
}
catch (Exception ex)
{
throw;
}
}
private void ScanType(Type type)
{
try
{
if (type.GetCustomAttributes(typeof(DataContractAttribute), false).Length <= 0) return; if (_knownTypes.ContainsKey(type.FullName)) return; bool valid = true; foreach (KeyValuePairpair in _validators)
{
valid = pair.Value.IsValid(type);
if (valid == true)
break;
}
_knownTypes[type.FullName] = type;
}
catch (Exception ex)
{
throw;
}
}
#endregion
#region Fields
private IList_assembliesScanned = new List ();
private static IList_exclusions = new List ();
private Regex _exclusionsEx;
private static IDictionary_knownTypes = new Dictionary ();
private static IDictionary_validators = new Dictionary ();
private static readonly object _synchLock = new object();
#endregion
#region Constants
public const string MethodName = "GetKnownTypes";
#endregion
}
public interface IDataContractKnownTypeValidator
{
bool IsValid(Type type);
}
Unfortunately there is no easy way to attach the ServiceKnownType except to open up the
reference.cs class for the service reference and decorate the class with the ServiceKnownTypeAttribute. However once that has been done and the appropriate assemblies and/or types registered, then whenver you create a new instance of the service a list of known types will be generated and now your data exchange will work.
One interesting point is that you can not get an assembly by a name in Silverlight, so how do you do it? Use can do it once or two ways, depending on whether you have an instance of an object:
Object Instance:
instance.GetType().Assembly;
By Type:
typeof(AppUtilities).Assembly;
You can use partial classes as the service definitions in reference.cs are partial - so you cna maintain your ServiceKnownType attributes in project files and not need to overwrite them each time you regenerate the service reference.
ReplyDelete