Skip to main content

Namespace prefixes in Microsoft Xml Document Transformation's SetAttributes and RemoveAttributes transforms

Link for the Codeplex fork I mention in this article: https://xdt.codeplex.com/SourceControl/network/forks/LordZoltan/XDTEx

Get this package from Nuget using the package ID Microsoft.Web.XdtEx.

Microsoft recently open-sourced the XDT library that's at the heart of the web.config transformations.  Partner this up with a new feature of Nuget 2.5 which supports the auto-importing of MSBuild .props and .targets files into a project file, and that means I could try something out with XML files in a Xamarin.Android project I'm working on at the moment.

I'm building an Android app (obviously) and due to our company's needs, we want to build a vanilla app which can then be re-used for other brands within the same group.  I had the idea that I could create the whole thing as a nuget package, now that Nuget supports the 'MonoAndroid' platform.

Deploying code to projects with nuget is easy - and with the partial class model in C# it's simple to deploy a core platform to a project with one set of code files that never change (thus letting us manage them with nuget's install/upgrade workflow), and then adding extensibility points with partial methods (in addition to virtual methods etc).

However, I also needed to be able to do the same for android resource files.  Consider the default main.axml from a new Xamarin.Android project:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <Button
    android:id="@+id/MyButton"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/Hello"
    />
</LinearLayout>

Now, in my scenario, I'd want to deploy this core layout as content in my nuget package, but then I want people to be able to customise it without touching the original.

Let's say, for example, that I want to change the text on the <Button /> element there from '@string/Hello' (an Android resource identifier) to the literal string 'Hello World'.  This is how we'd want to do it in XDT:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <Button
            android:id="@+id/MyButton"
            xdt:Locator="Match(android:id)"
            xdt:Transform="SetAttributes(android:text)"
            android:text="Hello World"
    />
</LinearLayout>

See the 'xdt:Transform="SetAttributes(android:text)"' attribute there? (If it worked in the base version) That would instruct XDT to replace the android:text attribute of our input file to "Hello World", because that's the content used here.

There is a slight snag, however: the current (as of 10th May 2013) version of XDT (available on codeplex, or, on nuget.org as a package) does not support namespace prefixes in xdt:Transform operations.  It does support them in the xdt:Locator attribute, however.

Again, since XDT is now open-source (although not yet accepting pull requests), I decided to fork it and have a go at putting it in - and an hour or so later I had it working.  You can see the diff here on codeplex: the primary changes are in XmlElementContext.cs, XmlTransform.cs and XmlAttributeTransform.cs, the rest are tests.  Note that in the next commit I renamed the test from LocatorNamespaceTests.cs to NamespacePrefixTests.cs.

If you're currently using the XDT library (either directly, from a codeplex build, or via the official nuget package) you will be able to switch over to using this version without issues.  If you're coming from an official release, then one thing to note is that this DLL is not signed - as is the case with the original codeplex release.

So what about the Android transform?

If you're wondering - yes I've managed to get the build integration working from a nuget package - and to have my files added to the project like this:

image

The xtransform is the file that is edited, the xbase is the file that contains the default content, and main.axml is generated by the build task before build (and before Xamarin.Android compiles it).

However, it's not been easy at all to do this - as it involves writing an install.ps1 to add the <DependentUpon /> metadata to the .axml.xbase and .axml files - which unfortunately requires a VS reload of the project in order to show the hierarchy in the IDE.  I use the Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection to get the MSBuild project (I'm not including the powershell scripts because it detracts from the main point in this post) in order to be able to add the metadata more easily, but I then have to save the project through that as well in order to trigger a reload.

If you do a search around, you'll find the official line is to save a project in a nuget package install/uninstall script through the $project parameter that is passed to your script, as this is a 'friendly' save, which doesn't reload the environment (and therefore almost always what you want to do).  The problem is that VS will only update the file hierarchy of dependent files when a project is reloaded - and there's no command in VS you can execute to perform this grouping (unless VSCommands is installed). Bah!

And then on uninstall/upgrade, the file hierarchy causes big issues, because if nuget removes a file that is a parent of others, then Visual Studio will automatically remove the children.  This means that my initial hierarchical attempt - which was to have xbase -> xtransform -> output as the hierarchy - kept dropping both the xtransform and output files even through they'd changed - because they were deleted when the xbase was deleted.

Furthermore, flattening the hierarchy again in the uninstall.ps1 script didn't make any difference, because the underlying change is not reflected in the environment unless a project reload occurs.

I'm not really sure if there's any way around this - so I went for putting the editable file at the root instead.

UPDATE - This doesn't work either - because nuget doesn't then find the xbase file when it goes looking for it.  I've had to write an Uninstall.ps1 script for the package that does nuget's job, finding the item and deleting it pseudo-manually.

What about a nuget package?

Yup - I've updated the post to include the package ID and nuget.org link - but here it is again for completeness: Microsoft.Web.XdtEx.

Comments

Popular posts from this blog

Asp.Net 2 and 4 default application pool generates CS0016 IIS7.5

Before I start – if you’ve found a bunch of other articles about this around the net, tried the fixes that are mentioned and still not getting any joy – then read on – you might find this solves your problem. Earlier today I discovered that when I run any ASP.Net 2 or 4 application through IIS7.5 using the default application pools (which use ApplicationPoolIdentity) on Windows 2008 R2 x64 I get an error message similar to this: Server Error in '/MvcApplication31' Application. Compilation ErrorDescription: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.
Compiler Error Message: CS0016: Could not write to output file 'c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\mvcapplication31\222b4fe6\4e80a86\App_global.asax.clb4bsnc.dll' -- 'The directory name is invalid. '
Source Error:[No relevant source lines]
Sou…

Serializing to attributes in WCF with DataContractSerializer

It’s a common problem – you want to return an object from a WCF service as XML, but you either want, or need, to deliver some or all of the property values as XML Attributes instead of XML Elements; but you can’t because the DataContractSerializer doesn’t support attributes (you’re most likely to have seen this StackOverflow QA if you’ve done a web search).  Most likely you’ve then migrated all your WCF service code to using the XmlSerializer (with all the XmlElement/XmlAttribute/XmlType attributes et al) – and you’ve cursed loudly.Well, I’m here to rescue you, because it is possible – and the answer to the problem is actually inferred from the MSDN article entitled ‘Types supported by the Data Contract Serializer’.The example I’m going to give is purely for illustration purposes only.  I don’t have a lot of time, so work with me!Create a new Asp.Net WCF service application, you can use Cassini as your web server (probably easier – otherwise you might have to enable Asp.Net compatibil…

Adding ‘Deny’ functionality to AuthorizeAttribute in Asp.Net Web API

For the web service project I’m working on at the moment I need to be able to treat authorization differently based on the hostname of the URL that requests are made through.To state more clearly – these web services will have a ‘sandbox’ mode in addition to the real mode, and the mode a request will operate under is determined as part of the controller-selection phase early in the Web API request lifecycle.  So, say that my web services will be hosted on services.acme.com; the sandbox will simply be sandbox.services.acme.com.Please note – a discussion of how this is implemented is entirely outside the scope of this article; but I’ll just say that I’ve developed an in-house multi-tenancy layer for both MVC 4 and Web API that allows us to define ‘brands’ and, under those, you can then redefine content, controllers, and even the DI container that is used.These services are going to require caller-level authentication for most operations via SCRAM Authentication (RFC 5802), and as such m…