Skip to content

Writing a mapping for using an initialization pattern

ldfallas edited this page Aug 31, 2015 · 4 revisions

The problem

The mappings mechanism works by processing individual API usages. For example you can write a code mapping for a specific method call such as System.Windows.MessageBox.Show. However there are scenarios where several elements could be converted to one element. For example the PhoneCallTask class https://msdn.microsoft.com/library/windows/apps/microsoft.phone.tasks.phonecalltask(v=vs.105).aspx (and several other task classes) work by following a pattern like this:

PhoneCallTask phoneCallTask = new PhoneCallTask();

phoneCallTask.PhoneNumber = model.PhoneNumber;
phoneCallTask.DisplayName = model.FullName;

phoneCallTask.Show();

The equivalent UWP code for this group of instructions is the following:

     Windows.ApplicationModel.Calls.PhoneCallManager.ShowPhoneCallUI(model.PhoneNumber, model.FullName);

The GROUP_INIT_STATEMENTS pattern

The conversion tool provides a way to deal with these kind of scenarios where it's common to find a group of initialization statements and a call to an action method.

<MapUnit xmlns="clr-namespace:Mobilize.Mappers.Extensibility.Core;assembly=Mobilize.ExtensibleMappers"
         xmlns:map="clr-namespace:Mobilize.Mappers.Extensibility.Code;assembly=Mobilize.ExtensibleMappers">
  <MapUnit.Elements>
    <map:CodeMapPackage Type="Microsoft.Phone.Tasks.PhoneCallTask">
      <map:CodeMapPackage.Maps>
         ...
      </map:CodeMapPackage.Maps>
      <map:CodeMapPackage.Metadata>
        <map:PackageMetadata Key="GROUP_INIT_STATEMENTS" Value="true"/>
      </map:CodeMapPackage.Metadata>
    </map:CodeMapPackage>
  </MapUnit.Elements>
</MapUnit>

With this metadata element we instruct the migration tool to search for this initialization pattern and group statements in a way it is easier to manipulate using common mapping elements.

ere's an example of how this directive operates:

initgroup

Having this grouping we can write a mapping as follows:

   <MapUnit xmlns="clr-namespace:Mobilize.Mappers.Extensibility.Core;assembly=Mobilize.ExtensibleMappers"
         xmlns:map="clr-namespace:Mobilize.Mappers.Extensibility.Code;assembly=Mobilize.ExtensibleMappers">
  <MapUnit.Elements>
    <map:CodeMapPackage Type="Microsoft.Phone.Tasks.PhoneCallTask">
      <map:CodeMapPackage.Maps>
        <map:CodeMap Kind="Call" MemberName="Show">
          <map:Conditional>
            <map:Case>
              <map:Case.Condition>
                <map:WithCalledMemberOwner>
                  <map:WithConstructorCall>
                    <map:WithMemberInitValue MemberName="PhoneNumber">
                      <map:AssignName>$phoneNumber</map:AssignName>
                    </map:WithMemberInitValue>
                    <map:WithMemberInitValue MemberName="DisplayName">
                      <map:AssignName>$displayName</map:AssignName>
                    </map:WithMemberInitValue>
                  </map:WithConstructorCall>
                </map:WithCalledMemberOwner>
              </map:Case.Condition>
              <map:Case.Action>
                  <map:ReplaceWithTemplate>
                      Windows.ApplicationModel.Calls.PhoneCallManager.ShowPhoneCallUI($phoneNumber, $displayName)
                  </map:ReplaceWithTemplate>
              </map:Case.Action>
            </map:Case>
            <map:Default>
               <map:Keep/>
            </map:Default>
          </map:Conditional>
        </map:CodeMap>
      </map:CodeMapPackage.Maps>
      <map:CodeMapPackage.Metadata>
        <map:PackageMetadata Key="GROUP_INIT_STATEMENTS" Value="true"/>
      </map:CodeMapPackage.Metadata>
    </map:CodeMapPackage>
  </MapUnit.Elements>
</MapUnit>

As shown above, when the MapPackage of a class specifies GROUP_INIT_STATEMENTS metadata element, the conversion tool will try to group the initalization statements before applying mapping. Once the mappings are groupped we can apply a complex mapping such as:

constructorMatching

Fallback to a helper class

There maybe scenarios where we can't group initialization statements as shown above. For example:

 PhoneCallTask phoneCallTask;

 void MyMethod()
 {

     phoneCallTask = CreatePhoneCallTask();

     SetPhoneEntryNumber(model, phoneCallTask);
     if (model.FullName == "Some special name")
     {
         SetSpecialName(model, phoneCallTask);
     }

     DoSomeTask(phoneCallTask);
 }

 private static void DoSomeTask(PhoneCallTask phoneCallTask)
 {
     phoneCallTask.Show();
 }

 private static void SetSpecialName(MyPhoneEntryModel model, PhoneCallTask phoneCallTask)
 {
     phoneCallTask.DisplayName = model.FullName;
 }

 private static void SetPhoneEntryNumber(MyPhoneEntryModel model, PhoneCallTask phoneCallTask)
 {
     phoneCallTask.PhoneNumber = model.PhoneNumber;
 }

 private static PhoneCallTask CreatePhoneCallTask()
 {
     PhoneCallTask phoneCallTask = new PhoneCallTask();
     return phoneCallTask;
 }

For these scenarios it is better to use a helper class which provides the equivalent functionality on UWP. See the [Adding helper classes](Adding helper classes) for more information. The helper class we will use for this example looks like this:

using Windows.ApplicationModel.Calls;

namespace UpgradeHelpers {
    class PhoneTaskHelper
    {
        public string DisplayName { get; internal set; }
        public string PhoneNumber { get; internal set; }

        internal PhoneTaskHelper()
        {
            DisplayName = "";
        }

        internal void Show()
        {
            PhoneCallManager.ShowPhoneCallUI(PhoneNumber, DisplayName);
        }
    }
}

Having this helper class we can include it using the following mapping:

<map:CodeMap Kind="Type">
  <map:Conditional>
    <map:Case>
      <map:Case.Condition>
        <map:DoesNotContainAnnotation>GROUP_INIT_STATEMENTS</map:DoesNotContainAnnotation>
      </map:Case.Condition>
      <map:Case.Action>
        <map:ActionSequence>
          <map:AddHelper Path="..\Helpers\PhoneTaskHelper.cs" />
          <map:ReplaceClassUsage NewNamespace="UpgradeHelpers" NewClassName="PhoneTaskHelper" />
        </map:ActionSequence>
      </map:Case.Action>
    </map:Case>
    <map:Default><map:Keep/></map:Default>
  </map:Conditional>
</map:CodeMap>

The DoesNotContainAnnotation condition verifies that the current type is not part of an identified GROUP_INIT_STATEMENTS pattern . The converted code for the previous example is the following:

UpgradeHelpers.PhoneTaskHelper phoneCallTask;
void MyMethod()
{
   phoneCallTask = CreatePhoneCallTask();
   SetPhoneEntryNumber(model, phoneCallTask);
   if ( model.FullName == "Some special name" )
   {
      SetSpecialName(model, phoneCallTask);
   }
   DoSomeTask(phoneCallTask);
}

private static void DoSomeTask(UpgradeHelpers.PhoneTaskHelper phoneCallTask)
{
   phoneCallTask.Show();
}

private static void SetSpecialName(MyPhoneEntryModel model, UpgradeHelpers.PhoneTaskHelper phoneCallTask)
{
   phoneCallTask.DisplayName = model.FullName;
}

private static void SetPhoneEntryNumber(MyPhoneEntryModel model, UpgradeHelpers.PhoneTaskHelper phoneCallTask)
{
   phoneCallTask.PhoneNumber = model.PhoneNumber;
}

private static UpgradeHelpers.PhoneTaskHelper CreatePhoneCallTask()
{
   UpgradeHelpers.PhoneTaskHelper phoneCallTask = new UpgradeHelpers.PhoneTaskHelper();
   return phoneCallTask;
}

Overview

Writing mappings

Code Mapping Actions

Code Mapping Conditions

XAML mapping actions

XAML mapping conditions

Misc

Clone this wiki locally