Friday 31 May 2013

How to Consume a WCF in a silverlight Mscrm

Create a Class Library
Create a class library project named DataAccessLayar and add a class named Customer.cs with following public properties.
namespace DataAccessLayer
{
    [DataContract]
    public class Customer
    {
        [DataMember]
        public string ContactName { get; set; }
        [DataMember]
        public string CustomerID { get; set; }
        [DataMember]
        public string Address { get; set; }
        [DataMember]
        public string City { get; set; }
        [DataMember]
        public string Hiredate { get; set; }
        [DataMember]
        public string Country { get; set; }
    }
}
In the above code snippet, you can notice that Customer class definition has an attribute named DataContract and properties have DataMember. These attributes are necessary to work with Wcf Services. If we will not keep them, WCF service will not be able to transfer the object and its properties. In order to add these attribute you may need to add reference of System.Runtime.Serialization (Right click and Add Reference … from .NET Tab)  to your class library.
Add another class called DataAccessLayer.cs in this class library that has a method called GetCustomers. This method is responsible for connecting to the database and returning the results as Generic lists. Here is the code for this.
NOTE: To avoid complications in this tutorial, I have created Customer class and DataAccessLayer class in the same class library project. In real scenario, you should separate them out.
string NorthwindConnStr = ConfigurationSettings.AppSettings.Get("NorthwindConnStr").ToString();
        /// <summary>
        /// Get customers
        /// </summary>
        /// <returns></returns>
        public List<Customer> GetCustomers(string startsWith)
        {
            List<Customer> list = new List<Customer>();
            using (SqlConnection conn = new SqlConnection(NorthwindConnStr))
            {
                conn.Open();
                using (SqlCommand dCmd = new SqlCommand("vs_GetCustomersStartingWith", conn))
                {
                    SqlParameter prms = new SqlParameter("@startsWith", SqlDbType.VarChar, 5);
                    prms.Value = startsWith + "%";
                    dCmd.Parameters.Add(prms);
                    dCmd.CommandType = CommandType.StoredProcedure;
                    using (SqlDataReader reader = dCmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            list.Add(new Customer
                            {
                                CustomerID = reader["CustomerID"].ToString(),
                                Address = reader["Address"].ToString(),
                                City = reader["City"].ToString(),
                                Country = reader["Country"].ToString(),
                                ContactName = reader["ContactName"].ToString()
                            });
                        }
                    }
                }
                conn.Close(); // lets close explicitely
            }
            return list;
        }
In the above method, I have created a generic type variable that will accept Customer object, I have a stored procedure named vs_GetCustomersStartsWith that will take a string as a parameter and return all records whose name starts with parameter value.

Finally I am forming the Customer object inside while (reader.Read()) loop and adding it into the generic list collection.
Get solutions of the .NET problems with video explanations, .pdf and source code in .NET How to's.
Create a WCF Service
Let’s create the WCF Service now. Right click the solution and click Add > New Proejct … Select Visual C# > Web from the Project Type and WCF Service Application from Templates, name it as WcfService. This will by default create following files:
  1. IService1.cs
  2. Service1.svc
  3. Service1.svc.cs
  4. Web.config
Now, let’s Add Reference to our Class Library project into our WCFService, right click the WcfService project and click Add Reference … Select Projects tab and select the DataAccessLayer project.
In the IService1 interface remove all code and add a method definition named GetCustomers. Notice that the interface declaration must have an attribute ServiceContract and its method declaration must have an attribute OperationContract otherwise this class and its member will not be exposed as services. My sample IService1 interface code looks like this.
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Collections.Generic;
using DataAccessLayer;
namespace WcfService
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        List<Customer> GetCustomers(string startsWith);
    }
}
Now let’s implement the above method in Service1.svc.cs file. If your project names are same as mine, your code for this file looks like below.
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using DataAccessLayer;
namespace WcfService
{
    public class Service1 : IService1
    {
        public List<Customer> GetCustomers(string startsWith)
        {
            DataAccessLayer.DataAccessLayer dal = new DataAccessLayer.DataAccessLayer();
            return dal.GetCustomers(startsWith);
        }
    }
}
Now we need a little configuration change in web.config file. So open the config file of WcfService project and change following.
In the system.serviceModel tag
  1. For endpoint – change the value of binding to basicHttpBinding.
  2. For identity > dns change the value to “localhost:3637” //as we are going to fix the port in the project properties later on
Following is the code snippet related with endpoint and dns of my web.config file
<!-- Service Endpoints -->


<endpoint address="" binding="basicHttpBinding" contract="WcfService.IService1">


<!-- 

Upon deployment, the following identity element should be removed or replaced to reflect the 

identity under which the deployed service runs. If removed, WCF will infer an appropriate identity 


automatically.

-->

<identity>

<dns value="localhost:3637"/>


</identity>

</endpoint>


<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>


Now right click the project, click Properties and go to Web tab (left side) and select Specific port and enter 3637 in the textbox and save it. This is to avoid automatically generation of random ports while we are working on our sample application.
As Silverlight doesn’t support cross domain access by default so we need create a ClientAccessPolicy.xml file that is read when any request is sent to the server to access wcf service.  In order to work for cross domain, write following code in this file
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>
This ensures that your wcf service will be consumed by Silverlight application even if both do not fall in the same domain domain.
Save this project and build it.
Now, right click the WcfService project and go to Debug and click start new Instance. This will open up a browser with http://localhost:3637/Service1.svc address. Click Stop button from Visual Studio and return to normal mode, you will notice that your localhost:3637 port is still active and is able to listen the request (See bottom-right of your screen and you will see that ASP.NET Development Server – Port 3637 is still active).  Keep it as it is.
Consume WCF Service in Silverlight
Right click the Silverlight application and click Add Service Reference... . Write the url of the wcf service you got above in the address box, click Go button. In the Namespace box, write WcfServiceReference and click OK. This should add a reference of the wcf service and also create ServiceReferences.ClientConfig file in the root of your Silverlight application.
Double click the xaml file and drag a TextBlock, TextBox, Button and DataGrid to your xaml file and ensure that they are looking similar to the below code snippet. This will ensure that your Search form is looking good.
<Grid x:Name="LayoutRoot" Background="White">
        <Canvas>
            <TextBlock Text="Search" Canvas.Left="10" Canvas.Top="12"></TextBlock>
            <TextBox x:Name="txtSearch" Width="75" Height="22" Canvas.Left="55" Canvas.Top="10"></TextBox>
            <Button Content="  Get Data from WCF Service  " Click="Button_Click" Canvas.Top="9" Canvas.Left="140"></Button>
            <TextBlock x:Name="lblWaiting" Canvas.Left="375" Canvas.Top="10" Text="Status: Idle"></TextBlock>
            <data:DataGrid x:Name="dataGrid1" Canvas.Left="10" Canvas.Top="35" Width="550" Height="300" IsReadOnly="True"
                           ItemsSource="{Binding Mode=OneWay}" AutoGenerateColumns="True" >
            </data:DataGrid>
        </Canvas>
    </Grid>
As you can see that we have specified Button_Click event handler that will fire when user clicks button after entering keyword in the textbox. Let us see the code for this event.
private void Button_Click(object sender, RoutedEventArgs e)
        {
            // call data web service
            var proxy = new WcfServiceReference.Service1Client("BasicHttpBinding_IService1");
            proxy.GetCustomersCompleted += new EventHandler<SilverlightApplication.WcfServiceReference.GetCustomersCompletedEventArgs>(proxy_GetCustomerCompleted);
            proxy.GetCustomersAsync(txtSearch.Text.Trim());
            // call simple web service
            lblWaiting.Text = "Status: Waiting ...";
        }
//-------------------------------------------------
        void proxy_GetCustomerCompleted(object sender, WcfServiceReference.GetCustomersCompletedEventArgs e)
        {
            System.Collections.ObjectModel.ObservableCollection<WcfServiceReference.Customer> list = e.Result;
            dataGrid1.ItemsSource = list;
            lblWaiting.Text = "Status: Done";
            if (list.Count.Equals(0))
                lblWaiting.Text = "No records found.";
            else
                lblWaiting.Text = list.Count + " records found.";
        }
You can see that in the Button_Click event I have specified a variable named proxy and instantiating it by passing the endpointConfigurationName as parameter. endpointConfigurationName is nothing but the bindingConfiguration value of the ServiceReferences.ClientConfig file.
Later on I have specified the event handler for GetCustomersCompleted and specified the parameter for GetCustomerAsync method as the keyword (textbox value).
When the GetCustomersCompleted will finish, it will raise an event named proxy_GetCustomerCompleted. In this event, write the code like above. Build your Silverlight project and ensure that it is built successfully.

Sunday 26 May 2013

Outer Join in Fetch Xml Ms Crm 2011

Creating a query to retrieve  opportunities with fields from related account entity. Here is the query.
 
  
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
  <entity name="opportunity">
    <attribute name="name" />
    <attribute name="customerid" />
     <attribute name="estimatedvalue_base" />
     <order attribute="name" descending="false" />
    <link-entity name="account" from="accountid" to="customerid" visible="false" link-type="outer" alias="accountid">
      <attribute name="telephone1" />
    </link-entity>
</entity>
</fetch>
 
 
 
 example 2
What if we want to return all the opportunities and also any phone call 
activities related to these opportunities. This is also achievable using
 outer joins. There is 1:N relationship between opportunity and 
activitypointer entity. Therefore the query will return multiple records
 for the same opportunity if there are more than one phone call recorded
 against an opportunity. Here is fetch xml for the query

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
  <entity name="opportunity">
    <attribute name="name" />
    <attribute name="customerid" />
    <attribute name="estimatedvalue_base" />
     <order attribute="name" descending="false" />
    <link-entity name="account" from="accountid" to="customerid" visible="false" link-type="outer" alias="accountid">
      <attribute name="telephone1" />
    </link-entity>
<link-entity name="activitypointer" from="regardingobjectid" to="opportunityid" visible="false" link-type="outer" alias="phonecall">
      <attribute name="subject" />
 <attribute name="description" />
 <attribute name="createdon" />
<filter type="and">
      <condition attribute="activitytypecode" operator="eq" value="4210" />
</filter>
    </link-entity>
  </entity>
</fetch> 
 
 

Adding Custom Button in MsCrm 2011

Suppose we want to add a button next to “Save & Close” button in Account Entity’s form.
To do that we need to define a CustomAction child element to CustomActions element within RibbonDiffXml.
Export the solution containing the account entity.
Attributes for CustomAction are
ID- Give it any unique id.
Location- For defining location we first need to find out the id for Save group within the MainTab of the form and then append _children to it.
_children needs to be defined to add a custom button to an existing group.
Open the accountribbon.xml file within
.. sdk\samplecode\cs\client\ribbon\exportribbonxml\exportedribbonxml\
There we can find the following info.

<Groups Id=”Mscrm.Form.account.MainTab.Groups“>


<Group Id=”
Mscrm.Form.account.MainTab.Save Command=”Mscrm.Enabled Sequence=”10 Title=”$Resources:Ribbon.Form.MainTab.Save Image32by32Popup=”/_imgs/ribbon/save32.png Template=”Mscrm.Templates.Flexible2“>


<Controls Id=”
Mscrm.Form.account.MainTab.Save.Controls“>


<Button Id=”Mscrm.Form.account.SaveAsComplete ToolTipTitle=”$Resources:Ribbon.Form.MainTab.Save.SaveAsComplete ToolTipDescription=”$Resources(EntityDisplayName):Ribbon.Tooltip.SaveAsComplete Command=”Mscrm.SavePrimaryActivityAsComplete Sequence=”10 LabelText=”$Resources:Ribbon.Form.MainTab.Save.SaveAsComplete Alt=”$Resources:Ribbon.Form.MainTab.Save.SaveAsComplete Image16by16=”/_imgs/ribbon/SaveAsCompleted_16.png Image32by32=”/_imgs/ribbon/SaveAsCompleted_32.png TemplateAlias=”o1
/>


Sequence : For sequence first find out the sequence defined for Save & Close button.
<Button Id=”Mscrm.Form.account.SaveAndClose ToolTipTitle=”$Resources:Mscrm_Form_Other_MainTab_Save_SaveAndClose_ToolTipTitle ToolTipDescription=”$Resources(EntityDisplayName):Ribbon.Tooltip.SaveAndClose Command=”Mscrm.SaveAndClosePrimary
Sequence=”30″
LabelText=”$Resources:Ribbon.Form.MainTab.Save.SaveAndClose Alt=”$Resources:Ribbon.Form.MainTab.Save.SaveAndClose Image16by16=”/_imgs/ribbon/saveandclose16.png Image32by32=”/_imgs/ribbon/saveandclose32.png TemplateAlias=”o1
/>


So now our CustomAction should look like this
<CustomAction Id=”CA_MyFirstButton Location=”Mscrm.Form.account.MainTab.Save.Controls._children Sequence=”31“>

Now we next need to define CommandUIDefinition and Button child element to our CustomAction
<CommandUIDefinition>

<Button
Id=B_MyFirstButton


LabelText=My First Button


ToolTipTitle=My First Button Tool Tip Title


ToolTipDescription=My First Button Tool Tip Description


TemplateAlias=o1


Image16by16=/_imgs/ribbon/saveandclose16.png


Image32by32=/_imgs/ribbon/saveandclose32.png

/>

</CommandUIDefinition>

Here we are using the existing icon of Save and Close button.
Save the file, zip it as a part of the solution and import and publish it.

Final definition should look something like this. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<RibbonDiffXml>
<CustomActions>
<CustomAction
 Id="CA_MyFirstButton"
 Location="Mscrm.Form.account.MainTab.Save.Controls._children"
 Sequence="31">
<CommandUIDefinition>
<Button
 Id="B_MyFirstButton"
 LabelText="My First Button"
 ToolTipTitle="My First Button Tool Tip Title"
 ToolTipDescription="My First Button Tool Tip Description"
 TemplateAlias="o1"
 Image16by16="/_imgs/ribbon/saveandclose16.png"
 Image32by32="/_imgs/ribbon/saveandclose32.png"/>
</CommandUIDefinition>
</CustomAction>
</CustomActions>
<Templates>
<RibbonTemplates
 Id="Mscrm.Templates"/>
</Templates>
<CommandDefinitions/>
<RuleDefinitions>
<TabDisplayRules/>
<DisplayRules/>
<EnableRules/>
</RuleDefinitions>
<LocLabels/>
</RibbonDiffXml>

Friday 24 May 2013

Create ,Update and Delete View in MScrm 2011

                    System.String layoutXml =
@"<grid name='resultset' object='3' jump='name' select='1' 
    preview='1' icon='1'>
    <row name='result' id='opportunityid'>
    <cell name='name' width='150' /> 
    <cell name='customerid' width='150' /> 
    <cell name='estimatedclosedate' width='150' /> 
    <cell name='estimatedvalue' width='150' /> 
    <cell name='closeprobability' width='150' /> 
    <cell name='opportunityratingcode' width='150' /> 
    <cell name='opportunitycustomeridcontactcontactid.emailaddress1' 
        width='150' disableSorting='1' /> 
    </row>
</grid>";

                    System.String fetchXml =
                    @"<fetch version='1.0' output-format='xml-platform' 
    mapping='logical' distinct='false'>
    <entity name='opportunity'>
    <order attribute='estimatedvalue' descending='false' /> 
    <filter type='and'>
        <condition attribute='statecode' operator='eq' 
        value='0' /> 
    </filter>
    <attribute name='name' /> 
    <attribute name='estimatedvalue' /> 
    <attribute name='estimatedclosedate' /> 
    <attribute name='customerid' /> 
    <attribute name='opportunityratingcode' /> 
    <attribute name='closeprobability' /> 
    <link-entity alias='opportunitycustomeridcontactcontactid' 
        name='contact' from='contactid' to='customerid' 
        link-type='outer' visible='false'>
        <attribute name='emailaddress1' /> 
    </link-entity>
    <attribute name='opportunityid' /> 
    </entity>
</fetch>";

                    SavedQuery sq = new SavedQuery
                    {
                        Name = "A New Custom Public View",
                        Description = "A Saved Query created in code",
                        ReturnedTypeCode = "opportunity",
                        FetchXml = fetchXml,
                        LayoutXml = layoutXml,
                        QueryType = 0
                    };
                    
                    _customViewId = _serviceProxy.Create(sq);
                    Console.WriteLine("A new view with the name {0} was created.", sq.Name);
 
 
 
 
 Retrieve View :
 
 
        QueryExpression mySavedQuery = new QueryExpression
        {
            ColumnSet = new ColumnSet("savedqueryid", "name", "querytype", "isdefault", "returnedtypecode", "isquickfindquery"),
            EntityName = SavedQuery.EntityLogicalName,
            Criteria = new FilterExpression
            {
                Conditions =
{
    new ConditionExpression
    {
        AttributeName = "querytype",
        Operator = ConditionOperator.Equal,
        Values = {0}
    },
    new ConditionExpression
    {
        AttributeName = "returnedtypecode",
        Operator = ConditionOperator.Equal,
        Values = {Opportunity.EntityTypeCode}
    }
}
            }
        };
        RetrieveMultipleRequest retrieveSavedQueriesRequest = new RetrieveMultipleRequest { Query = mySavedQuery };

        RetrieveMultipleResponse retrieveSavedQueriesResponse = (RetrieveMultipleResponse)_serviceProxy.Execute(retrieveSavedQueriesRequest);

        DataCollection<Entity> savedQueries = retrieveSavedQueriesResponse.EntityCollection.Entities;

        //Display the Retrieved views
        foreach (Entity ent in savedQueries)
        {
            SavedQuery rsq = (SavedQuery)ent;
            Console.WriteLine("{0} : {1} : {2} : {3} : {4} : {5},", rsq.SavedQueryId, rsq.Name, rsq.QueryType, rsq.IsDefault, rsq.ReturnedTypeCode, rsq.IsQuickFindQuery);
        }

Deactivate Views

If you do not want a public view to appear in the application, you can deactivate it. You cannot deactivate a public view that is set as the default view. The following sample deactivates the Closed Opportunities in Current Fiscal Year view for the Opportunity entity:
System.String SavedQueryName = "Closed Opportunities in Current Fiscal Year";
QueryExpression ClosedOpportunitiesViewQuery = new QueryExpression
{
    ColumnSet = new ColumnSet("savedqueryid", "statecode", "statuscode"),
    EntityName = SavedQuery.EntityLogicalName,
    Criteria = new FilterExpression
    {
        Conditions =
        {
            new ConditionExpression
            {
                AttributeName = "querytype",
                Operator = ConditionOperator.Equal,
                Values = {0}
            },
            new ConditionExpression
            {
                AttributeName = "returnedtypecode",
                Operator = ConditionOperator.Equal,
                Values = {Opportunity.EntityTypeCode}
            },
                            new ConditionExpression
            {
                AttributeName = "name",
                Operator = ConditionOperator.Equal,
                Values = {SavedQueryName}
            }
        }
    }
};

RetrieveMultipleRequest retrieveOpportuntiesViewRequest = new RetrieveMultipleRequest { Query = ClosedOpportunitiesViewQuery };

RetrieveMultipleResponse retrieveOpportuntiesViewResponse = (RetrieveMultipleResponse)_serviceProxy.Execute(retrieveOpportuntiesViewRequest);

SavedQuery OpportunityView = (SavedQuery)retrieveOpportuntiesViewResponse.EntityCollection.Entities[0];
_viewOriginalState = (SavedQueryState)OpportunityView.StateCode;
_viewOriginalStatus = OpportunityView.StatusCode;


SetStateRequest ssreq = new SetStateRequest
{
    EntityMoniker = new EntityReference(SavedQuery.EntityLogicalName, (Guid)OpportunityView.SavedQueryId),
    State = new OptionSetValue((int)SavedQueryState.Inactive),
    Status = new OptionSetValue(2)
};
_serviceProxy.Execute(ssreq);

Edit Filter Criteria or Configure Sorting

 

Deactivate form using Entity Moniker in mscrm 2011

IOrganizationService service;

new_entity record;  // a record of new_entity, can be any Entity.

SetStateRequest setStateReq = new SetStateRequest();

setStateReq.EntityMoniker = new EntityReference(record.LogicalName, record.Id);

//State represents the field with schema name “state code” and display name “status”.

//Suppose we are working on the account entity  in which the state code field has to be 1 so that the record be inactive.
setStateReq.State = new OptionSetValue(1);

//Status  represents the field with schema name “Status code” and display name “Status Reason”.

// set this value to -1 to let the system automatically set the appropriate corresponding status value.

setStateRequest.Status = new OptionSetValue(-1);
// Execute the request
SetStateResponse response = (SetStateResponse)service.Execute(setStateReq)

Note:  you can know at which values your entity is active or inactive by going to customizations , opening your entity then opening  field “status code” and checking the items of the drop down new the label “status” which are the values of the state code variable, you will see the text only and you can know the value as the value of the first item is 0 and every item after it  its value is incremented by 1(if we took the screenshot as an example the value of “Active” will be “0″ and the value of Inactive will be “1″ ).

How disable form , tab , filed using java script in Mscrm2011


Category Archives: Ms Crm 2011 JavaScript Events

Disable / Enable fields, sections, tabs and the whole Form in MS CRM 2011

Disable/Enable fields, sections, tabs and the whole Form in MS CRM 2011
When working with the MS CRM form , Your requirement will be to enable (set to read/write) or disable (set to read / only) selected fields, sections, tabs and the Whole form.
Please have a glance below code for these functionalities to work out.
1)     Enable / Disable a field
Xrm.Page.getControl(“fieldname”).setDisabled(false); 
2)    Enable / Disable a Section
function sectiondisable (sectionname, disablestatus)
{
var ctrlName = Xrm.Page.ui.controls.get();
for(var i in ctrlName) {
var ctrl = ctrlName[i];
var ctrlSection = ctrl.getParent().getName();
if (ctrlSection == sectionname) {
ctrl.setDisabled(disablestatus);
}
}
}  // sectiondisable
3)    Enable / Disable a Tab
function tabdisable (tabname, disablestatus)
{
 var tab = Xrm.Page.ui.tabs.get(tabname);
 if (tab == null) alert("Error: The tab: " + tabname + " is not on the form");
 else {
     var tabsections =  tab.sections.get();
     for (var i in tabsections) {
         var secname = tabsections[i].getName();
         sectiondisable(secname, disablestatus);
     }
  }
}   // tabdisable

4)    Enable / Disable a Form
function formdisable(disablestatus)
{
    var allAttributes = Xrm.Page.data.entity.attributes.get();
    for (var i in allAttributes) {
           var myattribute = Xrm.Page.data.entity.attributes.get(allAttributes[i].getName());
           var myname = myattribute.getName();          
           Xrm.Page.getControl(myname).setDisabled(disablestatus); 
    }
} // formdisable

5)     Enable / Disable All Controls in the TAB
function DisableAllControlsInTab(tabControlNo) 
{  
var tabControl = Xrm.Page.ui.tabs.get(tabControlNo);
    if (tabControl != null) 
{    
      Xrm.Page.ui.controls.forEach
( 
     function (control, index) 
{          
if (control.getParent().getParent() == tabControl && control.getControlType() != "subgrid") 
{              control.setDisabled(true);  
        }      
});
      } 
 }
function EnableAllControlsInTab(tabControlNo)
 {      
var tabControl = Xrm.Page.ui.tabs.get(tabControlNo);      
if (tabControl != null) 
{         
 Xrm.Page.ui.controls.forEach
(      
function (control, index) 
{          
if (control.getParent().getParent() == tabControl && control.getControlType() != "subgrid") 
{              
control.setDisabled(false);  
        }  
    });  
    } 
 }

Thursday 23 May 2013

Plugin in Oflline mode in outlook

Step 1: Modify the AccountCreate Plugin to Support Offline Execution

In this step, you are going to modify the AccountCreatePlugin project sample code to add support for executing the sample plug-in offline. The following code sample shows the complete AccountCreatePlugin source code with the offline support code changes highlighted.
[C#]
using System;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace Microsoft.Crm.Sdk.Walkthrough
{
    /// <summary>
    /// A simple plug-in class that creates a task activity
    /// to follow up with the customer in one week. The plug-in must
    /// be registered to execute after (post-event) an account is created
    /// in Microsoft Dynamics CRM 4 (on-premise).
    /// </summary>
    public class AccountCreateHandler: IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            DynamicEntity entity = null;

            // Check if the input parameters property bag contains a
            // target of the create operation and that target is of type
            // DynamicEntity.
            if (context.InputParameters.Properties.Contains("Target") &&
               context.InputParameters.Properties["Target"] is 
               DynamicEntity)
            {
                // Obtain the target business entity from the input
                // parameters.
                entity = (DynamicEntity) context.InputParameters.Properties["Target"];

                // Verify that the entity represents an account.
                if (entity.Name != EntityName.account.ToString()) { return; }
            }
            else
            {
                return;
            }

            try
            {
               // Create a task activity to follow up with the account customer in 7 days. 
               DynamicEntity followup = new DynamicEntity();
               followup.Name = EntityName.task.ToString();

               followup.Properties = new PropertyCollection();
               followup.Properties.Add(new StringProperty("subject", 
                 "Send e-mail to the new customer."));
               followup.Properties.Add(new StringProperty("description", 
                  "Follow up with the customer. Check if there are any new issues that need resolution."));

               followup.Properties.Add(new CrmDateTimeProperty("scheduledstart", 
                  CrmTypes.CreateCrmDateTimeFromUniversal(DateTime.Now.AddDays(7))));
               followup.Properties.Add(new CrmDateTimeProperty("scheduledend", 
                  CrmTypes.CreateCrmDateTimeFromUniversal(DateTime.Now.AddDays(7))));

               followup.Properties.Add(new StringProperty("category",
                  context.PrimaryEntityName));

               // Check if the service is offline.
               if (context.IsExecutingInOfflineMode)
               {
                    // When in offline mode, a key must be generated for
                    // each new entity. Perform any other offline work here.
                    Key key = new Key();
                    key.Value = Guid.NewGuid();

                    followup.Properties.Add(new KeyProperty("activityid", key));
               }

               // Refer to the new account in the task activity.
               if (context.OutputParameters.Properties.Contains("id"))
               {
                  Lookup lookup = new Lookup();
                  lookup.Value = new Guid(context.OutputParameters.Properties["id"].ToString());
                  lookup.type = EntityName.account.ToString();

                  followup.Properties.Add(new LookupProperty("regardingobjectid", lookup));
               }

               TargetCreateDynamic targetCreate = new TargetCreateDynamic();
               targetCreate.Entity = followup;

               // Create the request object.
               CreateRequest create = new CreateRequest();
               create.Target = targetCreate;

               // Create the task on the Microsoft Dynamics CRM server.
               ICrmService service = (ICrmService)context.CreateCrmService(true);
               CreateResponse created = (CreateResponse)service.Execute(create);
            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                throw new InvalidPluginExecutionException(
                    "An error occurred in the AccountCreateHandler plug-in.", ex);
            }
        }
    }
}
To add offline support to the AccountCreatePlugin project code, follow these steps:
  1. Make a copy of the SDK\Walkthroughs\AccountCreatePlugin folder. You can modify the copy for this walkthrough. As an alternative, use the solution that you created in the Creating a Simple Plug-in walkthrough.
  2. Double-click the AccountCreatePlugin.sln file to open the solution in Visual Studio.
  3. Modify the Plugin.cs file according to the code changes that are highlighted in the sample code shown above.
  4. Compile the project by clicking Build, and then Build Solution.
A plug-in assembly named AccountCreatePlugin.dll can now be found in the bin/Debug folder of your project.

Step 2: Register your Plug-in with the PluginRegistration Tool

The plug-in registration tool enables you to register your plug-in with a specific event in Microsoft Dynamics CRM.
To register your plug-in
  1. In Visual Studio click Tools, and then click CRM Plug-in Registration Tool. Plug-in Registration Tool
  2. Follow the instructions in Step 5 of the Creating a Simple Plug-in walkthrough to register the AccountCreatePlugin.dll assembly and plug-in that have been modified according to the instructions in this walkthrough. Do not follow the "To Register a Step for your plug-in" procedure in that walkthrough. Continue with the step registration procedure in this walkthrough.
To Register a Step for your plug-in
This next procedure registers a step that defines the conditions under which the plug-in is to execute.
  1. Select the plug-in (AccountCreateHandler) in the tree view of the tool.
  2. Select Register, and then click Register New Step.
  3. In the Register New Step dialog box, enter the information as shown in the figure below and select Register New Step. Registering an online and offline step
    Note that both the Server and Offline boxes are checked.
You just registered a synchronous post-event plug-in that will run when a new account is created in online or offline mode.

Step 3: Add a Registry Sub-key to the AllowList

An additional security restriction for an offline plug-in requires that a registry key named after the public key token of the plug-in assembly must be added to the system registry.
Determine the plug-in assembly public key token
  1. In the PluginRegistration tool, click the assembly in the tree view that contains the offline plug-in.
  2. Write down the value in the Public Key Token field to use in the next procedure.
Create a sub-key under the AllowList registry key
  1. On the host computer that runs Microsoft Dynamics CRM for Outlook, select Start, and then click Run.
  2. Type regedit in the text field and click OK to start the registry editor.
  3. Expand the tree view folders and navigate to the following registry key:
    My Computer\HKEY_CURRENT_USER\Software\Microsoft\MSCRMClient\AllowList
  4. Right-click the AllowList key, select New, then click Key.
  5. Type the offline plug-in assembly's public key token value as the name of the new key.
The following figure shows the AllowList with two sample keys. The keys were named with the public key token value of two assemblies containing offline plug-ins.
Adding a sub-key to the AllowList key

Test the Plug-in in Offline Mode

Test the plug-in by creating an account. This will trigger the plug-in code to run.
  1. Open Microsoft Dynamics CRM for Microsoft Office Outlook with Offline Access and verify that the client is online. There should be a Go Offline button in the Microsoft Dynamics CRM toolbar.
  2. Click the Go Offline button. After a short while of processing, Microsoft Dynamics CRM for Outlook with Offline Access is placed in offline mode.
  3. Close the Synchronizing Microsoft Dynamics CRM Data dialog box when the processing is finished.
  4. In the toolbar, click New Record and then click Account.
  5. Enter a unique Account Name and click Save and Close.
  6. After Microsoft Dynamics CRM finishes saving, click on the Activities link under Details.
  7. You should see a new activity created for the new account with the subject Send e-mail to the new customer.
In this walkthrough the plug-in was registered for both online and offline execution. When Microsoft Dynamics CRM for Microsoft Office Outlook with Offline Access goes from offline to online mode, synchronization of the client is performed with the Microsoft Dynamics CRM server. During this synchronization period the account that was created in Outlook while offline is now created on the server. Because the plug-in is registered for account creation in online mode, the plug-in executes a second time and creates a second follow-up activity. If this is undesirable behavior, you can either design your plug-in code to work around this issue or register the plug-in for offline mode only.

Test the Plug-in in Online Mode

The procedure to test the plug-in in online mode is similar to testing in offline mode.
  1. Open Microsoft Dynamics CRM for Outlook with Offline Access and verify that it is online. There should be a Go Offline button in the Microsoft Dynamics CRM toolbar.
  2. In the toolbar, click New Record and then click Account.
  3. Enter a unique Account Name and click Save and Close.
  4. After Microsoft Dynamics CRM finishes saving, click on the Activities link under Details.
  5. You should see a new activity created for the new account with the subject Send e-mail to the new customer.