{"id":1822,"date":"2011-05-24T10:47:57","date_gmt":"2011-05-24T08:47:57","guid":{"rendered":"http:\/\/www.limilabs.com\/blog\/?p=1822"},"modified":"2014-12-17T18:28:37","modified_gmt":"2014-12-17T16:28:37","slug":"inotifypropertychanged-with-custom-targets","status":"publish","type":"post","link":"https:\/\/www.limilabs.com\/blog\/inotifypropertychanged-with-custom-targets","title":{"rendered":"INotifyPropertyChanged with custom targets"},"content":{"rendered":"<p>Wouldn&#8217;t it be nice to have a simple attribute instead of backing field madness in Silverlight\/WPF? Just like this:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class MainViewModel : ViewModelBase\r\n{\r\n    &#x5B;NotifyPropertyChanged]\r\n    public string Title { get; set; }\r\n}\r\n<\/pre>\n<p>You can use <a href=\"\/blog\/inotifypropertychanged-with-postsharp\"> PostSharp for that<\/a>, you should at least <a href=\"\/blog\/inotifypropertychanged-with-lambdas\">use lambda expressions instead of strings<\/a>.<\/p>\n<p>Here&#8217;s how to do it without 3rd party software:<\/p>\n<p>1.<br \/>\nIn your project <strong>declare base view model<\/strong>:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class ViewModelBase : INotifyPropertyChanged\r\n{\r\n    public event PropertyChangedEventHandler PropertyChanged\r\n        = delegate { };\r\n\r\n    protected void OnPropertyChanged(string propertyName)\r\n    {\r\n        this.PropertyChanged(\r\n            this,\r\n            new PropertyChangedEventArgs(propertyName));\r\n    }\r\n};\r\n<\/pre>\n<p>2.<br \/>\n<strong>Declare the attribute<\/strong><\/p>\n<p>It will be used to mark properties that should raise the PropertyChanged event.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n&#x5B;AttributeUsage(AttributeTargets.Property)]\r\npublic class NotifyPropertyChangedAttribute : Attribute\r\n{\r\n}\r\n<\/pre>\n<p>3.<br \/>\nCreate <strong>new MSBuildTasks library project<\/strong> (it can be in different solution)<\/p>\n<p>Add references to:<\/p>\n<ul>\n<li>Mono.Cecil.dll<\/li>\n<li>Mono.Cecil.Pdb.dll (this needed so Cecil can updated pdb file, which is need for debugging the modified assembly)<\/li>\n<li>MSBuild assemblies<\/li>\n<\/ul>\n<p>4.<br \/>\n<strong>Create new MSBuild task<\/strong><\/p>\n<p>It will inject PropertyChanged event invocation on properties marked with NotifyPropertyChanged attribute.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing System;\r\nusing System.IO;\r\nusing Microsoft.Build.Framework;\r\nusing Microsoft.Build.Utilities;\r\nusing Mono.Cecil;\r\nusing Mono.Cecil.Cil;\r\n\r\nnamespace MSBuildTasks\r\n{\r\n    public class NotifyPropertyChangedTask : Task\r\n    {\r\n        public override bool Execute()\r\n        {\r\n            InjectMsil();\r\n            return true;\r\n        }\r\n\r\n        private void InjectMsil()\r\n        {\r\n            var assemblyDefinition = AssemblyDefinition.ReadAssembly(\r\n                AssemblyPath,\r\n                new ReaderParameters { ReadSymbols = true });\r\n\r\n            var module = assemblyDefinition.MainModule;\r\n\r\n            foreach (var type in module.Types)\r\n            {\r\n                foreach (var prop in type.Properties)\r\n                {\r\n                    foreach (var attribute in prop.CustomAttributes)\r\n                    {\r\n                        string fullName = attribute.Constructor.DeclaringType.FullName;\r\n                        if (fullName.Contains(&quot;NotifyPropertyChanged&quot;))\r\n                        {\r\n                            InjectMsilInner(module, type, prop);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            assemblyDefinition.Write(\r\n                this.AssemblyPath,\r\n                new WriterParameters { WriteSymbols = true });\r\n        }\r\n\r\n        private static void InjectMsilInner(\r\n            ModuleDefinition module,\r\n            TypeDefinition type,\r\n            PropertyDefinition prop)\r\n        {\r\n            var msilWorker = prop.SetMethod.Body.GetILProcessor();\r\n            var ldarg0 = msilWorker.Create(OpCodes.Ldarg_0);\r\n\r\n            MethodDefinition raisePropertyChangedMethod =\r\n                FindRaisePropertyChangedMethod(type);\r\n            if (raisePropertyChangedMethod == null)\r\n                throw new Exception(\r\n                    &quot;RaisePropertyChanged method was not found in type &quot;\r\n                    + type.FullName);\r\n\r\n            var raisePropertyChanged = module.Import(\r\n                raisePropertyChangedMethod);\r\n            var propertyName = msilWorker.Create(\r\n                OpCodes.Ldstr,\r\n                prop.Name);\r\n            var callRaisePropertyChanged = msilWorker.Create(\r\n                OpCodes.Callvirt,\r\n                raisePropertyChanged);\r\n\r\n            msilWorker.InsertBefore(\r\n                prop.SetMethod.Body.Instructions&#x5B;\r\n                    prop.SetMethod.Body.Instructions.Count - 1],\r\n                ldarg0);\r\n\r\n            msilWorker.InsertAfter(ldarg0, propertyName);\r\n            msilWorker.InsertAfter(\r\n                propertyName,\r\n                callRaisePropertyChanged);\r\n        }\r\n\r\n        private static MethodDefinition FindRaisePropertyChangedMethod(\r\n            TypeDefinition type)\r\n        {\r\n            foreach (var method in type.Methods)\r\n            {\r\n                if (method.Name == &quot;RaisePropertyChanged&quot;\r\n                    &amp;&amp;  method.Parameters.Count == 1\r\n                    &amp;&amp;  method.Parameters&#x5B;0].ParameterType.FullName\r\n                        == &quot;System.String&quot;)\r\n                {\r\n                    return method;\r\n                }\r\n            }\r\n            if (type.BaseType.FullName == &quot;System.Object&quot;)\r\n                return null;\r\n            return FindRaisePropertyChangedMethod(\r\n                type.BaseType.Resolve());\r\n        }\r\n\r\n        &#x5B;Required]\r\n        public string AssemblyPath { get; set; }\r\n    }\r\n}\r\n<\/pre>\n<p>5.<br \/>\n<strong>Compile the task assembly<\/strong>,<\/p>\n<p>and copy it to &#8220;$(SolutionDir)\/..\/lib\/MSBuild\/MSBuildTasks.dll&#8221; folder along with Mono.Cecil.dll and Mono.Cecil.Pdb.dll assemblies.<\/p>\n<p>4.<br \/>\nFinally <strong>modify your Silverlight\/WPF project (.csproj file)<\/strong>:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;project&gt;\r\n  ...\r\n  &lt;usingTask TaskName=&quot;MSBuildTasks.NotifyPropertyChangedTask&quot; AssemblyFile=&quot;$(SolutionDir)..libMSBuildMSBuildTasks.dll&quot; \/&gt;\r\n  &lt;target Name=&quot;AfterCompile&quot;&gt;\r\n    &lt;msbuildTasks.NotifyPropertyChangedTask AssemblyPath=&quot;$(ProjectDir)obj$(Configuration)$(TargetFileName)&quot; \/&gt;\r\n  &lt;\/target&gt;\r\n&lt;\/project&gt;\r\n<\/pre>\n<p>Voila! Enjoy!<\/p>\n<p>Here&#8217;s the source code of the MSBuildTasks project:<br \/>\n<a href='\/blog\/wp-content\/uploads\/2011\/05\/MSBuildTasks.zip'>MSBuildTasks<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wouldn&#8217;t it be nice to have a simple attribute instead of backing field madness in Silverlight\/WPF? Just like this: public class MainViewModel : ViewModelBase { &#x5B;NotifyPropertyChanged] public string Title { get; set; } } You can use PostSharp for that, you should at least use lambda expressions instead of strings. Here&#8217;s how to do it [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[15,48,62],"class_list":["post-1822","post","type-post","status-publish","format-standard","hentry","category-programming","tag-c","tag-silverlight","tag-wpf"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/posts\/1822"}],"collection":[{"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/comments?post=1822"}],"version-history":[{"count":2,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/posts\/1822\/revisions"}],"predecessor-version":[{"id":4879,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/posts\/1822\/revisions\/4879"}],"wp:attachment":[{"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/media?parent=1822"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/categories?post=1822"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/tags?post=1822"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}