Porting a large .NET application to Silverlight
If you've followed the latest in the Dataphor wiki you'll know we're building a Silverlight client for Dataphor. From the sound of "new client", one would think this effort would involve merely adding a new component to Dataphor, but in truth the effort is basically a port. This is due to the fact that a Dataphor client runs a Dataphor engine, and because Silverlight doesn't support "older" .NET technologies such as remoting. All in all, this "new client" actually entails:
- Modernizing the Dataphor code base. The code now makes use of generics, compiler inferred type declarations, lambda functions, etc.
- Removal of legacy portions.
- Splitting of the Dataphor server into Server and Engine.
- Replacement of Remoting with WCF.
- Adding platform support to libraries.
- Delayed class loading.
- Porting of the engine to Silverlight.
- Oh, and a new Silverlight client.
This effort represented nothing short of a massive upheaval of the source base, mostly for the better. I've not seen a detailed account of the porting of something as large as a relational database engine to Silverlight, so we kept notes along the way in hopes that this might help others. There are of course many levels at which this port could be discussed, from architecture to syntax. In this post, I'll just give a rough list of the runtime and BCL issues that were encountered in the port. Perhaps we'll expand on some of these in a future post. We may also post more about the architectural issues, such as thread implcations, in a future post.
Here's the overview:
Issue | Resolution |
Unsafe code for buffer splicing | Replaced with safe alternatives. Added ByteArrayUtility to provide services similar to BitConverter and BinaryReader/BinaryWriter |
Can't reference non-Silverlight projects from Silverlight projects | New Silverlight projects with links for each file |
No HashTable and ArrayList | Replaced with Dictionary and List generics |
Dictionary and List semantic differences |
|
No SerializationAttribute | Removed attributes, replaced with DataContract (WCF) |
No SerializationInfo | Removed, exception overloads replaced with FaultContract |
No StringCollection | Replaced with List<string> |
P/Invoke calls to QueryPerformanceCounter APIs | Replaced with calls to Environment.TickCount |
No Stopwatch class | Implemented alternative based on Environment.TickCount |
No File.GetAttributes() | Moved dependency into platform specific assembly |
No AppDomain.GetAssemblies() | Moved dependency into platform specific assembly |
No Assembly.Load (remaining overloads are security restricted | Refactored to use AssemblyPart.Load from a byte[] |
No WebRequest.GetResponse | Removed dependency |
No WebClient.OpenRead, only async supported | No longer dependency |
No MD5CryptoServiceProvider | Used this to replace framework implementation: http://www.markharris.net.au/blog/2008/10/23/md5cryptoserviceprovider-for-silverlight/ ...Then removed because a comparison was better than a hash in usage case. |
No XmlTextWriter | Replaced with XmlWriter |
No XmlTextReader | Replaced with XmlReader |
No XmlDocument and related | Replaced with XDocument (had to go to 3.5 framework) |
No TypeDescriptor (thus no GetTypeConverter) | Replaced with StringToValue and ValueToString implementations that switch on the type and convert the native and native like types to and from strings |
No Type.GetInterface overload with one argument | Used the two argument version. |
No Diagnostics.Trace class | Used the Debug class instead |
No String.Compare with case insenstivite | Replaced with String.Equals(x, y, StringComparison.OrdinalIgnoreCase) or similar Compare overload |
No Enum.Parse w/o case specifier | Added explicit false third argument. |
No remoting | Replaced with WCF |
No Diagnostics.TraceLevel | Removed usage |
No UnicodeEncoding.ASCII encoding | Replaced with UTF8 encoding |
No 2 argument Convert.ChangeType | Passed Thread.CurrentThread.CurrentCulture as 3rd argument |
No Assembly.GetType passing ignoreCase | Called another overload |
No Rijndael encryption | Replaced with Aes |
No Thread.Interrupt() | Removed usage |
ThreadInterruptedException not public | Rewrote Interrupt pattern with AutoResetEvent() |
No Environment.MachineName | Compiler conditional |
No BitVector32 | Removed reference (could have used BitVector) |
No System.Drawing (Image etc.) | Image replaced with Media.Imaging.BitmapImage. Image.Load() becomes BitmapImage.SetSource() |
No System.Drawing attributes | Attributes redefined as dummies; |
No ComponentModel attributes | Attributes redefined as dummies; |
No ReaderWriterLock | Found custom implementation, modified. Then found usage was minor and removed. |
P/Invoke to Terminal Services API | Compiler defined wrapper class |
ChannelFactory doesn't support overload that takes URI string | Constructed EndpointAddress as argument |
ComponentCollection of IContainer doesn't support enumeration (stub) | Compiler defined usage |
No ManualResetEvent.WaitOne(int, false) overload. | Replaced with invocation with just timeout. |
No Assembly.CreateQualifiedName | Replaced with string concatenation of <name>,<assembly name> |
No Thread.ResetAbort() | Removed reference, we no longer use abort (ever) anyway. |
Assembly.GetName() is marked security critical (can't access) | Wrote parser to get Name and Version from the FullName |
Can't DataMember serialize a private member | Changed to internal and used the "friend class" mechanism to allow the System.Runtime.Serialization assembly to access |
Designer related attributes | Created dummy attributes; used the class name (string) overload rather than typeof() overloads to avoid dependency on designer assemblies. |
No C# compiler | Ported Mono* |
*This is still in-flux. We've ported the mono compiler to Silverlight, but haven't tested it yet. If we run into too much trouble with that, we'll invoke the compiler on the server and load the resulting binary on the client.
Here are some notes regarding the XmlDocument to XDocument porting:
Issue | Resolution |
CreateElement | new XElement(…) |
CreateAttribute | Element.SetAttributeValue |
foreach (XmlAttribute in Element.Attributes) | foreach (XAttribute in Element.Attributes()) |
Node.NamespaceUri | Node.Name.NamespaceName |
Node.Name | Node.Name.LocalName or Node.Name.NamespaceName |
Attributes != null | HasAttributes |
FirstChild, Children | Elements() enumerator |
Document.DocumentElement | Document.Root |
XmlDocument.Normalize() | XDocument.Normalize (not present in Silverlight) ? may not be necessary any longer |
Attributes.Remove | SetAttributeValue(x, null) |
Attributes[x] | Attribute(x) |
XDocument.Load(Stream) | XDocument.Load(XmlReader.Create(Stream)) (BCL doesn't support the overload SL does) |
Node.Parent.RemoveChild(Node) | Node.Remove() |
ChildNodes | Elements() (in most cases) |
Comments
Sounds very cool guys, great job!
Posted by: Scott H | October 7, 2009 10:58 PM