You are currently browsing the tag archive for the ‘C#’ tag.

When importing data in Dynamics CRM 4.0 there is no client side code triggered, so to make sure you get phonenumbers (or anything else for that matter) in a clean format, you might want to consider creating a plugin that runs server side and generates an import failure to let the customer know some data is not valid in his imports.

This way the user knows when something went wrong and the correct data will still be imported.

To make this plugin work there is the issue of the country prefixes and areacodes that exist. Therefore it is necessary to create two new entities: Country and AreaCode.

The Country entity needs to contains the telephone prefix of that country (eg: belgium = 32, Netherlands = 33 …)

The AreaCode is contains a link to the country entity and contains the phone areacode. (eg: 9 for Ghent, Belgium …)

Ones this data is in CRM we can use it in our plugin through the webservice.

In the end the phonenumbers will be formatted in an outlook compatible format.

eg: +32 (9) 265.74.20

When using the installer inside the project on CodePlex the plugin will be registered for the create and update messages for lead, account and contact.

 

You can find the plugin on CodePlex in our Dynamics CRM 4.0 Plugins project

Advertisements

Why do I need it?

We’ve noticed the CRM Duplicate detection doesn’t allow for much creativity, you can only create Duplicate Detection Rules with the following condition:

  • full string match
  • begins with (number of characters)
  • ends with (number of characters)

The problem with this is that people or companies aren’t always entered in the same format. For instance if you look at our company name we would create the account with the name "Orbit One". Other people might write "Orbit One BVBA" (the bvba part is a type of business entity)
There are some other examples that come to mind:
"Dell NV" vs "Dell"
"HP Belgium" vs "HP"
"Gaëtan" vs "Gaetan"
"François" vs "Francois"
These exceptions will never be caught by the default CRM Duplicate detection.

How can I fix this?

We added the following attributes and filled the with data:

  • on account => new_matchname (contains the accounts name)
  • on contact => new_matchname (contains the contacts first + lastname)
  • on lead => new_matchcontact (contains the leads first+ lastname)
  • on lead => new_matchcompany (contains the lead company name)

The fields are filled with a normalized name. The process is:

  • to lower case and trim.
  • replace special characters (except parenthesis, a-z, 0-9, special characters like éèàç …, and spaces) with empty string.
  • remove everything between the parentheses eg: "HP (was Compaq)" becomes "HP"
  • remove suffixes and prefixes that are type of business entity
  • remove all spaces
  • replace special characters (eg: é > e, à => a, ç => c, ñ => n, …)

Next there need to be some new duplicate detection rules.

  • full string match on new_matchname for contact
  • full string match on new_matchname for account
  • full string match on new_matchcontact for lead (add companyname if needed in your specific cases)

The current implemented type of business entity which will be stripped are:

  • nv
  • sa
  • vzw
  • bvba
  • cvba
  • sarl
  • sprl
  • scrl
  • nvsa
  • bv
  • llc
  • gmbh
  • ltd
  • ag
  • kt
  • og
  • vof
  • cvoa
  • commva
  • commv
  • ebvba
  • ez
  • belgium

Duplicate Detection Jobs

In CRM you can add duplicate detection jobs by going to Settings > Data Management > Duplicate detection jobs

image

Create a new Duplicate detection job for for instance accounts. Make sure you filter on only active records. When you merge a duplicate record the record that isn’t the master record will be placed on inactive. So if you forget this filter you’ll keep getting the same duplicates 😉

Once the job ran, you can see the duplicates entries.

image

What we think is another major pain in the a$$ is that you can only merge 2 records at a time. So not really in bulk… Maybe we will come up with a solution for this later 🙂

 

You can find the Matchname plugin on CodePlex. There is an installer include so that you don’t need to worry about the plugin registration, creating the steps, …. More on this on CodePlex.

I added a project to CodePlex last week:

MailChimp Sync Module makes it easier for developers to synchronize their lists from any datasource to mailchimp.
It’s developed in C# on the mailchimp .NET wrapper.

Thank you, MailChimp, for the link on your blog.

One of our customers recently asked to do an update on their instance of the microsoft CRM 4.0 platform.

The goal was to change the current Country Picklist to a lookup field which points to a new_country entity.

The new_country entity contains a few translations of the countries in different languages like english, dutch and french, and the ISO codes for each country.

Try to update that in CRM 4.0. So we start looking. The first thing I came across was the MSCRM 4.0 Bulk Data Export Tool

This tool allows you to export data generated by an advanced find view to a CSV file which you can later on import in CRM 4.0 trhough an update job. So I started playing around with it.

The major issue for me is that with that tool I couldn’t get the lookup field to be populated. I tried a bunch of different things like “account,E52B43A8-5FC6-4934-A5A3-96B2983EE140” or just the “E52B43A8-5FC6-4934-A5A3-96B2983EE140” but nothing seemed to work.

So I decided to create a new tool that basically does what i want, without creating an import job. Everything is done through the CRM SDK.

A few things I thought are very cool:

  • You can save your connection. We are a hosting company and we have 6 different CRM instances on 3 different servers, so this is quite nice.
  • One of our clients has 15.000+ records in their contacts. Because CRM returns only 5.000 records for each request, I can’t update them all in one go. This was changed by making multiple requests if necessary.
  • Access all the records and all the fields. In the MSCRM 4.0 Bulk Data Export Tool you can only use the tool on Advanced find views. I allow you to use the tool on the main entity. Which is really helpfull in some cases.
  • It allows you to choose the Authentication type. You can connect to CRM with Active Directory, SPLA, Passport (this last one I haven’t been able to test so if anybody has a CRM with passport please let me know)

It is still a work in progress but it does the job quite nice already so try it out.

The CRM Bulk Update / Export Tool

I hope it helps some of you in your daily (or not so daily) tasks. I know it saved a colleague of mine about 80% of his time for only one case, and I consider that a big improvement. 🙂

The beginning

Ok, in the beginning there was nothing. So, let’s start by taking a look at Introduction to Plug-in Development for Microsoft Dynamics CRM 4.0.

From here we can copy paste some sample code into a new class library.

using System;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace MyPlugins
{
   public class HelloWorldPlugin: IPlugin
   {
      public void Execute(IPluginExecutionContext context)
      {
         // Call the Microsoft Dynamics CRM Web services here or perform
         // some other useful work.
         throw new InvalidPluginExecutionException("Hello world!");
      }
   }
}

 As you notice when you paste the above code, you’ll get missing an assembly reference errors.

If you haven’t already installed the Microsoft Dynamics CRM 4.0 Sdk , please do so now.

Next add the references and your almost good to go. We can build the code (that doesn’t do anything for now, but what the hay)

How do we get the assembly in CRM ???

There are a few steps you’ll need to preform:

  • Make sure the user with which you want to register the plugin in CRM has the proper roles: either the System Administrator role or the System Customizer role are needed.
  • Make sure the user is a Deployment Administrator in the Deployment Manager.
  • Download the Plugin Registration tool.
  • Run the tool on the server of on a location from where you can connect through AD authentication type (active directory) because otherwise it wont work.

Now you should see the Plugin registration tool.

Enter your connection information in the fields below. In Discovery server you can just type your servername. You’ll be prompted for a password when you hit the Connect button. Once you entered your password, you should see one or more CRM instances in the tree. Hit the organisation you want to register the plugin in and click on the connect button.

The tool is now gathering some information about already registered plugins, messages, …

Click on the register button and select the Register new assembly.

Now press the ellips button (…) to browse to your assembly and choose the location you want the assembly to be stored.

I recommend you use the Database as your assembly storage. The reason for this recommendation is quite simple: if you choose another method you’ll manually have to deploy your assembly after adding or updating.

Next select the register selected plugin’s button. You should now see our newly registered assembly in the list. If you click on the + sign in the registered Plugins & … tree you’ll see the HelloWorld plugin. Right click that and select register new step.

We are now createing a ‘trigger’, so that our plugin code gets hit when something happens. Let’s choose after the update of a contact to start. So make the screen look like this:

Plugin Registration Step

When you now try to edit a contact and save it in crm you’ll see an exception that says something like Hello World 🙂

Catch y’all on the flipside

A little trick i’ve used for quite some time…
 
If you want to test a windows service for instance, you can’t start it in the debugger. But I want to use the debugger.
 
Then you could write something like the code below in the Program.cs.
Insert an internal Start method in the service code that just calls the OnStart() method of your service.
 
Now for the magic… i can now easily switch the code blocks (first 2 lines and last 5 lines) by removing one of the first 2 asterix (*) or reinserting one.
 

        static void Main()

        {

/**/

            var service = new AgentService();

            service.Start();

/*/

            ServiceBase[] ServicesToRun;

            ServicesToRun = new ServiceBase[]

            {

                new AgentService()

            };

            ServiceBase.Run(ServicesToRun);

/**/

        }

 

Would become

 

        static void Main()

        {

/*/

            var service = new AgentService();

            service.Start();

/*/

            ServiceBase[] ServicesToRun;

            ServicesToRun = new ServiceBase[]

            {

                new AgentService()

            };

            ServiceBase.Run(ServicesToRun);

/**/

        }

 

 

 

I’ve been trying and failing for quite some time at creating my configuration based on values enterend through the installer interfaces… And today I finally succeeded.

Let me walk you through the hardships and how to get it to work.

Create a console application that displays a name from the configuration file. (pretty easy so far)

class Program

{

    static void Main(string[] args)

    {

        var config = MyConsoleApplicationSection.Load();

 

        Console.Out.WriteLine(“Name = {0}”, config.Name);

 

        Console.ReadLine();

    }

}

 

class MyConsoleApplicationSection : ConfigurationSection

{

    [ConfigurationProperty(“Name”, IsRequired = true)]

    public string Name

    {

        get { return this[“Name”].ToString(); }

        set { this[“Name”] = value; }

    }

 

    public MyConsoleApplicationSection(string name)

    {

        this.Name = name;

    }

 

    public MyConsoleApplicationSection() { }

 

    public static MyConsoleApplicationSection Load()

    {

        return (MyConsoleApplicationSection)ConfigurationManager.GetSection(“MyConsoleApplicationSection”);

    }

}

And now for the harder part, I want to create an installer and in this installer I want to set the name in a textbox.

Let us start by creating a new project and select a setup and deployment project | setup project.

I’ll name mine NameSetup and click on OK.  Next we add a Project output to the application folder, and we choose our program’s active configuration. Now do a right click on the Setup Project and select View | User Interface

View | User Interface

View | User Interface

Right click Install | Start in the treeview and Add Dialog | TextBoxes (A). You’ll see that we have to move textboxes (A) up 2 positions in order to be able to build the project so lets do that.

Now we change some of the properties on the TextBoxes (A) dialog.

Properties

Properties

Ok. The click work is almost finished…

Now click on the setup project again in your solution explorer, and select the View | Custom Actions

Rigth click on the Custom Actions Node (main) | Add Custom Action …

Select the application folder and choose the output.

On properties of the custom action that is added under the install folder we need to change the CustomActionData propertie value to ” /name=[EDITA1] ” (without quotes)

The EDITA1 value refers to the first textbox on the dialog we’ve just added. You can add other values in the same manner (like this /name=[EDITA1] /type=[EDITA2] /something=[EDITA3] … ‘catch my drift’)

Now our setup project is ready. We only have to do one more thing. Add a new component to the project you want to setup, the installer class. Add following code to the installer class:

  public override void Install(IDictionary stateSaver)

  {

      base.Install(stateSaver);

 

      var assemblyPath = Context.Parameters[“assemblyPath”];

      var name = Context.Parameters[“name”];

      var configuration = new MyConsoleApplicationSection(name);

      var conf = ConfigurationManager.OpenExeConfiguration(assemblyPath);

 

      conf.Sections.Add(“MyConsoleApplicationSection”, configuration);

      conf.Save(ConfigurationSaveMode.Modified);

  }

One more thing. Because there seems to be a bug or something in the OpenExeConfiguration, we can not read the MyConsoleApplicationSection in the installer class. I haven’t found a solution to this for now, but there is a workaround. Just remove the configuration section from your app.Config entirely (configsection part and MyConsoleApplicationSection). Now when we call the save method (like on the last line above) the file is saved correctly.

 

So thats about it for this post. If you have any remarks or improvements, or this has been a great help please let me know.

See y’all next time…

Welcome back,

Today I would like to look into creating a Distribution List through the Exchange Webservice. This is not as straight forward as creating a contact, but it should work none the less. According to this book and the documentation found on the internet, it not possible to accomplish this. But there is a way around. Lets take the scenic route to create a Distribution List J

Take a look at the code in the previous post. You can see that – in the method to create the contact – a CreateItemType is instantiated. If you look at the Items property of that variable you can see that the it expects an NonEmptyArrayOfAllItemsType which again has an Items Property. In that property we can store any ItemType we want except a Distribution List, as you can see here. But if we can not insert the DistributionList in a CreateItem, then how the f* are we going to get it to the exchange server. Well we are going to create an ItemType (base class) and use that to set all the properties needed so that exchange and outlook recognizes the Item as a distribution list. Because an ItemType is the base class, it is not provided with any of the properties we need to create a distribution list. That is where the Extended Properties come in handy. Everything needs to be set through those properties.

Here are some links with more information on the properties:

And you can also still use OutlookSpy which was linked in the previous post.
Now for some code.
The first thing I’d like to do is declare the path to extended field variables and set them to point to the properties we’re going to use. I’ll name them all starting with ptef so we can get the quickly through intellisense.

private static PathToExtendedFieldType ptefDisplayName =

new PathToExtendedFieldType

{

PropertyTag = “0x3001”,

PropertyType = MapiPropertyTypeType.String

};

private static PathToExtendedFieldType ptefDistributionListName =

new PathToExtendedFieldType

{

PropertyId = 0x8053,

PropertyIdSpecified = true,

DistinguishedPropertySetId = DistinguishedPropertySetType.Address,

DistinguishedPropertySetIdSpecified = true,

PropertyType = MapiPropertyTypeType.String

};

private static PathToExtendedFieldType ptefFileUnder =

new PathToExtendedFieldType

{

PropertyId = 0x8005,

PropertyIdSpecified = true,

DistinguishedPropertySetId = DistinguishedPropertySetType.Address,

DistinguishedPropertySetIdSpecified = true,

PropertyType = MapiPropertyTypeType.String

};

private static PathToExtendedFieldType ptefMembers =

new PathToExtendedFieldType

{

PropertyId = 0x8055,

PropertyIdSpecified = true,

DistinguishedPropertySetId = DistinguishedPropertySetType.Address,

DistinguishedPropertySetIdSpecified = true,

PropertyType = MapiPropertyTypeType.BinaryArray

};

private static PathToExtendedFieldType ptefOneOffMembers =

new PathToExtendedFieldType

{

PropertyId = 0x8054,

PropertyIdSpecified = true,

DistinguishedPropertySetId = DistinguishedPropertySetType.Address,

DistinguishedPropertySetIdSpecified = true,

PropertyType = MapiPropertyTypeType.BinaryArray

};

Let’s take a look at these PathToExtendedFieldTypes:
  • ptefDisplayName: The name of the Distribution List
  • ptefDistributionListName: The name of the Distribution List
  • ptefFileUnder: Must be the same as the name of the Distribution List
  • ptefMembers:
    • The members extended property: This i a list of EntryIds of the objects corresponding to the members of the personal distribution list. Members of the personal distribution list can be other distribution lists, electronic addresses contained in a contact, global address list users or distribution lists, or one-off e-mail addresses. The format of each EntryId MUST be either a one-off EntryId or a Wrapped Entry Id. When setting this property, be sure to ensure its total size is less than 15,000 bytes. Since this property is a BinaryArray type it expects a string Array of Base64 strings. More info can be found in the Protocol Specifications on the MSDN site.
  • ptefOneOffMembers:
    • The property specifies the list of one-off EntryIds corresponding to the members of the personal distribution list. These one-off EntryIds encapsulate display names and e-mail addresses of the personal distribution list members. For each entry in this property, there should be an entry in the Members property. The size of this property must also be less than 15,000 bytes. Since this property is a BinaryArray type it expects a string Array of Base64 strings.
How the member entry id and the one off member entry id strings are built can also be found in the protocol specifications.
I’ll show you how I created the Member Entry Id:
First off you’ll need the ItemType of the contact we created in the previous post. To do that we can use the FindItemMethod or simply return the ItemType in the previous posts CreateContact method. So let us refactor that method a little bit:

private static ContactItemType CreateContact(string pGivenName, string pSurname, Gender pGender, string pTitle)

{

ContactItemType retval = null;

ContactItemType contact = new ContactItemType();

contact.GivenName = pGivenName;

contact.Surname = pSurname;

contact.DisplayName = contact.Subject = pGivenName + ” “ + pSurname;

contact.EmailAddresses = new EmailAddressDictionaryEntryType[1];

EmailAddressDictionaryEntryType email1 = new EmailAddressDictionaryEntryType();

email1.Key = EmailAddressKeyType.EmailAddress1;

email1.Value = “testUser@example.com”;

contact.EmailAddresses[0] = email1;

contact.ExtendedProperty = new ExtendedPropertyType[2];

ExtendedPropertyType gender = new ExtendedPropertyType();

gender.ExtendedFieldURI = new PathToExtendedFieldType();

gender.ExtendedFieldURI.PropertyTag = “0x3a4d”;

gender.ExtendedFieldURI.PropertyType = MapiPropertyTypeType.Short;

gender.Item = ((int)pGender).ToString();

ExtendedPropertyType title = new ExtendedPropertyType();

title.ExtendedFieldURI = new PathToExtendedFieldType();

title.ExtendedFieldURI.PropertyTag = “0x3a45”;

title.ExtendedFieldURI.PropertyType = MapiPropertyTypeType.String;

title.Item = pTitle;

contact.ExtendedProperty[0] = gender;

contact.ExtendedProperty[1] = title;

CreateItemType createItem = new CreateItemType();

createItem.Items = new NonEmptyArrayOfAllItemsType { Items = new[] { contact } };

DistinguishedFolderIdType folder = new DistinguishedFolderIdType();

folder.Id = DistinguishedFolderIdNameType.contacts;

TargetFolderIdType targetFolder = new TargetFolderIdType();

targetFolder.Item = folder;

createItem.SavedItemFolderId = targetFolder;

var response = esb.CreateItem(createItem);

//There is only one item in the createItem so the line below should work in this case

if (response.ResponseMessages.Items[0].ResponseClass == ResponseClassType.Success)

{

ItemInfoResponseMessageType rmt = (ItemInfoResponseMessageType)response.ResponseMessages.Items[0];

retval = (ContactItemType)rmt.Items.Items[0];

}

return retval;

}

To create a Distribution List with members you will need to find the EntryId when all we have is the ItemId and we want it in a Hex format. To achieve this I want to use the ConvertId Method on the ExchangeServiceBinding. The convertId Method accepts a ConvertIdType as a parameter. The ConvertIdType properties that are used here are the sourceIds and the destinationFormat. They kinda speak for themselves.
Lets take a look at some code again.

private static string CreateMemberEntryId(ItemType item)

{

byte[] retval = new byte[0];

string WrappedEntryIDPrefix = “00000000C091ADD3519DCF11A4A900AA0047FAA4C3”;

ConvertIdType convertReq = new ConvertIdType();

convertReq.DestinationFormat = IdFormatType.HexEntryId;

convertReq.SourceIds = new[]

{

new AlternateIdType()

{

Format = IdFormatType.EntryId,

Id = item.ItemId.Id

}

};

try

{

ConvertIdResponseType response = esb.ConvertId(convertReq);

ArrayOfResponseMessagesType aormt = response.ResponseMessages;

ResponseMessageType[] rmta = aormt.Items;

foreach (ConvertIdResponseMessageType resp in rmta)

{

if (resp.ResponseClass == ResponseClassType.Success)

{

ConvertIdResponseMessageType cirmt = resp;

AlternateIdType myId = (cirmt.AlternateId as AlternateIdType);

if (myId != null)

{

//Strip the last 48 bytes (96 chars) – this is actually the

folderId.

//Strip the first 7 bytes.

//then remove 1 byte right after the actual entryId (40 bytes)

//then the ChangeKey follows (in the rest of the string).

StringBuilder sb = new StringBuilder();

sb.Append(WrappedEntryIDPrefix);

sb.Append(“00000000”);

sb.Append(myId.Id.Substring(14, 84));

sb.Append(myId.Id.Substring(100, myId.Id.Length – 196));

retval = sb.ToString().ToByteArray();

}

}

else if (resp.ResponseClass == ResponseClassType.Error)

{

//TODO error Logging

}

else

{

//TODO warning logging

}

}

}

catch (Exception e)

{

//TODO error logging

}

return Convert.ToBase64String(retval);

}

You can see in previous code block that there is some comment:
Strip the last 48 bytes (96 chars) – this is actually the folderId.

Strip the first 7 bytes.

then remove 1 byte right after the actual entryId (40 bytes)

then the ChangeKey follows (in the rest of the string).

Don’t pay to much attention to this for the moment. If you want more information about this check out [MS-OXOCNTC]: Contact Object Protocol Specification.

The WrappedEntryIDPrefix is a fixed string except the last 2 characters. What it stands for you see in the protocol specification but in the MS-OXOMSG pdf.

They represent a bit settings which specifies what kind of email address the entry id is going to be. So if you change that you could be talking about another distribution list for instance.

We are going to use this MemberEntryId and Members extended property of a distribution list. As said before we’ll need the OneOffMembers entry’s also so we can put them in the same position as the MemberEntryId. The OneOffMembers entry is a bit less complicated.

private static string CreateOneOffMemberEntryId(ContactItemType contact)

{

var retval = new List<byte>();

var flags = Encoding.Unicode.GetBytes(“”);

var version = Encoding.Unicode.GetBytes(“”);

var pad = Encoding.Unicode.GetBytes(“”);

var muid = new byte[]

{

0x81, 0x2b, 0x1f, 0xa4, 0xbe,

0xa3, 0x10, 0x19, 0x9d, 0x6e,

0x00, 0xdd, 0x01, 0x0f, 0x54, 0x02

};

var wFlags = new byte[] {0x01, 0x90};

var first = Encoding.Unicode.GetBytes(contact.Subject + “(“ + contact.EmailAddresses[0].Value + “)”);

var middle = Encoding.Unicode.GetBytes(“UNKNOWN”);

var last = Encoding.Unicode.GetBytes(contact.EmailAddresses[0].Value);

retval.AddRange(flags);

retval.AddRange(muid);

retval.AddRange(version);

retval.AddRange(wFlags);

retval.AddRange(first);

retval.AddRange(pad);

retval.AddRange(middle);

retval.AddRange(pad);

retval.AddRange(last);

retval.AddRange(pad);

return Convert.ToBase64String(retval.ToArray());

}

Here we only need some actual strings from the contact to create the One Off Members entry. All the values except for wFlags, first, middle and last have fixed values. The wFlags should be changed to 0x8001 if you want to specify a distribution list. So it should read like this:

var wFlags = new byte[] {0x01, 0x80};

var first = Encoding.Unicode.GetBytes(distributionlist.Subject);

var middle = Encoding.Unicode.GetBytes(“MAPIPDL”);

var last = Encoding.Unicode.GetBytes(“Unknown”);

Now to actually create a distribution list with members we’ll need to create an Item that has all the correct Extended Properties:

private static void CreateDistributionList(string name, string description, ContactItemType contact)

{

ItemType list = new ItemType();

list.ItemClass = “IPM.DistList”;

list.Subject = name;

list.Body = new BodyType { BodyType1 = BodyTypeType.Text, Value = description };

List<string> members = new List<string>();

List<string> oneOffMembers = new List<string>();

members.Add(CreateMemberEntryId(contact));

members.Add(CreateOneOffMemberEntryId(contact));

List<ExtendedPropertyType> eProps = new List<ExtendedPropertyType>();

eProps.Add(new ExtendedPropertyType

{

ExtendedFieldURI = ptefDisplayName,

Item = name

});

eProps.Add(new ExtendedPropertyType

{

ExtendedFieldURI = ptefDistributionListName,

Item = name

});

eProps.Add(new ExtendedPropertyType

{

ExtendedFieldURI = ptefFileUnder,

Item = name

});

eProps.Add(new ExtendedPropertyType

{

ExtendedFieldURI = ptefMembers,

Item = new NonEmptyArrayOfPropertyValuesType

{

Items = members.ToArray()

}

});

eProps.Add(new ExtendedPropertyType

{

ExtendedFieldURI = ptefOneOffMembers,

Item = new NonEmptyArrayOfPropertyValuesType

{

Items = oneOffMembers.ToArray()

}

});

list.ExtendedProperty = eProps.ToArray();

}

I hope this was helpfull. Feel free to post comments and / or questions. I won’t bite

As you probably could see in the code this is not ready to ship…

There is alot more exception handling to be done and so forth, but in order to keep this document a bit more to the point I’ve skipped those parts.

During the past 2 weeks or so I’ve been experimenting with the Exchange Webservice (in Exchange 2007 SP1).

I’ve been trying to create a contact in the contacts folder of a user (testuser). I came accross some problems while trying to accomplish this. Specifically when I tried to set properties that are actually used by Outlook (client properties) and which are not necessarily used by Exchange 2007. But you can still access and even create properties all you want. Lets kick this blog off with the basics of working with the ews (Exchange WebService).

Some ground work:

  1. Create a new console app.
  2. Add the Web Reference to your Exchange Webservice (eg.: http://myMailServer/ews/Exchange.Asmx) and lets name it ExchangeService.
  3. Add the using to the ExchangeService

Now we can go and add a private member

private static ExchangeServiceBinding esb;

 

Initialize the ExchangeServiceBinding. You can insert credentials here in case of a https connection as seen in the example below.

esb = new ExchangeServiceBinding

{

    Credentials = new NetworkCredential

    {

        Domain = “example.com”,

        UserName = “testUser”,

        Password = “p455w0rd”

    },

    Url = https://192.168.2.143/EWS/Exchange.asmx&#8221;

};

 

esb.RequestServerVersionValue = new RequestServerVersion();

esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1;

 

As you can see in above code you need to set the server version, please do not ommit these 2 lines. If you do your code probably won’t work. In some cases you will get an error like this: 

System.Web.Services.Protocols.SoapException: The request is valid but does not specify the correct server version in the RequestServerVersion SOAP header. Ensure that the RequestServerVersion SOAP header is set with the correct RequestServerVersionValue.

Now that we have the base of the application we can start concentrating on creating a contact, so we’re going to create a method that does just that for us.

private static void CreateContact(string givenName, string surname)

{

    ContactItemType contact = new ContactItemType();

 

    contact.GivenName = givenName;

    contact.Surname = surname;

    contact.DisplayName = contact.Subject = givenName + ” “ + surname;

 

    contact.EmailAddresses = new EmailAddressDictionaryEntryType[1];

    EmailAddressDictionaryEntryType email1 = new EmailAddressDictionaryEntryType();

    email1.Key = EmailAddressKeyType.EmailAddress1;

    email1.Value = “testUser@example.com”;

    contact.EmailAddresses[0] = email1;

 

    CreateItemType createItem = new CreateItemType();

    createItem.Items = new NonEmptyArrayOfAllItemsType {Items = new[] {contact}};

 

    DistinguishedFolderIdType folder = new DistinguishedFolderIdType();

    folder.Id = DistinguishedFolderIdNameType.contacts;

 

    TargetFolderIdType targetFolder = new TargetFolderIdType();

    targetFolder.Item = folder;

 

    createItem.SavedItemFolderId = targetFolder;

 

    var response = esb.CreateItem(createItem);

}

 

You’ll see the contact being created in you testUser’s contacts folder.
If you don’t fill out the DisplayName, the contact will complain about it in outlook when you open the contact and then close it without editing anything. Outlook will try to save the contact item although we did not change anything. 

This was fairly easy to achieve, but just try to fill out all the contact’s porperties you need. You’ll soon find out that you need to use Extended Properties, because not all the properties are available on the ContactItemType type. I’ll show you how to add 2 Extended Properties. Firstly the Gender of the contact and secondly the Title. Insert following somewhere before the instantiation of the CreateItemType createItem.

 

    contact.ExtendedProperty = new ExtendedPropertyType[2];

 

    ExtendedPropertyType gender = new ExtendedPropertyType();

    gender.ExtendedFieldURI = new PathToExtendedFieldType();

    gender.ExtendedFieldURI.PropertyTag = “0x3a4d”;

    gender.ExtendedFieldURI.PropertyType = MapiPropertyTypeType.Short;

    gender.Item = “1”;

 

    ExtendedPropertyType title = new ExtendedPropertyType();

    title.ExtendedFieldURI = new PathToExtendedFieldType();

    title.ExtendedFieldURI.PropertyTag = “0x3a45”;

    title.ExtendedFieldURI.PropertyType = MapiPropertyTypeType.String;

    title.Item = “Dr.”;

 

    contact.ExtendedProperty[0] = gender;

    contact.ExtendedProperty[1] = title;

 

As you can see both these Extended Properties have a PropertyTag. This is not always the case. In other properties you’ll need to use the PropertyId and DistinguisedPropertyId, but we’ll see samples of that in a later post in which I am going to discuss how you need to create a DistributionList with the contact created here as the only member.

The Property Tags and Ids can be found in the pdf’s concerning the exchange protocol on msdn or through a nice tool that helped me alot these past weeks: OutlookSpy.

That’s it for now. I hope it was clear and helpfull in some way.
Until next time…