Welcome to EMC Consulting Blogs Sign in | Join | Help

Jon Sharratt's Blog

Microsoft Windows Phone 7 – MVVM Light NotifyPropertyChanged with Mono Cecil

As you may or may not be aware I have recently spending some time trying to get a re-usable MVVM template together.  On my voyage of discovery when creating this I wanted to take out the standard boiler plate NotifyPropertyChanged code form the MVVM Light Toolkit.  I wanted to use AOP (Aspect Orientated Programming) so that on properties I could just have a single attribute [PropertyChanged] and then it would all be taken care of automagically.  Obviously the big advantage of this is that I could then use auto properties to provide a much cleaner code base within my ViewModels.

Example of the end result:

[PropertyChanged]
public string RecordingMenuItemText { get; set; }

So how do we go about this ? Enter the world of Mono Cecil and a custom build task to the rescue.  The process I followed was quite simple, build the application and then weave IL against any properties that have the [PropertyChanged] attribute via Mono Cecil.  After this was done I just needed to update the XAP file so that i gets deployed to the Emulator / Device.

 

image

Putting it all together…

To code for the build task takes a single property so you can specify the assembly to weave the code into.  Below is the code I used to inject IL from the MVVM Light Toolkit.  I added a reference to the MVVM Light assembly so that I could weave the IL for notify property changed method within the ViewModelBase class adding in the string parameter for the property name from MVVM Light.

namespace SugarTank.Build
{
using System;
using System.Reflection;
using GalaSoft.MvvmLight;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.Cecil;
using Mono.Cecil.Cil;

public class PropertyChangedTask : Task
{
/// <summary>
/// When overridden in a derived class, executes the task.
/// </summary>
/// <returns>
/// true if the task successfully executed; otherwise, false.
/// </returns>
public override bool Execute()
{
this.InjectMsil();

return true;
}

/// <summary>
/// Injects the MSIL.
/// </summary>
private void InjectMsil()
{
ModuleDefinition module = ModuleDefinition.ReadModule(this.AssemblyPath);

foreach (TypeDefinition type in module.Types)
{
foreach (PropertyDefinition prop in type.Properties)
{
foreach (CustomAttribute attribute in prop.CustomAttributes)
{
if (attribute.Constructor.DeclaringType.FullName.Contains("PropertyChanged"))
{
ILProcessor msilWorker = prop.SetMethod.Body.GetILProcessor();

Instruction ldarg0 = msilWorker.Create(OpCodes.Ldarg_0);

var raisePropertyChangedInfo = typeof (ViewModelBase).GetMethod(
"RaisePropertyChanged",
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance, null, new[] {typeof (string)}, null);

var raisePropertyChanged = module.Import(raisePropertyChangedInfo);

Instruction propertyName = msilWorker.Create(OpCodes.Ldstr, prop.Name);

Instruction callRaisePropertyChanged = msilWorker.Create(OpCodes.Callvirt,
raisePropertyChanged);

msilWorker.InsertBefore(prop.SetMethod.Body.Instructions[0],
msilWorker.Create(OpCodes.Nop));

msilWorker.InsertBefore(
prop.SetMethod.Body.Instructions[prop.SetMethod.Body.Instructions.Count - 1],
ldarg0);

msilWorker.InsertAfter(ldarg0, propertyName);

msilWorker.InsertAfter(propertyName, callRaisePropertyChanged);

msilWorker.InsertAfter(callRaisePropertyChanged, msilWorker.Create(OpCodes.Nop));
}
}

module.Write(AssemblyPath);
}
}
}

/// <summary>
/// Gets or sets the assembly path.
/// </summary>
/// <value>The assembly path.</value>
[Required]
public string AssemblyPath { get; set; }
}
}

Now all we need to do is edit the Windows Phone 7 project file and hook into the after build event to execute our build task.  To do this I just edited the project file in notepad and added the following:

  <Import Project="$(SolutionDir)References\Community Tasks\MSBuild.Community.Tasks.Targets" />
  <UsingTask TaskName="SugarTank.Build.PropertyChangedTask" AssemblyFile="$(SolutionDir)References\Custom Build Tools\SugarTank.Build.dll"/>

  <Target Name="AfterBuild">
    <CallTarget RunEachTargetSeparately="True" Targets="CopyManifest;CopySplashScreen;WeaveCode;CreateZip" />
  </Target>
  <Target Name="CopyManifest">
    <Copy SourceFiles="$(ProjectDir)\Properties\WMAppManifest.xml" DestinationFolder="$(TargetDir)" />
  </Target>
  <Target Name="CopySplashScreen">
    <Copy SourceFiles="$(ProjectDir)\SplashScreenImage.jpg" DestinationFolder="$(TargetDir)" />
  </Target>
  <Target Name="WeaveCode">
    <PropertyChangedTask AssemblyPath="$(TargetPath)" />
  </Target>
  <Target Name="CreateZip">
    <ItemGroup>
      <ZipFiles Include="$(TargetDir)\*.*" Exclude="$(TargetDir)\*.xap;$(TargetDir)\*.pdb;$(TargetDir)\*.zip" />
    </ItemGroup>
    <Zip Files="@(ZipFiles)" WorkingDirectory="$(TargetDir)" ZipFileName="$(TargetDir)SugarTank.Mobile.xap" />
  </Target>

You will notice I have a few other targets I run, these manually create the XAP file as I couldn’t find a way via MSBuild to open up the XAP file and replace the single assembly.  I was hoping after the IL was weaved in the XAP file would be created and the updated assembly would automatically make its way into the XAP.  

But from what I can see it creates the XAP before the assembly has the code weaved into it which has forced me to take this approach and manually create a valid XAP.

If you know of a better approach to amend the XAP file it would be greatly appreciated.
@jsharratt

Published 17 January 2011 12:47 by jon.sharratt

Comments

No Comments
Anonymous comments are disabled

This Blog

Syndication

News

Powered by Community Server (Personal Edition), by Telligent Systems