Sample: Associate and Disassociate Records Using the REST Endpoint with Silverlight

[Applies to: Microsoft Dynamics CRM 2011]

This sample code is for Microsoft Dynamics CRM 2011, and can be found in the following location in the SDK download:

SDK/SampleCode/CS/Silverlight/CrmAssociateDisassociateSilverlight

If you just want to see how this sample works you can install (import) the AssociatedDisassociateSilverlightSample_1_0_0_0_managed.zip managed solution included in the download files. If you install this managed solution and want to create the web resources using the names below, your solution publisher customization prefix cannot be “sample” unless you uninstall (delete) the managed solution.

Requirements

For this sample to work two web resources will be created by using the following names and the content:

  • sample_/CrmAssociateDisassociateSilverlightTestPage.html
    An HTML page can be used to view the Silverlight control outside a form. The only purpose of this web resource is to provide the URL of the server when the Microsoft Silverlight control cannot access it itself through the Xrm.Page.context.getServerUrl when the Silverlight web resource is added in an entity form.
  • sample_/Clientbin/CrmAssociateDisassociateSilverlight.xap
    The name of this web resource just reflects the relative output location of the .xap file in the Microsoft Visual Studio 2010 Silverlight application (version 4) project.

Note

The customization prefix ‘sample_’ is not used in code. These samples will work using the customization prefix from any publisher. However the relative path of the simulated Scripts folder must be included in the name of the web resources.

Demonstrates

This sample provides the following:

  • Shows how to perform associate and disassociate operations using the REST endpoint and Silverlight.

  • When you preview the sample_/CrmAssociateDisassociateSilverlightTestPage.html web resource, you have two options:

    • Show Associating two Accounts

    • Show Associations using ActivityParty

  • If you choose Show Associating two Accounts, two accounts will be created and associated with one of the accounts set as the parent account. You will be prompted to disassociate and delete the accounts. If you choose not to disassociate and delete the accounts, the following output is displayed. You have the opportunity to open the account record that is associated to the other account.

    Created Account named “AssociateDisassociateExample1 6/8/2011 2:29:47 PM”

with AccountId =”9EA099DB-BFD6-4495-9526-8620B5EF6A0D” for testing.
Created Account named “AssociateDisassociateExample2 6/8/2011 2:29:47 PM”
with AccountId =”D32B9BC5-9653-46C3-A9F0-AA265CC1DCC7” for testing.
Account named “AssociateDisassociateExample1 6/8/2011 2:29:47 PM” (9EA099DB-BFD6-4495-9526-8620B5EF6A0D)
is now the parent of AssociateDisassociateExample2 6/8/2011 2:29:47 PM (D32B9BC5-9653-46C3-A9F0-AA265CC1DCC7)
You decided not to delete the records.
View them here.

  • If you choose Show Associations using ActivityParty, an account and email record will be created and associated with the account set as an ActivityParty in the From field of the email. You will be prompted to disassociate and delete the records. If you choose not to disassociate and delete the records, the following output is displayed. You have the opportunity to open the email record to view the associated account.

    Created Account named “ActivityPartyAssociateExample 6/8/2011 2:39:37 PM”

with AccountId =”65D7E730-6957-44B4-8265-2808A794A3C9” for testing.
Created Email with subject “ActivityPartyAssociateExample 6/8/2011 2:39:37 PM”
with ActivityId =”CE7E3D02-D39F-472B-8E46-DA0F946B65B1” for testing.
Created Activity party; the Account and Email are now related
You decided not to delete the records.
View them here.

Preliminary Steps

This Silverlight application uses a service reference named CrmODataService that is added by using the steps detailed under Generating WCF Data Services Client Data Service Classes. The name of the System.Data.Services.Client context is ‘AdventureWorksCycleContext’ because the name of the organization was AdventureWorksCycle.

sample_/CrmAssociateDisassociateSilverlightTestPage.html

The following sample is the HTML file that is produced when you create a Visual Studio 2010 Silverlight Application (version 4) project named ‘CrmAssociateDisassociateSilverlight’ and select to include a website with it. Only three lines have been modified:

  • The #silverlightControlHost style width is set to 100%

  • The script reference to the Silverlight.js file is commented out (or removed). This will be provided by Microsoft Dynamics CRM.

  • A script reference to ../ClientGlobalContext.js.aspx was added. This provides access to a context object that is access from the code within MainPage.xaml.cs.

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>
    <title>CrmAssociateDisassociateSilverlight</title>
    <style type="text/css">
    html, body {
        height: 100%;
        overflow: auto;
    }
    body {
        padding: 0;
        margin: 0;
    }
    #silverlightControlHost {
        height: 100%;
        width: 100%;
        text-align:center;
    }
    </style>
    <script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script>
    <script type="text/javascript">
        function onSilverlightError(sender, args) {
            var appSource = "";
            if (sender != null && sender != 0) {
                appSource = sender.getHost().Source;
            }

            var errorType = args.ErrorType;
            var iErrorCode = args.ErrorCode;

            if (errorType == "ImageError" || errorType == "MediaError") {
                return;
            }

            var errMsg = "Unhandled Error in Silverlight Application " + appSource + "\n";

            errMsg += "Code: " + iErrorCode + "    \n";
            errMsg += "Category: " + errorType + "       \n";
            errMsg += "Message: " + args.ErrorMessage + "     \n";

            if (errorType == "ParserError") {
                errMsg += "File: " + args.xamlFile + "     \n";
                errMsg += "Line: " + args.lineNumber + "     \n";
                errMsg += "Position: " + args.charPosition + "     \n";
            }
            else if (errorType == "RuntimeError") {
                if (args.lineNumber != 0) {
                    errMsg += "Line: " + args.lineNumber + "     \n";
                    errMsg += "Position: " + args.charPosition + "     \n";
                }
                errMsg += "MethodName: " + args.methodName + "     \n";
            }

            throw new Error(errMsg);
        }
    </script>
</head>
<body>
    <form id="form1" runat="server" style="height:100%">
    <div id="silverlightControlHost">
        <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
          <param name="source" value="ClientBin/CrmAssociateDisassociateSilverlight.xap"/>
          <param name="onError" value="onSilverlightError" />
          <param name="background" value="white" />
          <param name="minRuntimeVersion" value="4.0.50401.0" />
          <param name="autoUpgrade" value="true" />
          <a href="https://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50401.0" style="text-decoration:none">
              <img src="https://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
          </a>
        </object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>
    </form>
</body>
</html>

sample_/Clientbin/CrmAssociateDisassociateSilverlight.xap

This Silverlight file is compiled from files in a Visual Studio 2010 Silverlight application (version 4) project named ‘CrmAssociateDisassociateSilverlight’. The only files that were created or modified are as follows:

  • ServerUtility.cs
    A static class that provides functions to retrieve the server URL.
  • ConfirmationViewModel.cs
    Defines a class used to specify the text displayed in the confirmation dialog.
  • ConfirmationDialog.xaml
    A new XAML page added to provide a dialog when prompting to delete or disassociate the records created.
  • ConfirmationDialog.xaml.cs
    The Visual C# code-behind file to support ConfirmationDialog.xaml.
  • MainPage.xaml
    The default main page for the Silverlight application.
  • MainPage.xaml.cs
    The Visual C# code-behind file to support MainPage.xaml.

ServerUtility.cs

using System;
using System.Windows.Browser;

namespace Microsoft.Crm.Sdk.Samples
{
    public static class ServerUtility
    {
        /// <summary>
        /// Returns the ServerUrl from Microsoft Dynamics CRM.
        /// </summary>
        /// <returns>
        /// String representing the ServerUrl or String.Empty if not found.
        /// </returns>
        public static String GetServerUrl()
        {
            String serverUrl = String.Empty;

            // Try to get the ServerUrl from the Xrm.Page object.
            serverUrl = GetServerUrlFromContext();

            return serverUrl;
        }

        /// <summary>
        /// Attempts to retrieve the ServerUrl from the Xrm.Page object.
        /// </summary>
        /// <returns></returns>
        private static String GetServerUrlFromContext()
        {
            try
            {
                // If the Silverlight is in a form, this will get the server url.
                ScriptObject xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
                ScriptObject page = (ScriptObject)xrm.GetProperty("Page");
                ScriptObject pageContext = (ScriptObject)page.GetProperty("context");

                String serverUrl = (String)pageContext.Invoke("getServerUrl");

                // The trailing forward slash character from CRM Online needs to be
                // removed.
                if (serverUrl.EndsWith("/"))
                {
                    serverUrl = serverUrl.Substring(0, serverUrl.Length - 1);
                }

                return serverUrl;
            }
            catch
            {
                return String.Empty;
            }
        }
    }
}

ConfirmationViewModel.cs

using System;

namespace Microsoft.Crm.Sdk.Samples
{
    public class ConfirmationViewModel
    {
        public String ConfirmationText { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="confirmationText"></param>
        public ConfirmationViewModel(String confirmationText)
        {
            ConfirmationText = confirmationText;
        }
    }
}

ConfirmationDialog.xaml

<controls:ChildWindow x:Class="Microsoft.Crm.Sdk.Samples.ConfirmationDialog"
           xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
           Width="400" Height="300" 
           Title="Delete Confirmation">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Button x:Name="CancelButton" Content="No" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton" Content="Yes" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
        <TextBlock Height="Auto" HorizontalAlignment="Stretch" Margin="5" Name="content" Text="{Binding ConfirmationText}" VerticalAlignment="Stretch" />
    </Grid>
</controls:ChildWindow>

ConfirmationDialog.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;

namespace Microsoft.Crm.Sdk.Samples
{
    public partial class ConfirmationDialog : ChildWindow
    {
        internal object State { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="state">
        /// An object that will be stored for when the dialog closes.
        /// </param>
        public ConfirmationDialog(object state, String confirmationText, String title)
        {
            State = state;
            Title = title;
            // Create a ViewModel for the confirmation text binding.
            DataContext = new ConfirmationViewModel(confirmationText);
            InitializeComponent();
        }

        /// <summary>
        /// Handles the OK Button and sets result to true, representing delete.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = true;
        }

        /// <summary>
        /// Handles the Cancel Button and sets result to false, representing do not
        /// delete.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }
    }
}

MainPage.xaml

    <UserControl x:Class="Microsoft.Crm.Sdk.Samples.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel>
            <Button Click="ShowAccountsSample_Click" Name="ShowAccountsSample" Content="Show Associating two Accounts"
                Width="200" Height="20" VerticalAlignment="Top" HorizontalAlignment="Right"/>
            <Button Name="ShowActivityPartySample" Click="ShowActivityPartySample_Click"
                Content="Show Associations using ActivityParty"
                Width="220" Height="20" VerticalAlignment="Top" HorizontalAlignment="Right"/>
        </StackPanel>
        <StackPanel x:Name="MessagePanel" VerticalAlignment="Top" />
    </Grid>
</UserControl>

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Crm.Sdk.Samples.CrmODataService;
using System.Windows.Browser;

namespace Microsoft.Crm.Sdk.Samples
{
    public partial class MainPage : UserControl
    {
        private SynchronizationContext _syncContext;
        private AdventureWorksCycleContext _context;
        private String _serverUrl;

        public MainPage()
        {
            InitializeComponent();

            // Keep a reference to the UI thread.
            _syncContext = SynchronizationContext.Current;

            // Get the ServerUrl (ServerUrl is formatted differently OnPremise than
            // OnLine).
            _serverUrl = ServerUtility.GetServerUrl(); 

            if (!String.IsNullOrEmpty(_serverUrl))
            {
                
                // Setup context.
                _context = new AdventureWorksCycleContext(
                    new Uri(String.Format("{0}/xrmservices/2011/organizationdata.svc/", 
                        _serverUrl), UriKind.Absolute));

                //This is important because if the entity has new 
                //attributes added the code will fail.
                _context.IgnoreMissingProperties = true;
            }
            else
            {
                // No ServerUrl was found. Display a message.
                SendMessage(
                        "Unable to access server url. Launch this Silverlight " + 
                        "Web Resource from a CRM Form OR host it in a valid " + 
                        "HTML Web Resource with a " + 
                        "<script src='../ClientGlobalContext.js.aspx' " + 
                        "type='text/javascript'></script>" 
                );
            }
        }

        private void ShowAccountsSample_Click(object sender, RoutedEventArgs e)
        {
            // First, create some example accounts to link together.
            BeginCreateExampleAccounts();
            ShowAccountsSample.Visibility = Visibility.Collapsed;
            ShowActivityPartySample.Visibility = Visibility.Collapsed;
        }

        private void ShowActivityPartySample_Click(object sender, RoutedEventArgs e)
        {
            // First, create an example account and e-mail.
            BeginCreateAccountAndEmail();
            ShowAccountsSample.Visibility = Visibility.Collapsed;
            ShowActivityPartySample.Visibility = Visibility.Collapsed;
        }

        #region AccountsAssociation

        /// <summary>
        /// Create 2 example accounts for associating and disassociating.
        /// </summary>
        private void BeginCreateExampleAccounts()
        {
            var account1 = new Account
            {
                Name = String.Format(
                    "AssociateDisassociateExample1 {0}", DateTime.Now.ToString())
            };

            var account2 = new Account
            {
                Name = String.Format(
                    "AssociateDisassociateExample2 {0}", DateTime.Now.ToString())
            };

            _context.AddToAccountSet(account1);
            _context.AddToAccountSet(account2);
            var accounts = new List<Account> { account1, account2 };
            _context.BeginSaveChanges(OnExampleAccountsCreated, accounts);
        }

        /// <summary>
        /// Notifies when the Example Accounts have been created, then delegates to a
        /// a method that associates the accounts.
        /// </summary>
        /// <param name="result"></param>
        private void OnExampleAccountsCreated(IAsyncResult result)
        {
            try
            {
                _context.EndSaveChanges(result);
                var accounts = (List<Account>)result.AsyncState;
                foreach (var account in accounts)
                {
                    NotifyAccountCreation(account);
                }

                // Associate the accounts we just created.
                BeginAssociateAccounts(accounts);
            }
            catch (SystemException se)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
            }
        }

        /// <summary>
        /// Associated 2 accounts, making the first account the parent of the second
        /// account.
        /// </summary>
        /// <param name="accounts"></param>
        private void BeginAssociateAccounts(List<Account> accounts)
        {
            Debug.Assert(accounts.Count == 2);

            // This will make accounts[0] the parent of accounts[1].
            _context.AddLink(
                accounts[0], "Referencedaccount_parent_account", accounts[1]);

            _context.BeginSaveChanges(OnAccountsAssociated, accounts);
        }

        /// <summary>
        /// Notify the user when the accounts have been associated.  Ask if they should
        /// be disassociated.
        /// </summary>
        /// <param name="result"></param>
        private void OnAccountsAssociated(IAsyncResult result)
        {
            try
            {
                _context.EndSaveChanges(result);
                var accounts = (List<Account>)result.AsyncState;
                Debug.Assert(accounts.Count == 2);

                var account1 = accounts[0];
                var account2 = accounts[1];

                var message = new StringBuilder();
                message.AppendLine(String.Format(
                    "Account \"{0}\" ({1})", account1.Name, account1.AccountId));
                message.AppendFormat("\tis now the parent of Account \"{0}\" ({1})",
                    account2.Name, account2.AccountId);

                SendMessage(message.ToString());

                // Ask the user to confirm whether the accounts should be disassociated.
                var confirm = new ConfirmationDialog(accounts,
                    "Do you want to disassociate the accounts?",
                    "Disassociate Confirmation");
                confirm.Closed += OnDisassociateAccountsConfirmClosed;
                confirm.Show();
            }
            catch (SystemException se)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
            }
        }

        /// <summary>
        /// Decide whether to disassociate the accounts or skip this step, based on the
        /// user's input.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDisassociateAccountsConfirmClosed(object sender, EventArgs e)
        {
            var dialog = (ConfirmationDialog)sender;
            if (dialog.DialogResult.HasValue && dialog.DialogResult.Value)
            {
                BeginDisassociateAccounts((List<Account>)dialog.State);
            }
            else
            {
                // Skip disassociation and ask if the user wants to delete the accounts.
                ConfirmDeleteAccounts((List<Account>)dialog.State);
            }
        }

        /// <summary>
        /// Disassociate 2 accounts, removing the parent-child relationship between the
        /// first account and the second account.
        /// </summary>
        /// <param name="accounts">the accounts to disassociate</param>
        private void BeginDisassociateAccounts(List<Account> accounts)
        {
            Debug.Assert(accounts.Count == 2);
            _context.DeleteLink(
                accounts[0], "Referencedaccount_parent_account", accounts[1]);
            _context.BeginSaveChanges(OnDisassociateAccountsComplete, accounts);
        }

        /// <summary>
        /// Notify the user when the accounts have been disassociated.
        /// </summary>
        /// <param name="result"></param>
        private void OnDisassociateAccountsComplete(IAsyncResult result)
        {
            try
            {
                _context.EndSaveChanges(result);
                var accounts = (List<Account>)result.AsyncState;
                Debug.Assert(accounts.Count == 2);

                var account1 = accounts[0];
                var account2 = accounts[1];

                var message = new StringBuilder();

                message.AppendLine(String.Format(
                    "Account \"{0}\" ({1})", account1.Name, account1.AccountId));

                message.AppendFormat(
                    "\tis no longer the parent of Account \"{0}\" ({1})",
                    account2.Name, account2.AccountId);

                SendMessage(message.ToString());
                ConfirmDeleteAccounts(accounts);
            }
            catch (SystemException se)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
            }
        }

        /// <summary>
        /// Ask the user to confirm if the accounts should be deleted.
        /// </summary>
        /// <param name="accounts">accounts to be stored in the dialog's state</param>
        private void ConfirmDeleteAccounts(List<Account> accounts)
        {
            var confirm = new ConfirmationDialog(accounts,
                    "Do you want to delete the accounts?", "Delete confirmation");
            confirm.Closed += OnDeleteAccountsConfirmClosed;
            confirm.Show();
        }

        /// <summary>
        /// Delete the accounts if the user confirms that they should be deleted.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDeleteAccountsConfirmClosed(object sender, EventArgs e)
        {
            var dialog = (ConfirmationDialog)sender;
            // Retrieve the accounts from the dialog's state.
            var accounts = (List<Account>)dialog.State;
            if (dialog.DialogResult.HasValue && dialog.DialogResult.Value)
            {
                BeginDeleteAccounts(accounts);
            }
            else
            {
                // Show the user where they can view the records.
                SendMessage("You decided not to delete the records.");
                SendHyperlink("View them here.", String.Format(
                    "etn=account&pagetype=entityrecord&id=%7B{0}%7D",
                    accounts[1].AccountId));
            }
        }

        /// <summary>
        /// Delete the passed in accounts.
        /// </summary>
        /// <param name="accounts">the accounts to delete</param>
        private void BeginDeleteAccounts(List<Account> accounts)
        {
            foreach (var account in accounts)
            {
                _context.DeleteObject(account);
            }
            _context.BeginSaveChanges(OnAccountsDeleted, accounts);
        }

        /// <summary>
        /// When the accounts have been deleted, notify the user.
        /// </summary>
        /// <param name="result"></param>
        private void OnAccountsDeleted(IAsyncResult result)
        {
            try
            {
                _context.EndSaveChanges(result);
                var accounts = (List<Account>)result.AsyncState;
                Debug.Assert(accounts.Count == 2);

                var account1 = accounts[0];
                var account2 = accounts[1];

                var message = new StringBuilder();

                message.AppendLine(String.Format(
                    "Account \"{0}\" ({1})", account1.Name, account1.AccountId));

                message.AppendLine(String.Format(
                    "\tand Account \"{0}\" ({1})",
                    account2.Name, account2.AccountId));

                message.AppendFormat("\thave been deleted.");

                SendMessage(message.ToString());
            }
            catch (SystemException se)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
            }
        }

        #endregion

        #region AccountAndEmailAssociation

        /// <summary>
        /// Creates an example Account and Email for use in the rest of the sample.
        /// </summary>
        private void BeginCreateAccountAndEmail()
        {
            var account = new Account
            {
                Name = String.Format(
                    "ActivityPartyAssociateExample {0}", DateTime.Now.ToString())
            };
            _context.AddToAccountSet(account);

            var email = new Email
            {
                Subject = String.Format(
                    "ActivityPartyAssociateExample {0}", DateTime.Now.ToString())
            };

            _context.AddToEmailSet(email);

            _context.BeginSaveChanges(OnAccountAndEmailCreated,
                new Tuple<Email, Account>(email, account));
        }

        /// <summary>
        /// Notifies the user once the e-mail and account have been created.
        /// </summary>
        /// <param name="result"></param>
        private void OnAccountAndEmailCreated(IAsyncResult result)
        {
            try
            {
                _context.EndSaveChanges(result);
                var emailAndAccount = (Tuple<Email, Account>)result.AsyncState;
                NotifyAccountCreation(emailAndAccount.Item2);
                NotifyEmailCreation(emailAndAccount.Item1);
                BeginCreateActivityParty(emailAndAccount);
            }
            catch (SystemException se)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
            }
        }

        /// <summary>
        /// Creates an ActivityParty entity to associate the account and the e-mail.
        /// ActivityParty serves as the junction entity between e-mails and accounts in
        /// the relationship between accounts and e-mails (and between other activities
        /// and parties).
        /// </summary>
        /// <param name="emailAndAccount"></param>
        private void BeginCreateActivityParty(Tuple<Email, Account> emailAndAccount)
        {
            var email = emailAndAccount.Item1;
            var account = emailAndAccount.Item2;
            var activityParty = new ActivityParty();

            // Set the "activity" of the ActivityParty (the e-mail, in this case).
            activityParty.ActivityId = new EntityReference()
            {
                Id = email.ActivityId, LogicalName = "email"
            };

            // Set the "party" of the ActivityParty (what will be related to the
            // activity).
            activityParty.PartyId = new EntityReference()
            {
                Id = account.AccountId,
                LogicalName = "account"
            };
            
            // Set the participation type (what role the party has on the activity). For
            // this example, we'll put the account in the From field (which has a value of
            // 1).
            activityParty.ParticipationTypeMask = new OptionSetValue()
            {
                Value = 1
            };

            _context.AddToActivityPartySet(activityParty);
            _context.BeginSaveChanges(OnActivityPartyCreated,
                new Tuple<Email, Account, ActivityParty>(email, account, activityParty));
        }

        /// <summary>
        /// Notify the user that the activity party has been created, link the account
        /// to the e-mail.
        /// </summary>
        /// <param name="result"></param>
        private void OnActivityPartyCreated(IAsyncResult result)
        {
            try
            {
                _context.EndSaveChanges(result);
                var triple = (Tuple<Email, Account, ActivityParty>)result.AsyncState;
                SendMessage(
                    "Created Activity Party; the Account and Email are now related");

                // Disassociating an activity and a party (deleting the activityparty)
                // record is not supported by OData; otherwise, we would include that
                // option here.

                // Confirm whether the user wishes to delete the account and e-mail.
                var confirm = new ConfirmationDialog(triple,
                    "Do you want to delete the account and e-mail?",
                    "Deletion confirmation");
                confirm.Closed += OnDeleteAccountAndEmailConfirmClosed;
                confirm.Show();
            }
            catch (SystemException se)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
            }
        }

        /// <summary>
        /// If the user wanted account and e-mail deleted, delete them.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDeleteAccountAndEmailConfirmClosed(object sender, EventArgs e)
        {
            var dialog = (ConfirmationDialog)sender;
            var triple = (Tuple<Email, Account, ActivityParty>)dialog.State;
            if (dialog.DialogResult.HasValue && dialog.DialogResult.Value)
            {
                _context.DeleteObject(triple.Item1);
                _context.DeleteObject(triple.Item2);
                _context.BeginSaveChanges(OnAccountAndEmailDeleted, triple);
            }
            else
            {
                // Show the user where they can view the records.
                SendMessage("You decided not to delete the records.");
                SendHyperlink("View them here.", String.Format(
                    "etn=email&pagetype=entityrecord&id=%7B{0}%7D",
                    triple.Item1.ActivityId));
            }
        }

        /// <summary>
        /// Notify the user that the account and e-mail were deleted.
        /// </summary>
        /// <param name="result"></param>
        private void OnAccountAndEmailDeleted(IAsyncResult result)
        {
            try
            {
                _context.EndSaveChanges(result);

                SendMessage("The Account and Email have been deleted.");
            }
            catch (SystemException se)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
            }
        }

        #endregion

        /// <summary>
        /// Displays a message in the MessagePanel.
        /// </summary>
        /// <param name="message">the message to send</param>
        private void SendMessage(String message)
        {
            MessagePanel.Children.Add(new TextBlock() { Text = message });
        }

        private void SendHyperlink(String text, String queryString)
        {
            dynamic window = HtmlPage.Window;
            var serverUrl = ServerUtility.GetServerUrl();

            var uri = new Uri(String.Format("{0}/main.aspx?{1}", serverUrl, queryString),
                UriKind.Absolute);
            MessagePanel.Children.Add(new HyperlinkButton
            {
                Content = new TextBlock { Text = text },
                NavigateUri = uri
            });
        }

        /// <summary>
        /// Notifies the user that an account has been created.
        /// </summary>
        /// <param name="account"></param>
        private void NotifyAccountCreation(Account account)
        {
            var message = new StringBuilder();
            message.AppendLine(
                String.Format("Created Account named \"{0}\"", account.Name));
            message.AppendFormat(
                "\twith AccountId = \"{0}\" for testing.", account.AccountId);
            SendMessage(message.ToString());
        }

        /// <summary>
        /// Notifies the user that an e-mail has been created.
        /// </summary>
        /// <param name="email"></param>
        private void NotifyEmailCreation(Email email)
        {
            var message = new StringBuilder();
            message.AppendLine(
                String.Format("Created Email with subject \"{0}\"", email.Subject));
            message.AppendFormat(
                "\tand ActivityId = \"{0}\" for testing.", email.ActivityId);
            SendMessage(message.ToString());
        }

        /// <summary>
        /// Will display exception details if an exception is caught.
        /// </summary>
        /// <param name="ex">An System.Exception object</param>
        private void showErrorDetails(object ex)
        {
            // Assure the control is visible.
            MessagePanel.Visibility = System.Windows.Visibility.Visible;

            Exception exception = (Exception)ex;
            String type = exception.GetType().ToString();

            MessagePanel.Children.Add(new TextBlock() { Text = 
                String.Format("{0} Message: {1}", type, exception.Message) });

            MessagePanel.Children.Add(new TextBlock() { Text = 
                String.Format("Stack: {0}", exception.StackTrace) });

            if (exception.InnerException != null)
            {
                String exceptType = exception.InnerException.GetType().ToString();
                MessagePanel.Children.Add(new TextBlock() { Text = 
                    String.Format("InnerException: {0} : {1}", exceptType, 
                        exception.InnerException.Message) });
            }
        }
    }
}

Microsoft Dynamics CRM 2011
Send comments about this topic to Microsoft.
© 2013 Microsoft Corporation. All rights reserved.