為了能夠讓使用者自行部署ClickOnce應用程式,需要編寫一個生成ClickOnce應用程式的ClickOnce專用安裝程式setup.exe,而生成這個setup.exe的方法就是編寫一個XML格式的生成配置檔案,使用MSBuild.exe來建立。
一般情況下,建立XML檔案本來是個很簡單的事情,用XDocument、XElement、XAttribute一頓Add,然後Save成檔案就完成了。但是建立setup.exe用的XML檔案的根節點(Project)必須指定XML名稱空間"http://schemas.microsoft.com/developer/msbuild/2003",形式如下:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <BootstrapperFile Include=".NETFramework,Version=v4.0"> <ProductName>Microsoft .NET Framework 4 (x86 和 x64)</ProductName> </BootstrapperFile> <BootstrapperFile Include="Microsoft.Windows.Installer.3.1"> <ProductName>Windows Installer 3.1</ProductName> </BootstrapperFile> </ItemGroup> <Target Name="BuildBootstrapper"> <GenerateBootstrapper ApplicationFile="MyApp.application" ApplicationName="我的應用程式" ApplicationUrl="http://192.168.0.8/MyApp/" BootstrapperItems="@(BootstrapperFile)" CopyComponents="false" OutputPath="." Path="X:\SerupExeBuilder\Packages"/> </Target> </Project>
於是,生成該XML的程式碼則寫為:
#region ItemGroup XElement itemGroup = new XElement("ItemGroup", new XElement("BootstrapperFile", new XAttribute("Include", ".NETFramework,Version=v4.0"), new XElement("ProductName", "Microsoft .NET Framework 4 (x86 和 x64%)")), new XElement("BootstrapperFile", new XAttribute("Include", "Microsoft.Windows.Installer.3.1"), new XElement("ProductName", "Windows Installer 3.1")) ); #endregion #region Target XElement target = new XElement("Target"); target.Add(new XAttribute("Name", "BuildBootstrapper")); XElement generateBootstrapper = new XElement("GenerateBootstrapper"); generateBootstrapper.Add(new XAttribute("ApplicationFile", applicationFile)); generateBootstrapper.Add(new XAttribute("ApplicationName", applicationName)); generateBootstrapper.Add(new XAttribute("ApplicationUrl", applicationUrl)); if (componentsLocation == 1) { generateBootstrapper.Add(new XAttribute("ComponentsLocation", "Relative")); } else if (componentsLocation == 2 && !string.IsNullOrWhiteSpace(componentsUrl)) { generateBootstrapper.Add(new XAttribute("ComponentsLocation", "Absolute")); generateBootstrapper.Add(new XAttribute("ComponentsUrl", componentsUrl)); } generateBootstrapper.Add(new XAttribute("BootstrapperItems", "@(BootstrapperFile)")); generateBootstrapper.Add(new XAttribute("CopyComponents", false)); generateBootstrapper.Add(new XAttribute("OutputPath", outputPath)); string packagesPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); generateBootstrapper.Add(new XAttribute("Path", Path.GetDirectoryName(Assembly.GetEntryAssembly().Location))); target.Add(generateBootstrapper); #endregion XNamespace xmlns = "http://schemas.microsoft.com/developer/msbuild/2003"; XElement root = new XElement(xmlns + "Project"); root.Add(itemGroup); root.Add(target); XDocument setupDocument = new XDocument(); setupDocument.Add(root); setupDocument.Declaration = new XDeclaration("1.0", "UTF-8", "yes"); setupDocument.Save(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "setup.xml"));
但是生成的XML檔案中,ItemGroup和Target節點上會出現 xmlns="" 的屬性,而這在編譯的時候是不允許的(也是錯誤的),而又沒有辦法不讓它們出現。
這個問題鬱悶了一陣子,也沒找到資料,最後用土辦法,也就是StringBuilder手工建立,程式是成功了,但是這個問題一直耿耿於懷。
今天終於知道原因了,當然也知道了“解決辦法”了。
其實原因是,在XML中,如果某個節點使用了NameSpace,則要求其下的每個節點都必須指定NameSpace的,以便於區分各個節點屬於哪一個NameSpace。在生成XML檔案的時候,如果子節點的NameSpace與父節點相同,則忽略xmlns屬性。所以在建立子節點的時候,必須為子節點指定NameSpace,否則就會因為沒有指定NameSpace,出現 xmlns="" 的情況。
所以是對XML的NameSpace瞭解的不夠,才導致程式的錯誤寫法。因此,改為正確的寫法,問題自然就消失不見了。當然,這個實際上不能稱為“解決辦法” 。
另外,該setup.exe也可以使用Microsoft.Build.Tasks.GenerateBootstrapper生成,但是目前只做到生成,尚未進行實踐驗證。