Friday, February 15, 2013

WCF Behaviors. Wildcard name or empty string name.

Few days ago I found a WCF behavior configuration section similar to this:

<behaviors>
    <endpointBehaviors>
        <behavior name="*">
     <webHttp/>
        </behavior>
    </endpointBehaviors>
</behaviors>

Does name="*" make any sense? .net 4 comes with great impovements in default configurations. And if one wants to specify a behavior for all endpoints then behavior with an empty name (or even without the name attribute at all) is used. So what about wildcards? Usually an asterics is used for exactly the same purpose. What is the difference then? This question marked as answered actually doesn't contain the answer.

First of all we can use an asterics as an argument during channel factory creation.

var factory = new ChannelFactory<IMyService>("*");

In this case first endpoint configuration is taken. The feature is described in more details here.
It has nothing to do with behaviors.

In the configuration above the "*" symbol acts as a regular behaviorConfiguration name. You can reference it in any of our endpoints.

<endpoint 
    address="http://localhost:9001/http/" 
    contract="Shared.IMyService" 
    binding="basicHttpBinding" 
    behaviorConfiguration="*"/>

But if you want apply a behavior to all of your enpoints just use an empty name for it.

<behavior name="">
    <webHttp/>
</behavior>

Or even

<behavior>
    <webHttp/>
</behavior>

You can read more about WCF configuration defaults in this article.

Thursday, February 14, 2013

WCF. Fighting your way through a proxy.

Recently I discovered that my desktop tool for memorizing English words doesn't work when a client is behind a proxy.

The problem was pretty common and I quickly found this awesome answer on stackoverflow. But still there were a few things to deal with:
1. Move all those hardcoded strings to config files.
2. Define proxy usage per binding.
3. Test the application.

First might be easily accomplished using ConfigurationManager class. Here is the code I got:

using System;
using System.Configuration;
using System.Net;
using log4net;

namespace VX.Desktop.Infrastructure
{
    public class CustomProxy : IWebProxy
    {
        private const string CustomProxyAddressKey = "CustomProxyAddress";
        private const string CustomProxyUserKey = "CustomProxyUser";
        private const string CustomProxyPassword = "CustomProxyPassword";

        private readonly ILog logger = LogManager.GetLogger(typeof (CustomProxy));
        
        public Uri GetProxy(Uri destination)
        {
            var proxyAddress = ConfigurationManager.AppSettings[CustomProxyAddressKey];
            if (string.IsNullOrEmpty(proxyAddress))
            {
                logger.Error("Error retrieving CustomProxyAddress from configuration file. Make sure you have a corresponding key in appSettings section of application config file.");
                return null;
            }
            
            logger.InfoFormat("Proxy address: {0}", proxyAddress);
            return new Uri(proxyAddress);
        }

        public bool IsBypassed(Uri host)
        {
            logger.InfoFormat("IsBypassed for host: {0} is false", host);
            return false;
        }

        public ICredentials Credentials
        {
            get
            {
                logger.InfoFormat("Getting proxy credentials");
                string userName = ConfigurationManager.AppSettings[CustomProxyUserKey];
                string password = ConfigurationManager.AppSettings[CustomProxyPassword];
                logger.InfoFormat("Done. {0}", userName);
                if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
                {
                    logger.Error(
                        "Error retrieving proxy credentials from configuration file. Make sure you have corresponding keys in appSettings section of application config file.");
                }

                return new NetworkCredential(userName, password);
            }
            set { }
        }
    }
}

Nice ways of addressing the second and the third issues are described here.
So we should use something like:

<bindings>
    <basicHttpBinding>
        <binding name="myBindingWithProxy" useDefaultWebProxy="true" />
    </basicHttpBinding>
</bindings>
instead of
<defaultProxy enabled="true" useDefaultCredentials="false">
  <module type = "SomeNameSpace.MyProxy, SomeAssembly" />
</defaultProxy>

Now we can use other bindings without any proxies. And to test all this stuff we can use a great tool - Fiddler. Just check the Rules -> Require Proxy Authentication option and you're done.

Monday, January 14, 2013

Using the Razor engine for rendering email templates

The problem. You want to have a flexible infrastructure to manage emails that are sent to users automatically.
The solution. First thing to deal with is making a text of an email body configurable. Hardcoding a template is not a good idea unless you want to recompile and redeploy your application each time you need to change the text. Templates can be stored in a database or in files, the exact solution depends on the situation. Second you need to find a way to insert a dynamic content to your emails. In this case you have to use placeholders in body text and replace them somewhere in the code.

Hello #FirstName# #LastName#!

string.Replace("#FirstName#", "Evgeny");
string.Replace("#LastName#", "Skurikhin");

This is an appropriate way only for very simple templates. But what if you need to render a table and you don't know the number of rows beforehand (only during runtime)? Well we still can use some kind of markers in email body. And what about conditional rendering? Hmm... another markers. But why reinvent the wheel? Let's use Razor.

After some research I found that the problem is quite common, there are multiple libraries that address the issue. RazorEngineRazorTemplates and RazorMachine are the most popular among developers. So I decided to try them all and choose the one that suits my needs.

Consider the following test class:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using RazorEngine;
using RazorTemplates.Core;
using Xipton.Razor;

namespace RazorEngineExample
{
    [TestClass]
    public class EngineTests
    {
        private const string CorrectResult =
            "<h1>FirstLevelField</h2><p>SecondLevelField</p><table><tr><td>ThirdLevelItem1</td></tr><tr><td>ThirdLevelItem2</td></tr><tr><td>ThirdLevelItem3</td></tr></table>";
        
        private readonly object fullyDynamicModel = new
        {
            FirstLevelValue = "FirstLevelField",
            NestedField = new
                {
                    SecondLevel = "SecondLevelField",
                    ThirdLevelList = new[]
                        {
                            new {Item = "ThirdLevelItem1"},
                            new {Item = "ThirdLevelItem2"},
                            new {Item = "ThirdLevelItem3"}
                        }
                }
        };

        private readonly object partiallyDynamicModel = new
        {
            FirstLevelValue = "FirstLevelField",
            NestedField = new
            {
                SecondLevel = "SecondLevelField",
                ThirdLevelList = new[]
                    {
                        new MyItem {Item = "ThirdLevelItem1"},
                        new MyItem {Item = "ThirdLevelItem2"},
                        new MyItem {Item = "ThirdLevelItem3"},
                    }
            }
        };

        private const string MyTemplate =
            "<h1>@Model.FirstLevelValue</h2><p>@Model.NestedField.SecondLevel</p><table>@foreach(var item in @Model.NestedField.ThirdLevelList){<tr><td>@item.Item</td></tr>}</table>";

        [TestMethod]
        public void RazorEngineFullyDynamicModelTest()
        {
            CheckResult(Razor.Parse(MyTemplate, fullyDynamicModel));
        }

        [TestMethod]
        public void RazorTemplatesFullyDynamicModelTest()
        {
            CheckResult(Template.Compile(MyTemplate).Render(fullyDynamicModel));
        }

        [TestMethod]
        public void RazorMachineFullyDynamicModelTest()
        {
            CheckResult(new RazorMachine().Execute(MyTemplate, fullyDynamicModel).Result);
        }

        [TestMethod]
        public void RazorEnginePartiallyDynamicModelTest()
        {
            CheckResult(Razor.Parse(MyTemplate, partiallyDynamicModel));
        }

        [TestMethod]
        public void RazorTemplatesPartiallyDynamicModelTest()
        {
            CheckResult(Template.Compile(MyTemplate).Render(partiallyDynamicModel));
        }

        [TestMethod]
        public void RazorMachinePartiallyDynamicModelTest()
        {
            CheckResult(new RazorMachine().Execute(MyTemplate, partiallyDynamicModel).Result);
        }

        private static void CheckResult(string actual)
        {
            Assert.AreEqual(CorrectResult, actual);
        }
    }

    public class MyItem
    {
        public string Item { get; set; }

    }
}

It contains two simple models. The first one is fully dynamic while the second one contains typed items in array. Each library is tested against both models using the same template.

All of them failed to render a fully dynamic model. RazorEngine and RazorMachine successfully rendered the template against the second model, while RazorTemplates didn't. In both cases RazorTemplates had problems with nested dynamic objects:


Test method RazorEngineExample.EngineTests.RazorTemplatesPartiallyDynamicModelTest threw exception:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'object' does not contain a definition for 'SecondLevel'.


Unfortunately when I added RazorEngine 3.0.8beta to our main project it crashed. The problem looked similar to this one. I wasn't able to make things work for partially dynamic models. It worked fine for fully typed models though.

RazorMachine 2.4.1 worked fine either in test or main solutions. So I decided to go with it.

EPiServer Commerce. Loading MetaObject in meta controls explicitly.

The problem. Suppose you want to design a control for managing data in your custom meta field. You inherit CoreBaseUserControl (or BaseUserControl) class and implement IMetaControl interface. The interface contains two fields: MetaField and MetaObject. These fields are filled when you open an instance of certain meta class for edit. You use MetaField property to manage the meta field your control designed for. MetaObject is used to interact with other fields of the same item.
Sometimes you may have a dependency between fields e.g. the second control is rendered in different ways according to the value in the first field. But sometimes we get a MetaObject empty during control loading. It means that we can't access other fields without loading an object explicitly.

The solution. To address this problem we can enhance a MetaField getter in a folliwing way:

public MetaObject MetaObject
{
 get
 {
  if (this.m_metaObject == null)
  {
   const int metaObjectDefaultId = 0;
   int metaObjectId = ManagementHelper.GetIntFromQueryString("catalogentryid", metaObjectDefaultId);
   if (metaObjectId != metaObjectDefaultId && MetaField.OwnerMetaClass != null)
   {
    try
    {
     this.m_metaObject = MetaObject.Load(this.MDContext, metaObjectId, MetaField.OwnerMetaClass.Id);
    }
    catch (Exception exception)
    {
     s_logger.Error("Error retrieving MetaObject", exception);
    }
   }
  }

  return this.m_metaObject;
 }
 set
 {
  this.m_metaObject = value;
 }
}

Monday, December 17, 2012

Outlook 2007 addin. Distribution list based on AD look up.

Hello there! Today I'm going to describe my recent small project. The goal was simple: provide colleagues with a flexible way of creation of email distribution lists. Something like outlook groups but with possibility of excluding particular users from the groups. Useful thing if you want to notify the group of people about someone's birthday. Of course you don't want the guy whose name-day is celebrated be in the list.

So I scanned our company Active Directory with ADExporer and found all the details I need to build a small console application. I decided to use builder pattern and keep distribution list building logic in a separate assembly. Here is the class diagram:



And here is the corresponding code:

namespace DLExt.Builder
{
    public class AddressBuilder
    {
        private readonly IList<DirectoryEntry> containersToSearch;
        private readonly IList<Location> locationsToSearch;
        private readonly List<Person> extractedPersons;
        private readonly IList<Person> personsToExclude;
        private readonly List<Person> filteredPersons;

        public string Server { get; private set; }

        public string ResultAddress { get; private set; }

        public AddressBuilder(string server, IEnumerable<Location> locations, IEnumerable<Person> personsToExclude)
        {
            Server = server;
            locationsToSearch = locations.ToList();
            this.personsToExclude = personsToExclude.ToList();

            containersToSearch = new List<DirectoryEntry>();
            extractedPersons = new List<Person>();
            filteredPersons = new List<Person>();
            ResultAddress = string.Empty;
        }

        public void Build()
        {
            try
            {
                LocateContainersToSearch(locationsToSearch);
                GetPersons();
                ExcludePersons();
                BuildDistributionList();
            }
            finally
            {
                FinalizeBuilder();
            }
        }

        protected virtual void LocateContainersToSearch(IList<Location> locations)
        {
            foreach (Location location in locations)
            {
                containersToSearch.Add(new DirectoryEntry(string.Format("LDAP://{0}/{1},{2}", Server, "OU=Users", location.Path)));
            }
        }

        protected virtual void GetPersons()
        {
            foreach (DirectoryEntry container in containersToSearch)
            {
                using (var directorySearcher = new DirectorySearcher(container, "(objectCategory=Person)"))
                {
                    SearchResultCollection results = directorySearcher.FindAll();
                    if (results.Count == 0)
                    {
                        return;
                    }

                    extractedPersons.AddRange((from SearchResult result in results
                                select new Person(
                                    result.Properties["displayName"][0].ToString(), 
                                    result.Properties["mail"][0].ToString())).ToList());
                }
            }
        }

        protected virtual void ExcludePersons()
        {
            filteredPersons.AddRange(extractedPersons.Except(personsToExclude, new PersonEqualityComparer()));
        }

        protected virtual void BuildDistributionList()
        {
            var builder = new StringBuilder();
            foreach (Person person in filteredPersons)
            {
                builder.Append(person.Email).Append(';');
            }

            ResultAddress = builder.ToString();
        }

        protected virtual void FinalizeBuilder()
        {
            foreach (DirectoryEntry container in containersToSearch)
            {
                container.Dispose();
            }
        }
    }
}

namespace DLExt.Builder.Retrievers
{
    public class PersonsRetriever : IRetriever<Person>
    {
        public string Server { get; private set; }

        public PersonsRetriever(string server)
        {
            Server = server;
        }

        public IList<Person> Retrieve(string path)
        {
            var result = new List<Person>();
            try
            {
                using (DirectoryEntry entry = new DirectoryEntry(string.Format("LDAP://{0}/{1}", Server, path)))
                {
                    using (DirectorySearcher searcher = new DirectorySearcher(entry, "(objectCategory=Person)"))
                    {
                        SearchResultCollection locations = searcher.FindAll();
                        if (locations.Count == 0)
                        {
                            return result;
                        }

                        result.AddRange(from SearchResult location in locations
                                        orderby location.Properties["displayName"][0].ToString()
                                        select new Person(
                                            location.Properties["displayName"][0].ToString(),
                                            location.Properties["mail"][0].ToString()));
                    }
                }
            }
            catch
            {
            }

            return result;
        }
    }
}

namespace DLExt.Builder.Retrievers
{
    public class LocationsRetriever :IRetriever<Location>
    {
        public string Server { get; private set; }

        public LocationsRetriever(string server)
        {
            Server = server;
        }

        public virtual IList<Location> Retrieve(string path)
        {
            var result = new List<Location>();
            try
            {
                using (DirectoryEntry entry = new DirectoryEntry(string.Format("LDAP://{0}/{1}", Server, path)))
                {
                    using (DirectorySearcher searcher = new DirectorySearcher(entry, "(objectClass=organizationalUnit)") { SearchScope = SearchScope.OneLevel })
                    {
                        SearchResultCollection locations = searcher.FindAll();
                        if (locations.Count == 0)
                        {
                            return result;
                        }

                        result.AddRange(from SearchResult location in locations 
                                        select new Location(
                                            location.Properties["name"][0].ToString(), 
                                            location.Properties["distinguishedName"][0].ToString()));
                    }
                }
            }
            catch
            {
            }

            return result;
        }
    }
}

After the searching logic was implemented and the console application was finished I decided to move further and build the UI. One option was to create a small tray WPF application. But who wants to run an additional application for such a minor task? I guess nobody. So I decided to build an Outlook 2007 addin. After some searching I found two great articles on MSDN and Codeproject. I was very excited when discovered that I can use WPF for achieving my goal. Here is a code I got:

using System;
using System.Windows.Forms;
using Microsoft.Office.Core;

namespace DLExt.OutlookAddin
{
    public partial class ThisAddIn
    {
        static readonly Timer Timer = new Timer();
        
        private void ThisAddIn_Startup(object sender, EventArgs e)
        {
            // HACK: looks like it is the only way to deal with minimized outlook startup
            Timer.Interval = 100;
            Timer.Tick += (o, args) =>
                              {
                                  var activeExplorer = Application.ActiveExplorer();
                                  if (activeExplorer != null)
                                  {
                                      Timer.Stop();
                                      CreateToolbar();
                                  }
                              };
            Timer.Start();
        }

        private void ThisAddIn_Shutdown(object sender, EventArgs e)
        {
            RemoveToolbar();
        }

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }
        
        #endregion

        private const string MenuToolbarTag = "Distribution List Addin";
        private CommandBar toolBar;
        private CommandBarButton toolBarButton;

        private CommandBar FindBar()
        {
            var activeExplorer = Application.ActiveExplorer();
            return activeExplorer != null
                       ? (CommandBar) activeExplorer.CommandBars.FindControl(missing, missing, MenuToolbarTag, true)
                       : null;
        }

        private void RemoveToolbar()
        {
            var commandBar = FindBar();
            if (commandBar != null)
            {
                commandBar.Delete();
            }
        }

        private void CreateToolbar()
        {
            try
            {
                toolBar = FindBar() 
                    ?? Application.ActiveExplorer().CommandBars.Add(MenuToolbarTag, MsoBarPosition.msoBarTop, false, true);

                toolBarButton = (CommandBarButton)toolBar.Controls.Add(MsoControlType.msoControlButton, missing, missing, 1, true);
                toolBarButton.Style = MsoButtonStyle.msoButtonIconAndCaption;
                toolBarButton.Caption = "Generate Distribution List";
                toolBarButton.FaceId = 65;
                toolBarButton.Tag = MenuToolbarTag;
                toolBarButton.Click += (CommandBarButton ctrl, ref bool @default) =>
                {
                    MainWindow window = new MainWindow(
                        "controller",
                        "OU=Sites,OU=Company,DC=domain,DC=corp",
                        "OU=Sites,OU=Company,DC=domain,DC=corp");
                    window.Show();
                };
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message, "Error Message");
            }
        } 
    }
}

using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using DLExt.Builder;
using DLExt.Builder.Model;
using DLExt.Builder.Retrievers;
using Microsoft.Office.Interop.Outlook;

namespace DLExt.OutlookAddin
{
    public partial class MainWindow : INotifyPropertyChanged
    {
        private readonly BackgroundWorker loadingWorker;
        private readonly BackgroundWorker composeWorker;
        private AddressBuilder builder;
        private IList<Location> locationsList;
        private IList<Person> personsList;

        public event PropertyChangedEventHandler PropertyChanged;

        public string Server { get; private set; }

        public string LocationsRootPath { get; private set; }

        public string PersonsRootPath { get; private set; }

        private bool isProcessing;

        public bool IsProcessing
        {
            get { return isProcessing; }
            set
            {
                isProcessing = value;
                PropertyChanged(this, new PropertyChangedEventArgs("IsProcessing"));
            }
        }

        public MainWindow(string server, string locationsRootPath, string personsRootPath)
        {
            Server = server;
            LocationsRootPath = locationsRootPath;
            PersonsRootPath = personsRootPath;

            loadingWorker = new BackgroundWorker();
            loadingWorker.DoWork += (o, args) =>
            {
                IsProcessing = true;
                locationsList = new LocationsRetriever(Server).Retrieve(LocationsRootPath);
                personsList = new PersonsRetriever(Server).Retrieve(PersonsRootPath);
            };

            loadingWorker.RunWorkerCompleted += (sender, args) =>
            {
                locations.ItemsSource = locationsList;
                persons.ItemsSource = personsList;
                IsProcessing = false;
            };

            composeWorker = new BackgroundWorker();
            composeWorker.DoWork += (sender, args) =>
            {
                IsProcessing = true;
                builder = new AddressBuilder(
                    Server,
                    locations.Items.OfType<Location>().Where(loc => loc.IsSelected),
                    personsToExclude.Items.OfType<Person>());
                builder.Build();
            };

            composeWorker.RunWorkerCompleted += (sender, args) =>
            {
                IsProcessing = false;
                try
                {
                    var app = new Microsoft.Office.Interop.Outlook.Application();
                    var mailItem = (MailItem)app.CreateItem(OlItemType.olMailItem);

                    mailItem.To = builder.ResultAddress;
                    mailItem.Display(true);

                }
                catch (COMException)
                {
                }
            };

            loadingWorker.WorkerSupportsCancellation = true;
            DataContext = this;
            InitializeComponent();
        }

        private void WindowLoaded(object sender, RoutedEventArgs e)
        {
            loadingWorker.RunWorkerAsync();
        }

        private void ComposeEmail(object sender, RoutedEventArgs e)
        {
            composeWorker.RunWorkerAsync();
        }

        private void ExcludePerson(object sender, RoutedEventArgs e)
        {
            if (!personsToExclude.Items.Contains(persons.SelectedItem))
            {
                personsToExclude.Items.Add(persons.SelectedItem);
            }
        }

        private void CloseForm(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void WindowClosing(object sender, CancelEventArgs e)
        {
            if (loadingWorker.IsBusy)
            {
                loadingWorker.CancelAsync();
            }
        }
    }
}

The UI was completed in a first approximation. The main thing I wasn't satisfied with was execution of LDAP requests in the same thread as UI. So the next step was implementing multithreading with background worker.

public MainWindow(string server, string locationsRootPath, string personsRootPath)
{

 ...            

 loadingWorker = new BackgroundWorker();
 loadingWorker.DoWork += (o, args) =>
 {
  IsProcessing = true;
  locationsList = new LocationsRetriever(Server).Retrieve(LocationsRootPath);
  personsList = new PersonsRetriever(Server).Retrieve(PersonsRootPath);
 };

 loadingWorker.RunWorkerCompleted += (sender, args) =>
 {
  locations.ItemsSource = locationsList;
  persons.ItemsSource = personsList;
  IsProcessing = false;
 };


 loadingWorker.WorkerSupportsCancellation = true;

 ...

 InitializeComponent();
}

 ...

private void WindowLoaded(object sender, RoutedEventArgs e)
{
 loadingWorker.RunWorkerAsync();
}

The application was completed but unfortunately it was crushing during Outlook start up. After some research I found the problem was the absence of ActiveExplorer instance when outlook started minimized. No UI - no ActiceExplorer instance. Sounds fair but how to fix it? I had to recognize that the hack with a timer was the best solution for me.

private void ThisAddIn_Startup(object sender, EventArgs e)
{
 // HACK: looks like it is the only way to deal with minimized outlook startup
 Timer.Interval = 100;
 Timer.Tick += (o, args) =>
  {
    var activeExplorer = Application.ActiveExplorer();
    if (activeExplorer != null)
    {
     Timer.Stop();
     CreateToolbar();
    }
  };
 Timer.Start();
}

The app was completed this time. But I understood that I need to provide a way for easy distribution of the addin. So I decided to build a setup project to handle all prerequisites and registry stuff. The hardest part here was to find this great article for excel plugins distribution. I followed it to create a setup project for my outlook plugin.

Final sources can be found here.

Friday, November 9, 2012

Serialization of entities (Entity Framework)

I want to tell you about interesting issue I faced when tried to serialize an entity in Microsoft Entity Framework.

I'm going to use Northwind database for demonstration purposes. Suppose you've added the Northwind model to your solution. And you are interested in three entity classes: Customer, Order and Order_Detail. Here is the corresponding diagram:
 You want to serialize particular customer with all his orders and order details. Consider the following code:

using System.Data.Objects;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using Serializing;

namespace EntitiesSerialization
{
    class Program
    {
        static void Main()
        {
            using (var context = new SerializingContext())
            {
                context.ContextOptions.LazyLoadingEnabled = false;
                var query = context.Customers.Include("Orders.Order_Details");
                var customer = query.Where("it.CustomerID = @Id", new ObjectParameter("Id", "ALFKI")).First();                
                var serializer = new XmlSerializer(typeof (Customers), new[] {typeof(Orders), typeof(Order_Details)});
                serializer.Serialize(new XmlTextWriter("test.xml", Encoding.UTF8), customer);                
            }
        }
    }
}

As you can see, I use XmlSerializer to serialize the customer to xml file. Just to make sure that LazyLoading doesn't affect the solution I've turned it off and included all entities explicitly. I pass Order and Order_Details types to XmlSerializer constructor as extraTypes. Unfortunatelly this solution won't work as it should. Result xml file contains only customer fields, no orders and order details are persisted:

<?xml version="1.0" encoding="utf-8"?>
<Customers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <EntityKey>
    <EntitySetName>Customers</EntitySetName>
    <EntityContainerName>SerializingContext</EntityContainerName>
    <EntityKeyValues>
      <EntityKeyMember>
        <Key>CustomerID</Key>
        <Value xsi:type="xsd:string">ALFKI</Value>
      </EntityKeyMember>
    </EntityKeyValues>
  </EntityKey>
  <CustomerID>ALFKI</CustomerID>
  <CompanyName>Alfreds Futterkiste</CompanyName>
  <ContactName>Maria Anders</ContactName>
  <ContactTitle>Sales Representative</ContactTitle>
  <Address>Obere Str. 57</Address>
  <City>Berlin</City>
  <PostalCode>12209</PostalCode>
  <Country>Germany</Country>
  <Phone>030-0074321</Phone>
  <Fax>030-0076545</Fax>
</Customers>

One way to make this work is described here. You can use DataContractSerializer to persist all the data to xml. Like this:

DataContractSerializer serializer = new DataContractSerializer(customer.GetType());
serializer.WriteObject(new XmlTextWriter("test.xml", Encoding.UTF8), customer);

Another option is to use binary formatter. In the following code I perform sequental serialization/deserialization of the customer object:

var binaryFormatter = new BinaryFormatter();
var stream = new MemoryStream();
binaryFormatter.Serialize(stream, customer);
stream.Position = 0;
var result = binaryFormatter.Deserialize(stream);

Thursday, November 1, 2012

Setting the value of new properties for old pages.

PageTypeBuilder is a great tool and I can't imagine developing against EPiServer CMS without it. But it has a well known issue: after a new property is added to page type old pages of this type have this property empty. It becomes worth if new property has required attribute set to true. You can't update old pages without filling this property.

In this post I want to share one possible solution. The idea is simple. Get all pages of certain type, find all required attributes with specified default value. Then iterate through these pages and set the required properties with their default values.

The first thing we need to do is to find all pages of certain type. It can be done using FindPagesWithCriteria method. To create search criteria we will need an ID of page type. So we have to use one of three overloads of PageType.Load method. Of course we can hardcode the guid or name, but more convenient way is to lookup this data. Here is what we've got for now:

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAccess;
using EPiServer.Filters;
using PageTypeBuilder;

public class DefaultValuesSetter<TPageData> where TPageData : PageData
{        
 private const string PageTypeCriteriaName = "PageTypeID";

 private readonly PageType pageType;
 private readonly IList<PageDefinition> targetPageDefinitions;

 public DefaultValuesSetter()
 {
  pageType = GetPageType();
  targetPageDefinitions = GetRequiredDefinitionsWithDefaultValue();
 }

 public virtual IList<TPageData> GetPages()
 {
  if (pageType == null)
  {
   return new List<TPageData>();
  }

  var criteria = new PropertyCriteria
         {
          Condition = CompareCondition.Equal,
          Name = PageTypeCriteriaName,
          Type = PropertyDataType.PageType,
          Value = pageType.ID.ToString(CultureInfo.InvariantCulture),
          Required = true
         };  

  return DataFactory.Instance
   .FindPagesWithCriteria(PageReference.RootPage, new PropertyCriteriaCollection {criteria})
   .OfType<TPageData>()
   .ToList();
 } 

 private static PageType GetPageType()
 {
  var pageTypeId = PageTypeResolver.Instance.GetPageTypeID(typeof (TPageData));
  return pageTypeId.HasValue 
       ? PageType.Load(pageTypeId.Value) 
       : null;
 } 
}

The next thing to deal with is searching for property attributes. As I mentioned before we are interested in required properties with not empty default value. Here is the code:

private IList<PageDefinition> GetRequiredDefinitionsWithDefaultValue()
 {
  return pageType.Definitions
   .Where(def => def.Required && 
    def.DefaultValueType == DefaultValueType.Value && 
    def.DefaultValue != null)
   .ToList();
 }

Finally we need to iterate through all found pages and set their required properties to default values:

public virtual void SetDefaultValues(IList<TPageData> pages)
 {
  foreach (TPageData page in pages)
  {
   var pageData = page.CreateWritableClone();
   foreach (PageDefinition definition in targetPageDefinitions)
   {
    if (pageData[definition.Name] == null)
    {
     pageData[definition.Name] = definition.DefaultValue;
    }
   }

   try
   {
    DataFactory.Instance.Save(pageData, SaveAction.Publish);
   }
   catch (Exception exception)
   {
    // TODO: add logging here    
   }
  }
 }

We're done with infrastructure. Now suppose you've created a new page type class:

[PageType(Name = "TestPageType")]
public class TestPageType : TypedPageData
{
}

You've created a page of this type in CMS and decided to add a property to it:

[PageType(Name = "TestPageType")]
public class TestPageType : TypedPageData
{
 [PageTypeProperty(            
  EditCaption = "Test Property",            
  Type = typeof(PropertyString),
  Required = true,            
  DefaultValue = "My default value",
  DefaultValueType = DefaultValueType.Value)]
 public virtual string TestProperty { get; set; }        
}

To update the page you can use the following code:

var setter = new DefaultValuesSetter<TestPageType>();
var pages = setter.GetPages();
setter.SetDefaultValues(pages);

Happy coding!