Create, Send, Route an Email with a template an attachment

As the title suggests, I want to Create an email from a template and attach a file.  I also want the option to send the email or route to a queue for validation before manually hitting Send.

The purpose for the is an invoice process. Generate a batch of invoices, create a PDF from a PDF template , attach the file to a templated email etc…

Firstly, create an email from a template.  The template has been created and is saved as an Email Template:

  1. // Create an e-mail message using a template.
  2. InstantiateTemplateRequest instTemplateReq = new InstantiateTemplateRequest
  3. {
  4.     TemplateId = fetchTemplateId(LocalName),
  5.     ObjectId = Id,
  6.     ObjectType = "invoice"
  7. };
  8. InstantiateTemplateResponse instTemplateResp = null;
  9. Entity eMail = null;
  10.  
  11. try
  12. {
  13.     instTemplateResp = (InstantiateTemplateResponse)helper.Execute(instTemplateReq, true);
  14. }
  15. catch (Exception ex)
  16. {
  17.     log.Write(this, "Failed to create email from template (using " + LocalName + ") " + ex.Message.ToString());
  18. }

 

Using the InstantiateTemplateRequest, I need to specify the templateid (derived the the PDF template name),the objectid (the previously created invoiceid) and the objecttype – the invoice.

The result is an email Entity:

  1. eMail = instTemplateResp.EntityCollection.Entities[0];

 

From this point I can create my PDF and attach:

  1. // Fetch invoicenumber and add to parameters
  2. string prefix = "topmostSubform[0].Page1[0].";
  3. string invno = fetchInvoiceNumber(Id);
  4. Parameters.Add(new PDFParameter() { PathName = prefix + "invno[0]", Value = invno });
  5.  
  6. writeEmailAttachmentToCrm(emailId, "email", invno + ".pdf", createPDFArray(LocalName, Parameters));

 

Depending on the parameters passed I need to either SEND or ROUTE to an invoice queue for checking:

  1.  
  2. if (Send)
  3. {
  4.     // Send the email
  5.     SendEmailRequest send = new SendEmailRequest();
  6.     send.EmailId = emailId;
  7.     send.TrackingToken = "";
  8.     send.IssueSend = true;
  9.  
  10.     //SendEmailResponse sendEmailresp = (SendEmailResponse)helper.Execute(send, true);
  11. }
  12. else
  13. {
  14.     // write to queue
  15.     AddToQueueRequest routeRequest = new AddToQueueRequest
  16.     {
  17.         Target = new EntityReference("email", emailId),
  18.         DestinationQueueId = Guid.Parse("3CEB6F78-2698-E111-9B55-782BCB776B8C")
  19.     };
  20.  
  21.     helper.Execute(routeRequest, true);
  22.  
  23. }

 

During testing , I’ve taken precautions to NOT send emails:

1. Comment out the SendRequest

2. Hardcode the partyIds

3. Hardcode the Queue

Hopefully this demonstrates how easy it is to send templated emails with attachments. 

Oh Yes – the PDF routines – using iTextSharp:

  1. /// <summary>
  2. /// Create a PDFstream from a localfile
  3. /// </summary>
  4. /// <param name="LocalName"></param>
  5. /// <returns></returns>
  6. public PDFData CreatePDF(Guid Id, string LocalName, ObservableCollection<PDFParameter> Parameters)
  7. {
  8.     PDFData returnData = new PDFData();
  9.  
  10.     string currentDir = AppDomain.CurrentDomain.BaseDirectory + @"Templates\";
  11.  
  12.     PdfReader reader = new PdfReader(currentDir + LocalName );
  13.  
  14.     // filling in the form
  15.  
  16.     MemoryStream op = new MemoryStream();
  17.     PdfStamper stamp1 = new PdfStamper(reader, op);
  18.     AcroFields form1 = stamp1.AcroFields;
  19.  
  20.     foreach (PDFParameter parameter in Parameters)
  21.     {
  22.         try
  23.         {
  24.             form1.SetField(parameter.PathName, parameter.Value);
  25.         }
  26.         catch (Exception ex)
  27.         { }
  28.     }
  29.     stamp1.FormFlattening = true;
  30.     stamp1.Close();
  31.  
  32.     returnData.Data = op.ToArray();
  33.     returnData.Id = Id;
  34.  
  35.     op.Close();
  36.     op.Dispose();
  37.     reader.Close();
  38.  
  39.     return returnData;
  40. }

The PDF files are preformatted with placeholders (fields) to be substituted here .

Posted in CRM 2011 | Comments Off

Parse savedquery and userquery attributes with Linq

I’ve been writing a Silverlight grid that displays columns from a CRM view.  (dynamic grid,  display labels as column names and built at runtime). 

Some challenges, the first being how to parse the output from the returned fetch query to get the view columns, linked entities and conditions.

Firstly, my fetchxml looks like this:

  1.             string fetchXml = @"<fetch mapping='logical'>
  2.                                     <entity name='{1}'>
  3.                                         <attribute name='name'/>
  4.                                         <attribute name='layoutxml'/>
  5.                                         <attribute name='fetchxml'/>
  6.                                         <filter type='and'>
  7.                                             <condition attribute='returnedtypecode' operator='eq' value='{0}'/>
  8.                                         </filter>
  9.                                     </entity>
  10.                                 </fetch>
  11.                             ";

{0} = either savedquery or userquery – depending on the purpose of the grid.

The output from the above used for the test code below:

  1. static string LayoutXml = "<grid name=\"resultset\" object=\"1090\" jump=\"name\" select=\"1\" icon=\"1\" preview=\"1\"><row name=\"result\" id=\"invoiceid\"><cell name=\"customerid\" width=\"150\"/><cell name=\"name\" width=\"300\"/><cell name=\"invoicenumber\" width=\"125\"/><cell name=\"t4a_collectionmethod\" width=\"125\"/><cell name=\"t4a_invoicedate\" width=\"100\"/><cell name=\"t4a_issueddate\" width=\"100\"/><cell name=\"statecode\" width=\"100\"/><cell name=\"totalamount\" width=\"100\"/><cell name=\"t4a_adviserid\" width=\"150\"/><cell name=\"a_550a704187f6e01187a9b8ac6f1693a8.fullname\" width=\"100\" disableSorting=\"1\"/></row></grid>";
  2.  static string FetchXml = "<fetch version=\"1.0\" output-format=\"xml-platform\" mapping=\"logical\" distinct=\"false\"><entity name=\"invoice\"><attribute name=\"name\"/><attribute name=\"customerid\"/><attribute name=\"totalamount\"/><attribute name=\"statecode\"/><attribute name=\"t4a_issueddate\"/><attribute name=\"t4a_invoicedate\"/><attribute name=\"t4a_collectionmethod\"/><attribute name=\"t4a_adviserid\"/><attribute name=\"invoicenumber\"/><attribute name=\"invoiceid\"/><order attribute=\"customerid\" descending=\"false\"/><link-entity name=\"systemuser\" from=\"systemuserid\" to=\"t4a_adviserid\" visible=\"false\" link-type=\"outer\" alias=\"a_550a704187f6e01187a9b8ac6f1693a8\"><attribute name=\"fullname\"/></link-entity></entity></fetch>";
  3.  
  4.  static string FetchConditionXml = "<fetch version=\"1.0\" output-format=\"xml-platform\" mapping=\"logical\" distinct=\"false\"><entity name=\"invoice\"><attribute name=\"name\" /><attribute name=\"totalamount\" /><attribute name=\"customerid\" /><attribute name=\"statecode\" /><attribute name=\"t4a_issueddate\" /><attribute name=\"t4a_invoicedate\" /><attribute name=\"t4a_collectionmethod\" /><attribute name=\"t4a_adviserid\" /><attribute name=\"invoicenumber\" /><order attribute=\"t4a_invoicedate\" descending=\"true\" /><filter type=\"and\"><filter type=\"and\"><condition attribute=\"statecode\" operator=\"eq\" value=\"0\" /><condition attribute=\"duedate\" operator=\"last-x-years\" value=\"5\" /></filter></filter><attribute name=\"invoiceid\" /></entity></fetch>";

 

1. Parse the display columns:

  1. XDocument layoutRows = XDocument.Parse(LayoutXml);
  2.  
  3. var lrows = from i in layoutRows.Descendants("grid").Descendants("row").Descendants("cell")
  4.            select new
  5.            {
  6.                name = i.Attribute("name").Value
  7.            };
  8.  
  9. foreach (var item in lrows)
  10. {
  11.     Console.WriteLine(item.name);
  12. }

 

2. Parse the “linked” entities for the view

  1. XDocument fetchRows = XDocument.Parse(FetchXml);
  2.  
  3. var frows = from i in fetchRows.Descendants("fetch").Descendants("entity").Descendants("link-entity")
  4.            select new
  5.            {
  6.                name = i.Attribute("name").Value,
  7.                alias = i.Attribute("alias").Value
  8.            };
  9.  
  10. foreach (var item in frows)
  11. {
  12.     Console.WriteLine(item.name);
  13.     Console.WriteLine(item.alias);
  14. }

 

3. Parse the “condition” for the view

  1. XDocument fetchConditionRows = XDocument.Parse(FetchConditionXml);
  2.  
  3. var fcrows = from i in fetchConditionRows.Descendants("fetch").Descendants("entity").Descendants("condition")
  4.             select new
  5.             {
  6.                 attribute = i.Attribute("attribute").Value,
  7.                 cOperator = i.Attribute("operator").Value,
  8.                 value = i.Attribute("value").Value
  9.             };
  10.  
  11. foreach (var item in fcrows)
  12. {
  13.     Console.WriteLine(item.attribute);
  14.     Console.WriteLine(item.cOperator);
  15.     Console.WriteLine(item.value);
  16.     Console.WriteLine();
  17. }

 

The above three routines demonstrate how to extract the necessary attributes using Linq.

NOTE: If you are using Silverlight, you will need to add System.Xml.Linq as a reference to use XDocument.

Posted in CRM 2011 | Tagged , , , | Comments Off

An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail

An interesting problem occurred today. 

Our claims server is virtual and the dev server is physical.

We rebooted the dev server whilst suspending the claims server. After the reboot, all of the WCF services complained about the “unsecured fault”.

The reason for this was the time difference between the claims and dev server.  Resyncing the date and time resolved the issue.

All our dev server have now been setup with timeservers!.

Posted in CRM 2011, WCF | Comments Off

Hiding nav items and groups

At some point you may need to show or hide individual nav items or the complete group.

Hide Item:

image

var navItem = Xrm.Page.ui.navigation.items.get(“navActivities”);
navItem .setVisible(false);

Show Item:

var navItem = Xrm.Page.ui.navigation.items.get(“navActivities”);
navItem .setVisible(true);

Hide Nav Group:

image

Located the tag which displays the groupname , in this case “Common”. Then hide the parent (as the parent doesn’t have an ID to find).

var navGroup = document.getElementById(“_NA_Info”);

navGroup.parentNode.style.display=”none”;

To find all the necessary IDs, use the IE debugger.  This can be started by F12.

Posted in CRM 2011 | Tagged , | Comments Off

Custom Lookups Filters

Just a very quick post to demonstrate how you can enhance the custom lookup filtering capabilities of the humble crm 2011 lookup.

In previous versions (Crm 3, 4.0) there were several published tricks to create filtered and custom lookups.  These , in general , were unsupported and wouldn’t upgrade.

This approach uses the addCustomView method. We tend to use this when the standard filters don’t offer us the flexibility.

The code snippet simply demonstrates how we can display values in a lookup based on another field. In this instance, an entity contains matching rules for a commission type. The calling entity only needs to choose matching rules based on the selected commission type.

  1. function setMatchRuleLookup(lookupFieldName, resetSelection) {
  2.  
  3.     if (resetSelection == true) {
  4.         // reset old selection for Matching rull
  5.         Xrm.Page.getAttribute(lookupFieldName).setValue(null);
  6.     }
  7.  
  8.     // Get the commission type – optionsetvalue
  9.     var commissionType = Xrm.Page.getAttribute("t4a_commissiontype").getValue();
  10.  
  11.     // use randomly generated GUID Id for our new view
  12.     var viewId = "{1DFB2B35-B07C-55D1-867D-258DEEBB87E2}";
  13.     var entityName = "t4a_matchingrule";
  14.  
  15.     // give the custom view a name
  16.     var viewDisplayName = "Matching rules for this commmission type";
  17.  
  18.     // Create a fetchXml statement
  19.     var fetchXml = "<fetch mapping='logical'> " +
  20.                    "     <entity name='t4a_matchingrule'> " +
  21.                    "         <attribute name='t4a_name'/> " +
  22.                    "         <filter type='and'> " +
  23.                    "             <condition attribute='t4a_commissiontype' operator='eq' value='" + commissionType + "'/> " +
  24.                    "         </filter> " +
  25.                    "     </entity> " +
  26.                    " </fetch>";
  27.  
  28.     // build Grid Layout
  29.     var layoutXml = "<grid name='resultset' " +
  30.                                "object='2' " +
  31.                                "jump='t4a_matchingrule' " +
  32.                                "select='1' " +
  33.                                "icon='1' " +
  34.                                "preview='1'>" +
  35.                             "<row name='result' " +
  36.                                 "id='t4a_matchingruleid'>" +
  37.                              "<cell name='t4a_name' " +
  38.                                    "width='200' />" +
  39.                             "</row>" +
  40.                          "</grid>";
  41.  
  42.     // add the Custom View to the indicated [lookupFieldName] Control
  43.     Xrm.Page.getControl(lookupFieldName).addCustomView(viewId, entityName, viewDisplayName, fetchXml, layoutXml,true);
  44. }

 

A more complex example shown here allows the lookup of relationships where we need to select client contacts from the selected agency

  1. var fetchXml = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>" +
  2.                "     <entity name='contact'> " +
  3.                "         <attribute name='accountid'/> " +
  4.                "         <attribute name='contactid'/> " +
  5.                "         <attribute name='fullname'/> " +
  6.                "         <link-entity name='account' from='accountid' to='parentcustomerid' alias='ClientAccount'> " +
  7.                "             <filter type='and'> " +
  8.                "                 <condition attribute='ts_isagent' operator='eq' value='0'/> " +
  9.                "             </filter> " +
  10.                "             <link-entity name='customerrelationship' from='customerid' to='accountid' alias='Customer'> " +
  11.                "                  <link-entity name='relationshiprole' from='relationshiproleid' to='customerroleid'> " +
  12.                "                    <filter type='and'> " +
  13.                "                        <condition attribute='name' operator='eq' value='Agency Client'/> " +
  14.                "                    </filter> " +
  15.                "                  </link-entity> " +
  16.                "                   <link-entity name='account' from='accountid' to='partnerid' alias='PartnerAccount'> " +
  17.                "                        <filter type='and'> " +
  18.                "                            <condition attribute='accountid' operator='eq' value='" + Xrm.Page.getAttribute("customerid").getValue()[0].id + "'/> " +
  19.                "                        </filter> " +
  20.                "                   </link-entity> " +
  21.                "               </link-entity> " +
  22.                "           </link-entity> " +
  23.                "       </entity> " +
  24.                "</fetch>";
  25.  
  26. // build Grid Layout
  27. var layoutXml = "<grid name='resultset' " +
  28.                            "object='2' " +
  29.                            "jump='contact' " +
  30.                            "select='1' " +
  31.                            "icon='1' " +
  32.                            "preview='1'>" +
  33.                        "<row name='result' " +
  34.                             "id='contactid'>" +
  35.                          "<cell name='fullname' " +
  36.                                "width='200' />" +
  37.                         "</row>" +
  38.                      "</grid>";

Finally, a BIG thank you to James Downey – the author of the FetchXml builder. I still use this tool in its CRM 4 livery, still loving it… a true rapid fetchxml building tool.

Posted in CRM 2011 | Tagged | Comments Off

Migration testing from CRM 4.0 to CRM 2011

We are just running through quite a few scenarios for upgrading our beta clients from CRM 4.0 to CRM 2011.

The first pass of the upgrade process went OK, however, it was a little slow. We made some changes to the source CRM 4.0 database (via the UI) and tried to re-upgrade/import this database into another tenant on the CRM 2011 system using the following:

CRM 4 Server

  • Change the CRM 4.0 config.
  • Backup the database

CRM 2011 Server

  • Move the backup to the CRM 2011 database server.
  • Restore the database to another name.
  • Import the tenant using another name.

We wanted to do this to minimise downtime for the data migration consultant. The end result was proposed to have the dm preparing against the earlier upgrade and the dev team tweeking the import/upgrade process.

However, the error “you have already imported and upgraded this organisation and cannot perform the operation again” was displayed from the Import Manager:

image

A few “bings” presented the same result – You can’t re-import the same CRM 4.0 database – and atleast have more than one tenant.

The solution we came up with works like this:

CRM 4 Server

  • Change the CRM 4.0 config.
  • Backup the database
  • Restore the bak to another database name
  • Import the organisation to another name
  • Backup the new database

CRM 2011 Server

  • Move the backup to the CRM 2011 database server.
  • Restore the database to another name.
  • Import the tenant using another name.

The import process checks for the OrganisationId, the trick is to get a new organisationid – this can be achieved by making a “clone” of the CRM 4.0 tenant using CRM 4.0 Deployment tool

Posted in CRM 2011, CRM 4.0 | Comments Off

Set a default pricelist to an Invoice etc…

The solution below implements Using the FetchXML CRM 2011 Service within a JavaScript Web Resource.

99% of our customisations require the pricelist to be pre-filled.  Here is a very quick snippet to populate the price list based on a supplied name and currency.

Firstly, the onload event:

  1. try {
  2.  
  3.     if (Xrm.Page.getAttribute("pricelevelid").getValue() == null) {
  4.         var priceLookup = GetPriceLevelId("Personal", Xrm.Page.getAttribute("transactioncurrencyid").getValue()[0].id);
  5.  
  6.         Xrm.Page.getAttribute("pricelevelid").setValue(priceLookup);
  7.  
  8.     }
  9. }
  10. catch (err)
  11. { }

 

Library function:

  1. function GetPriceLevelId(Name, CurrencyId) {
  2.  
  3.     var fetchXml = "<fetch mapping='logical'> " +
  4.                         "<entity name='pricelevel'> " +
  5.                         "    <attribute name='name'/> " +
  6.                         "   <filter type='and'> " +
  7.                         "        <condition attribute='name' operator='eq' value='" + Name + "'/> " +
  8.                         "        <condition attribute='transactioncurrencyid' operator='eq' value='" + CurrencyId + "'/> " +
  9.                         "    </filter> " +
  10.                         "</entity> " +
  11.                     "</fetch> "
  12.  
  13.     fetchService = new FetchUtil();
  14.     var pricelevelid = fetchService.Fetch(fetchXml)[0].attributes["pricelevelid"].value;
  15.  
  16.     var lookupData = new Array();
  17.     var lookupItem = new Object();
  18.     lookupItem.id = pricelevelid;
  19.     lookupItem.entityType = 'pricelevel';
  20.     lookupItem.name = Name;
  21.     lookupData[0] = lookupItem;
  22.     return lookupData;
  23. }

 

Enjoy , and Happy Christmas to all.

Posted in CRM 2011 | Tagged , , | Comments Off

Rename a ribbon button

My “5 min” project this morning was to make some ribbon changes.  I needed to go from this:

Detailed Scope:

image

Remove the three buttons highlighted and change…

· “Close as won” to “Close as Satisfied”

· “Close as lost” to “Close as NTU”

to this:

image

The SDK does not have commands for Replace or Change, however, it does have command for Add and Hide. With a combination of both we achieved our results.

1. Using the “sdk\samplecode\cs\client\ribbon\exportribbonxml” open the opportunityRibbon.xml in visual studio.

2. Note the group we want is for the form. Look for the “Mscrm.Form.opportunity.MainTab

image

Within this “Tab”, you will notice the group names correspond to the ribbon bar group names:

imageimage

3. Locate the group “Mscrm.Form.opportunity.MainTab.Actions”, note the button Ids for the buttons we need to remove and rename.

4. In CRM, create a solution and add the opportunity  (don’t include the required elements). Export and extract the zip.

5. Open the customisation.xml from the extracted solution in visual studio.

6. Locate the RibbonDiffXml .

7. Add a new CustomAction to replace the existing button. This example will replace the MarkAsWon button.

  1. <RibbonDiffXml>
  2.   <CustomActions>
  3.  
  4.     <CustomAction Id="T4A.opportunity.form.CloseAsWon.CustomAction"
  5.           Location="Mscrm.Form.opportunity.MainTab.Actions.Controls._children"
  6.           Sequence="3">
  7.  
  8.       <CommandUIDefinition>
  9.  
  10.         <Button Id="Mscrm.Form.opportunity.MarkAsWon"
  11.                 Command="Mscrm.Form.opportunity.MarkAsWon"
  12.                 Sequence="3"
  13.                 Alt="$Resources:Ribbon.Form.opportunity.MainTab.Actions.MarkAsWon"
  14.                 LabelText="$LocLabels:T4A.opportunity.form.CloseAsWon.LabelText"
  15.                 Image16by16="/_imgs/SFA/MarkAsWon_16.png"
  16.                 Image32by32="/_imgs/SFA/MarkAsWon_32.png"
  17.                 TemplateAlias="o1"
  18.                 ToolTipTitle="$LocLabels:T4A.opportunity.form.CloseAsWon.LabelText"
  19.                 ToolTipDescription="$LocLabels:T4A.opportunity.form.CloseAsWon.LabelText" />
  20.  
  21.       </CommandUIDefinition>
  22.     </CustomAction>

 

For the Button, I copied the existing code from the button section in the “Mscrm.Form.opportunity.MainTab.Actions” in the opportunityRibbon.xml.

8. Next define the labels.

  1.       </CustomActions>
  2.       <Templates>
  3.         <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
  4.       </Templates>
  5.            <LocLabels>
  6.           <LocLabel Id="T4A.opportunity.form.CloseAsWon.LabelText">
  7.             <Titles>
  8.                 <Title languagecode="1033"description="Close as Satisfied" />
  9.             </Titles>
  10.           </LocLabel>
  11.           <LocLabel Id="T4A.opportunity.form.CloseAsLost.LabelText">
  12.             <Titles>
  13.               <Title languagecode="1033" description="Close as NTU" />
  14.             </Titles>
  15.           </LocLabel>
  16.         </LocLabels>

9. Change the button LabelText, ToolTip to use the custom labels. Don’t change anything else in here as we need the button to function as standard.

10. Lastly, hide the existing buttons:

  1. <HideCustomAction Location="Mscrm.Form.opportunity.MarkAsWon" HideActionId="T4A.Mscrm.SubGrid.opportunity.AddExistingAssoc.HideAction" />
  2. <HideCustomAction Location="Mscrm.Form.opportunity.MarkAsLost" HideActionId="T4A.Mscrm.SubGrid.opportunity.AddExistingAssoc.HideAction" />
  3. <HideCustomAction Location="Mscrm.Form.opportunity.Recalculate" HideActionId="T4A.Mscrm.SubGrid.opportunity.AddExistingAssoc.HideAction" />

 

Again, The Location can be found in the opportuntyRibbon.xml

11. Save the customisation.xml, apply to the solution zip and import.

12. The completed RibbonDiffXml

 

  1. <RibbonDiffXml>
  2.         <CustomActions>
  3.  
  4.           <CustomAction Id="T4A.opportunity.form.CloseAsWon.CustomAction"
  5.                 Location="Mscrm.Form.opportunity.MainTab.Actions.Controls._children"
  6.                 Sequence="3">
  7.  
  8.             <CommandUIDefinition>
  9.  
  10.               <Button Id="Mscrm.Form.opportunity.MarkAsWon"
  11.                       Command="Mscrm.Form.opportunity.MarkAsWon"
  12.                       Sequence="3"
  13.                       Alt="$Resources:Ribbon.Form.opportunity.MainTab.Actions.MarkAsWon"
  14.                       LabelText="$LocLabels:T4A.opportunity.form.CloseAsWon.LabelText"
  15.                       Image16by16="/_imgs/SFA/MarkAsWon_16.png"
  16.                       Image32by32="/_imgs/SFA/MarkAsWon_32.png"
  17.                       TemplateAlias="o1"
  18.                       ToolTipTitle="$LocLabels:T4A.opportunity.form.CloseAsWon.LabelText"
  19.                       ToolTipDescription="$LocLabels:T4A.opportunity.form.CloseAsWon.LabelText" />
  20.  
  21.             </CommandUIDefinition>
  22.           </CustomAction>
  23.  
  24.           <CustomAction Id="T4A.opportunity.form.CloseAsLost.CustomAction"
  25.                 Location="Mscrm.Form.opportunity.MainTab.Actions.Controls._children"
  26.                 Sequence="4">
  27.  
  28.             <CommandUIDefinition>
  29.  
  30.               <Button Id="Mscrm.Form.opportunity.MarkAsLost"
  31.                       Command="Mscrm.Form.opportunity.MarkAsLost"
  32.                       Sequence="4"
  33.                       Alt="$Resources:Ribbon.Form.opportunity.MainTab.Actions.MarkAsLost"
  34.                       LabelText="$LocLabels:T4A.opportunity.form.CloseAsLost.LabelText"
  35.                       Image16by16="/_imgs/SFA/MarkAsLost_16.png"
  36.                       Image32by32="/_imgs/SFA/MarkAsLost_32.png"
  37.                       TemplateAlias="o1"
  38.                       ToolTipTitle="$LocLabels:T4A.opportunity.form.CloseAsLost.LabelText"
  39.                       ToolTipDescription="$LocLabels:T4A.opportunity.form.CloseAsLost.LabelText" />
  40.  
  41.             </CommandUIDefinition>
  42.           </CustomAction>
  43.  
  44.           <HideCustomAction Location="Mscrm.Form.opportunity.MarkAsWon" HideActionId="T4A.Mscrm.SubGrid.opportunity.AddExistingAssoc.HideAction" />
  45.           <HideCustomAction Location="Mscrm.Form.opportunity.MarkAsLost" HideActionId="T4A.Mscrm.SubGrid.opportunity.AddExistingAssoc.HideAction" />
  46.           <HideCustomAction Location="Mscrm.Form.opportunity.Recalculate" HideActionId="T4A.Mscrm.SubGrid.opportunity.AddExistingAssoc.HideAction" />
  47.  
  48.         </CustomActions>
  49.         <Templates>
  50.           <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
  51.         </Templates>
  52.              <LocLabels>
  53.             <LocLabel Id="T4A.opportunity.form.CloseAsWon.LabelText">
  54.               <Titles>
  55.                   <Title languagecode="1033"description="Close as Satisfied" />
  56.               </Titles>
  57.             </LocLabel>
  58.             <LocLabel Id="T4A.opportunity.form.CloseAsLost.LabelText">
  59.               <Titles>
  60.                 <Title languagecode="1033" description="Close as NTU" />
  61.               </Titles>
  62.             </LocLabel>
  63.           </LocLabels>     
  64.       </RibbonDiffXml>

Posted in CRM 2011 | Tagged , | Comments Off

Silverlight and the CRM form – get in sync

As part of our QA process, we are required to check all elements of our code against On-Premise, Partner Hosted and Microsoft On-line.

Generally, On-Premise always work, however, timing issues sometimes occur with Partner hosted and always happen with on-line.

Can I define time issues:

I have a complex CRM form that relies on a Silverlight control to “host” some scriptable objects.  The control should run through some routines and present a simple message in a text box back to the user. The challenge happens when the form is still loading and the Silverlight control has already loaded and is running its constructor. Therefore requests from the Silverlight control back to the starting CRM form fail:

Example:

  1. // Get the crm page
  2. Xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
  3.  
  4. try
  5. {
  6.     // Get the name of the entity
  7.     sourceEntity = Xrm.Page.data.entity.getEntityName();
  8. }
  9. catch
  10. {

The requested for the source entity from the page constructor will throw an exception..

I have tried to put a “wait” function here, but this only suspends the thread and doesn’t really solve the problem. My solution is to user the TimerCallback delegate found in the system.threading namespace.

The principle is the create a “callback” to my controlling routine after x milliseconds – giving the CRM form a chance to load and be ready for business.

  1. public partial class Page1 : Page
  2. {
  3.     dynamic Xrm = null; // name it capitialsed to cut & past from crm js
  4.     string sourceEntity = string.Empty;
  5.  
  6.     public TimerCallback adTimer = null;
  7.     private Timer timer;

A declaration of the TimerCallback and the Timer routine I will use as the end of the elapsed time.

  1. /// <summary>
  2. /// Main Page – runs on start of the Silverlight control
  3. /// </summary>
  4. public Page1()
  5. {
  6.     try
  7.     {
  8.         InitializeComponent();
  9.  
  10.         // Create a callback to the initcontrol for the timer
  11.         adTimer = new TimerCallback(callbackinitControl);
  12.  
  13.         // Try the routine first.
  14.         InitControl();
  15.     }
  16.     catch
  17.     { }
  18. }

The callback is defined in my constructor for the page.

  1. /// <summary>
  2. /// Call back routine for the timer
  3. /// </summary>
  4. /// <param name="stateInfo"></param>
  5. public void callbackinitControl(Object stateInfo)
  6. {
  7.     // Run the initcontrol back on the calling UI thread.
  8.     this.Dispatcher.BeginInvoke(() => InitControl());
  9. }

The callback will happen on a different thread, therefore I need to call my main control on the UI thread.

  1. /// <summary>
  2. /// A routine to run on Silverlight control startup
  3. /// </summary>
  4. public void InitControl()
  5. {
  6.     // Get the crm page
  7.     Xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
  8.  
  9.     try
  10.     {
  11.         // Get the name of the entity
  12.         sourceEntity = Xrm.Page.data.entity.getEntityName();
  13.     }
  14.     catch
  15.     {
  16.         // Restart, try again in 2 secs
  17.         timer = new Timer(adTimer, null, 2 * 1000, System.Threading.Timeout.Infinite);
  18.      }
  19.  
  20. }

The main routine, a simple line of code in my catch to start the timer, after 2 seconds run the routine defined as the callback delegate.

An the complete page:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Documents;
  8. using System.Windows.Input;
  9. using System.Windows.Media;
  10. using System.Windows.Media.Animation;
  11. using System.Windows.Shapes;
  12. using System.Windows.Navigation;
  13. using System.Threading;
  14. using System.Windows.Browser;
  15.  
  16. namespace Time4Advice
  17. {
  18.     public partial class Page1 : Page
  19.     {
  20.         dynamic Xrm = null; // name it capitialsed to cut & past from crm js
  21.         string sourceEntity = string.Empty;
  22.  
  23.         public TimerCallback adTimer = null;
  24.         private Timer timer;
  25.  
  26.         /// <summary>
  27.         /// Main Page – runs on start of the Silverlight control
  28.         /// </summary>
  29.         public Page1()
  30.         {
  31.             try
  32.             {
  33.                 InitializeComponent();
  34.  
  35.                 // Create a callback to the initcontrol for the timer
  36.                 adTimer = new TimerCallback(callbackinitControl);
  37.  
  38.                 // Try the routine first.
  39.                 InitControl();
  40.             }
  41.             catch
  42.             { }
  43.         }
  44.  
  45.         /// <summary>
  46.         /// Call back routine for the timer
  47.         /// </summary>
  48.         /// <param name="stateInfo"></param>
  49.         public void callbackinitControl(Object stateInfo)
  50.         {
  51.             // Run the initcontrol back on the calling UI thread.
  52.             this.Dispatcher.BeginInvoke(() => InitControl());
  53.         }
  54.  
  55.         /// <summary>
  56.         /// A routine to run on Silverlight control startup
  57.         /// </summary>
  58.         public void InitControl()
  59.         {
  60.             // Get the crm page
  61.             Xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
  62.  
  63.             try
  64.             {
  65.                 // Get the name of the entity
  66.                 sourceEntity = Xrm.Page.data.entity.getEntityName();
  67.             }
  68.             catch
  69.             {
  70.                 // Restart, try again in 2 secs
  71.                 timer = new Timer(adTimer, null, 2 * 1000, System.Threading.Timeout.Infinite);
  72.              }
  73.  
  74.         }
  75.     }
  76. }

 

Hope this helps someone.

Posted in CRM 2011 | Tagged | Comments Off

BBC Countryfile Calendar 2012

A good friend of mine runs the Tiverton Canal , and it extremely proud that his horse and barge have been chosen for the front of the BBC Countryfile 2012 Calendar.

The Tiverton canal is well worth a visit for a totally relaxing day, its also the last horse drawn barge in the west country.

” Breaking News “The Horse-Drawn Barge has now ” WON ” the overall competition for the BBC Country File Calendar 2012. The Horse-Drawn Barge has been proudly represented on the front, and inside the calendar. The calendar will be sold in aid of Children In Need. Last year the calendar sold 268,000 across the country, and raised 1.19m for the charity. To order your calendar in time for Christmas click here: https://www.hcscalendar.co.uk/home/index.asp

319281_273964882634745_100000637062033_923392_676851433_n

Posted in General | Tagged | Comments Off