One of the bigger annoyances dealing with programming in Silverlight is the deployment of XAP files. In order to properly update a XAP file you typically:
- Rename XAP file to a ZIP file.
- Extract the ServiceReferences.ClientConfig file.
- Update the configuration file with the proper IP information.
- Update the ZIP file and save.
- Rename ZIP file back to XAP.
Doesn’t that sound stupid? I think so. What the hell was Microsoft thinking?
Q: So, how do we do that with code so we can skip this frustration?
A: Let’s first look at a few factors:
- We are using the .Net 4.0 Framework.
- Don’t bother using System.IO.Packaging.ZipPackage. It thinks XAP files are always corrupt. It’s annoying.
- We are just updating the IP information.
To solve the ZIP file library issue, I turned to DotNetZip: http://dotnetzip.codeplex.com/. It’s by far one of the most convenient libraries out there and it’s free.
First, let’s look at how we update the configuration file if it was committed to a MemoryStream. In this snippet we:
- Grab all the contents from the MemoryStream.
- Replace the IP information in the content.
- Clear the MemoryStream.
- Overwrite the stream contents with the new content.
- Reset the position in the stream to 0.
/// <summary> /// Updates the configuration file. /// </summary> /// <param name="stream">The stream.</param> /// <param name="replacementIp">The replacement ip.</param> /// <param name="destinationIp">The destination ip.</param> /// <returns></returns> private bool UpdateConfigurationFile(MemoryStream stream, string replacementIp, string destinationIp) { bool isSuccessful = false; try { // Read current file var reader = new StreamReader(stream); stream.Position = 0; var contents = reader.ReadToEnd(); // Update IP information var newContents = contents.Replace(replacementIp, destinationIp); // Reset contents of stream stream.SetLength(0); // Overwrite original configuration file var writer = new StreamWriter(stream); writer.Write(newContents); writer.Flush(); // Set position in stream to 0. // This allows us to start writing from the beginning of the stream. stream.Seek(0, SeekOrigin.Begin); // Success isSuccessful = true; } catch (Exception) { } // return return isSuccessful; }
Our main method below does this:
- Extract the ServiceReferences.ClientConfig file.
- Call the UpdateConfigurationFile method above to revise the IP information.
- Update the ZIP file and commit the changes.
/// <summary> /// Updates the silverlight configuration file. /// </summary> /// <param name="configFileName">Name of the config file.</param> /// <param name="xapFilePath">The xap file path.</param> /// <param name="replacementIp">The replacement ip.</param> /// <param name="destinationIp">The destination ip.</param> /// <returns></returns> private bool UpdateSilverlightConfigurationFile(string configFileName, string xapFilePath, string replacementIp, string destinationIp) { // Check file path if (!File.Exists(xapFilePath)) { return false; } // Open XAP and modify configuration using (var zip = ZipFile.Read(xapFilePath)) { // Get config file var entry = zip[configFileName]; var stream = new MemoryStream(); entry.Extract(stream); // Manipulate configuration var updated = UpdateConfigurationFile(stream, replacementIp, destinationIp); // Evaluate if (updated) { // Replace existing configuration file in XAP zip.UpdateEntry(configFileName, stream); zip.Save(); } } // return return true; }
So, let’s look at the code in it’s entirety so that we get an implementation example as well as the needed includes:
using System; using System.IO; using System.Windows; using System.Windows.Controls; using Ionic.Zip; namespace XAPFileUpdaterTest { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainPage : UserControl { public MainPage() { // Intialize UI InitializeComponent(); // Parameters string configFile = "ServiceReferences.ClientConfig"; string xap = @"MyAwesomeApp.xap"; string replacementIp = "127.0.0.1"; string destinationIp = "12.23.45.67"; // Check var isClientConfigUpdated = UpdateSilverlightConfigurationFile( configFile, xap, replacementIp, destinationIp); // Setup message var message = (isClientConfigUpdated) ? "was successful" : "failed"; // Notify user MessageBox.Show("The current update " + message); } /// <summary> /// Updates the configuration file. /// </summary> /// <param name="stream">The stream.</param> /// <param name="replacementIp">The replacement ip.</param> /// <param name="destinationIp">The destination ip.</param> /// <returns></returns> private bool UpdateConfigurationFile(MemoryStream stream, string replacementIp, string destinationIp) { bool isSuccessful = false; try { // Read current file var reader = new StreamReader(stream); stream.Position = 0; var contents = reader.ReadToEnd(); // Update IP information var newContents = contents.Replace(replacementIp, destinationIp); // Reset contents of stream stream.SetLength(0); // Overwrite original configuration file var writer = new StreamWriter(stream); writer.Write(newContents); writer.Flush(); // Set position in stream to 0. // This allows us to start writing from the beginning of the stream. stream.Seek(0, SeekOrigin.Begin); // Success isSuccessful = true; } catch (Exception) { } // return return isSuccessful; } /// <summary> /// Updates the silverlight configuration file. /// </summary> /// <param name="configFileName">Name of the config file.</param> /// <param name="xapFilePath">The xap file path.</param> /// <param name="replacementIp">The replacement ip.</param> /// <param name="destinationIp">The destination ip.</param> /// <returns></returns> private bool UpdateSilverlightConfigurationFile(string configFileName, string xapFilePath, string replacementIp, string destinationIp) { // Check file path if (!File.Exists(xapFilePath)) { return false; } // Open XAP and modify configuration using (var zip = ZipFile.Read(xapFilePath)) { // Get config file var entry = zip[configFileName]; var stream = new MemoryStream(); entry.Extract(stream); // Manipulate configuration var updated = UpdateConfigurationFile(stream, replacementIp, destinationIp); // Evaluate if (updated) { // Replace existing configuration file in XAP zip.UpdateEntry(configFileName, stream); zip.Save(); } } // return return true; } } }
With the help of DotNetZip we were able to update the XAP file with our revised IP information.
That’s all for now. I hope I helped you with this annoyance.
Thank you for posting this, prior to this I usually use 7zip by opening the xap file in 7zip, and modify the config file from there, it works but it will be tough when you deploy the application.