Showing posts with label Online. Show all posts
Showing posts with label Online. Show all posts

Friday, May 13, 2016

Retrieving the Message from Service Bus Queue

This is a follow up to the previous post where I sent a strongly typed XML message from a CRM plugin to a Service Bus Queue. This is what the Xml looks like that I sent to the queue.
Note that the root node is the name of the entity. This is a snippet to show the attributes and then the formatted values.  You can easily manipulate the Xml by changing the way it populates the datatable.

 <contact>
  <attributes>
    <address1_addressid>c6fd930f-512a-42fb-a645-af4a672c740f</address1_addressid>
    <address2_addressid>6a280e36-c009-463b-84f1-8666c2dfc5ba</address2_addressid>
    <address2_addresstypecode>1</address2_addresstypecode>
    <address2_freighttermscode>1</address2_freighttermscode>
    <address2_shippingmethodcode>1</address2_shippingmethodcode>
    <address3_addressid>5475a20e-3c79-479a-a1d5-05b0f144a8fc</address3_addressid>
    <contactid>1d50bcf0-2519-e611-80da-5065f38b46e1</contactid>
    <createdby>Charles Emes</createdby>
    <createdon>13/05/2016 16:15:51</createdon>
;... more attributes
;... now the formatted values
    <address2_addresstypecodename>Default Value</address2_addresstypecodename>
    <address2_freighttermscodename>Default Value</address2_freighttermscodename>
    <address2_shippingmethodcodename>Default Value</address2_shippingmethodcodename>
    <createdonname>13/05/2016 17:15</createdonname>
  </attributes></contact>

What I'm missing is a namespace and that is easy enough to add once I load it into an XmlDocument. I'm a BizTalk developer so right now I'm happy with a message that I can transform into anything I want. The attributes are in alphabetical order so I can cope with missing attributes easily enough in the XSD by making Nillable=true and MinOccurs=0. But I can also deserialize this into a contact object by creating a class form the XSD using the XSD.EXE.  Note this is absolutely not a CRM Contact object but my own object. Check out my usings because there is no Microsoft.Xrm.SDK anywhere to be seen. 

This receiver is just a console app and I ripped the code from another blogger. I'm just using it show what you can do with the message now you've got it.

     
static void Main(string[] args)
{
string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
QueueClient Client = QueueClient.CreateFromConnectionString(connectionString, "anoqueue");
//Console.WriteLine("\nReceiving message from Queue...");
BrokeredMessage message = null;
NamespaceManager namespaceManager = NamespaceManager.Create();
while (true)
{
try
{
//receive messages from Queue
message = Client.Receive(TimeSpan.FromHours(10));
if (message != null)
{
Console.WriteLine(string.Format("Message received: Id = {0}", message.MessageId));
string s = new StreamReader(message.GetBody<Stream>(), Encoding.ASCII).ReadToEnd();
// load into an XML Document to s
System.Xml.XmlDocument xmldoc = new System.Xml.XmlDocument();
xmldoc.LoadXml(s);
// need to add a namespace because it doesn't have one
xmldoc.DocumentElement.SetAttribute("xmlns", "http://xrm.generic.schemas");
string mydocpath = @"C:\Projects\Test\ReceiveFromSB\";
xmldoc.Save(mydocpath + "saved.xml");
// Deserialize to an object too
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = message.Properties["EntityName"].ToString();
XmlSerializer serializer = new XmlSerializer(typeof(contact),xRoot);
StringReader rdr = new StringReader(s);
contact myContact = (contact)serializer.Deserialize(rdr);
// Now delete the message
message.Complete();

}
else
{
//no more messages in the queue
break;
}
}
catch (MessagingException me)
{
if (!me.IsTransient)
{
Console.WriteLine(me.Message);
throw;
}
else
{
// HandleTransientErrors(e);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw;
}
}
Client.Close();
}
}
 

Dynamics CRM Online: Posting Messages to the Azure Service Bus

I have to say I've never really liked the way Dynamics CRM integrates with the Azure Service Bus. I've posted on this before here and here. What I dislike is the way it posts the Context to the Service Bus.  In order to do anything with the Context I need to use the RemotePluginContext and then extract the entity that I want. That means using the XrmSDK and being familiar with the way to handle the Context. But I like a loosely coupled architecture. I would expect a CRM plugin to place a strongly typed Xml message on the Service Bus. Then processing of the message requires no knowledge of CRM. I'm a BizTalk developer and for interfaces are always about messages that comply with XML schemas because that acts as the contract.  In a development environment where you have different teams of developers with different skill sets this separation I believe is vital.

Now you may know that Dynamics CRM only supports the ACS authentication of the Service Bus and you have to create this using PowerShell commands because it is not possible via the portal. See this post. You can find the details of setting up the Service Endpoint elsewhere but the bottom line is that you are using a Guid that points to the Service Endpoint record. So what about deploying to another environment where you will use a different Service Endpoint? Well you have to go through the manual process again. What in a Production environment? Are you kidding me? No, I want to be able to deploy and then just configure the endpoint url.

The challenge in doing this online is that the plugin runs in Sandbox mode and it restricts what .Net assemblies I can use, System.Security being one of them. That rules out SAS authentication because it requires on System.Security.Cryptography.

My solution is a plugin that does two things. It populates a data table with the attributes from the entity and produces strongly typed Xml.  It then uses WebClient to POST the xml as a string to the Service Bus using the REST API. The authentication uses the ACS token and the only configuration parameters I need are the service bus namespace, the queue name, and the issuer secret. The great thing about the solution is it is totally generic, it works with any entity, all you need is to configure a plugin step. A couple of points to note.
1. I'm using a queue.
2. My plugin is registered as synchronous because I want to maintain ordered delivery
3. I'm sending to the queue synchronously, because I want to know I successfully sent it.
4. I can choose if I want to use the PreImage, PostImage or Target
5.The plugin calls my EntitySerialize class
6. I am indebted to other bloggers for much of this code

The next post shows you can retrieve the message form the queue.
using System;

using System.Collections.Specialized;
using System.Data;
using System.Linq;
using System.Text;
using System.Net;
using Microsoft.Xrm.Sdk;

public class EntityHelper
    {
        // **************Use bits from  the Service bus namespace below ******************
        //
        static string ServiceNamespace = "yournamespace";
        static string ServiceHttpAddress = "https://yournamespace.servicebus.windows.net/queuename/messages";
        const string acsHostName = "accesscontrol.windows.net";
        const string sbHostName = "servicebus.windows.net";
        const string issuerName = "owner";
        const string issuerSecret = "your_issuer_secret_goes_here";
        public static void EntitySerialize(ITracingService tracingService,  IOrganizationService service, Entity entity, string orgName, string msgName, string correlation)
        {
            try
            {
                DataSet ds = new DataSet(entity.LogicalName);
                DataTable dt = new DataTable("attributes");
                DataTable dataTable = new DataTable();
                ConvertEntityToDataTable(dt, entity);
                ds.Tables.Add(dt);
                string xml = ds.GetXml();
                PostMessageToBus(entity, orgName, msgName, correlation, xml);
            }
            catch (System.Net.WebException  we)
            {
                tracingService.Trace(we.Message);
                throw new Exception("Web Exception: " +we.Message);
            }
            catch (Exception e)
            {
                tracingService.Trace(e.Message);
                throw e;
            }
            return ;
        }
        private static void PostMessageToBus(Entity entity, string orgName, string msgName, string correlation, string xml)
        {
            WebClient webClient = new WebClient();
            webClient.Headers[HttpRequestHeader.Authorization] = GetToken();
            webClient.Headers[HttpRequestHeader.ContentType] = "application/atom+xml;type=entry;charset=utf-8";
            // Set brokered Properties this way
            webClient.Headers.Add("BrokerProperties", "{ \"MessageId\":\"123456789\", \"Label\":\"M1\"}");
            // example of the parameters that can be passed and available to receiver as mesage.Properties collection
            webClient.Headers["Correlation"] = correlation;
            webClient.Headers["OrganisationName"] = orgName;
            webClient.Headers["MessageName"] = msgName;
            webClient.Headers["EntityName"] = entity.LogicalName;
            var response = webClient.UploadData(ServiceHttpAddress, "POST", System.Text.Encoding.UTF8.GetBytes(xml));
            string responseString = Encoding.UTF8.GetString(response);
        }
        ///////
          private static void ConvertEntityToDataTable(DataTable dataTable, Entity entity)
         {
             DataRow row = dataTable.NewRow();
             foreach (var attribute in entity.Attributes.OrderBy(a=>a.Key))
             {
                 if (!dataTable.Columns.Contains(attribute.Key))
                 {
                     dataTable.Columns.Add(attribute.Key);
                 }
                 if (getAttributeValue(attribute.Value) != null)
                 {
                     row[attribute.Key] = getAttributeValue(attribute.Value).ToString();
                 }
             }
             foreach (var fv in entity.FormattedValues.OrderBy(a=>a.Key))
             {
                 if (!dataTable.Columns.Contains(fv.Key + "name"))
                 {
                     dataTable.Columns.Add(fv.Key + "name");
                 }
                 row[fv.Key + "name"] = fv.Value;
             }
             dataTable.Rows.Add(row);
         }
        ///////
         private static object getAttributeValue(object entityValue)
         {
             object output = "";
             switch (entityValue.ToString())
             {
                 case "Microsoft.Xrm.Sdk.EntityReference":
                     output = ((EntityReference)entityValue).Name;
                     break;
                 case "Microsoft.Xrm.Sdk.OptionSetValue":
                     output = ((OptionSetValue)entityValue).Value.ToString();
                     break;
                 case "Microsoft.Xrm.Sdk.Money":
                     output = ((Money)entityValue).Value.ToString();
                     break;
                 case "Microsoft.Xrm.Sdk.AliasedValue":
                     output = getAttributeValue(((Microsoft.Xrm.Sdk.AliasedValue)entityValue).Value);
                     break;
                 default:
                     output = entityValue.ToString();
                     break;
             }
             return output;
         }
         private static string GetToken()
         {
             var acsEndpoint = "https://" + ServiceNamespace + "-sb." + acsHostName + "/WRAPv0.9/";
             // Note that the realm used when requesting a token uses the HTTP scheme, even though
             // calls to the service are always issued over HTTPS
             var realm = "http://" + ServiceNamespace + "." + sbHostName + "/";
             NameValueCollection values = new NameValueCollection();
             values.Add("wrap_name", issuerName);
             values.Add("wrap_password", issuerSecret);
             values.Add("wrap_scope", realm);
             WebClient webClient = new WebClient();
             byte[] response = webClient.UploadValues(acsEndpoint, values);
             string responseString = Encoding.UTF8.GetString(response);
             var responseProperties = responseString.Split('&');
             var tokenProperty = responseProperties[0].Split('=');
             var token = Uri.UnescapeDataString(tokenProperty[1]);
             return "WRAP access_token=\"" + token + "\"";
         }

     }
}

Monday, August 31, 2015

Serialize a CRM Entity to XML in Sandbox mode Plugin

I have been struggling with this problem for a while. Previous posts have outlined the problem: how to get changes in CRM sent to Back Office systems as XML messages. When using CRM Online a big frustration is that you cannot serialize objects to XML because the WriteObject method of the serializer throws a security exception.

While you can post the Context of a plugin to the Azure Service Bus, you have to use the CRM SDK to interpret this and turn it into something useful. My previous post outlines how to do this with an Azure Worker Process.

(You could construct the XML message line by line using string builder but seriously who wants to do that?)

I found a simple solution which may solve the problem. It was this post that provided the inspiration. It describes how to turn FetchXML results into a data table.  Now FetchXML returns an entity collection and the code shows how to load that into a data table and it also handles the special CRM data types like Entity Reference. 

So there you are in a Sandbox plugin.  You have the Post-Image entity and you want to convert that into a nicely structured XML file that you can post to the Azure Service Bus.  You can use part of the code above (ignoring the FetchXML query) to convert the Post-Image to a data table.  I put it into a function I called ConvertEntityToDataTable.

Now getting XML is easy.

DataSet ds = new DataSet("Invoice");
DataTable dt = new DataTable("Attributes");
ConvertEntityToDataTable(dt, entity);
ds.Tables.Add(dt);

string xml = ds.GetXml();

The result looks like this (I've just given an extract)
<Invoice>
 <Attributes>
 <billto_city>London</billto_city>
 <billto_line1>12 Hgh Street</billto_line1>
 <billto_line2>Clapham Common</billto_line2>
 <billto_line3>Clapham</billto_line3>
 </Attributes>
</Invoice>


Remember that PostImages provide their attributes in alphabetical order which is great for mapping structured XML messages. The Target entity does not so you would need to address that.
The XML is missing some things I would need to add:
  (a) the xml declaration at the top specifying the encoding
  (b) a namespace to identify the message to an ESB

But I am delighted with the result because now I can construct proper XML messages in a Sandbox plugin. 

The two functions required are shown below
///////
         private void ConvertEntityToDataTable(DataTable dataTable, Entity entity)
        {
            DataRow row = dataTable.NewRow();
            foreach (var attribute in entity.Attributes)
            {
                if (!dataTable.Columns.Contains(attribute.Key))
                {
                    dataTable.Columns.Add(attribute.Key);
                }
                row[attribute.Key] = getAttributeValue(attribute.Value).ToString();
            }
            foreach (var fv in entity.FormattedValues)
            {
                if (!dataTable.Columns.Contains(fv.Key + "name"))
                {
                    dataTable.Columns.Add(fv.Key + "name");
                }
                row[fv.Key + "name"] = fv.Value;
            }
            dataTable.Rows.Add(row);
        }
///////
        private object getAttributeValue(object entityValue)
        {
            object output = "";
            switch (entityValue.ToString())
            {
                case "Microsoft.Xrm.Sdk.EntityReference":
                    output = ((EntityReference)entityValue).Name;
                    break;
                case "Microsoft.Xrm.Sdk.OptionSetValue":
                    output = ((OptionSetValue)entityValue).Value.ToString();
                    break;
                case "Microsoft.Xrm.Sdk.Money":
                    output = ((Money)entityValue).Value.ToString();
                    break;
                case "Microsoft.Xrm.Sdk.AliasedValue":
                    output = getAttributeValue(((Microsoft.Xrm.Sdk.AliasedValue)entityValue).Value);
                    break;
                default:
                    output = entityValue.ToString();
                    break;
            }
            return output;
        }
    }

Saturday, March 5, 2011

Connecting to CRM 2011 in the Cloud

Connecting to CRM 2011 in the Cloud is trickier than connecting to CRM 2011 On Premise.
There is a great article by Deepak Kumar but it is worth recapping on his points.
Check c:\Program Files to see if you have Windows Identity Foundation. If not you will need to install it.

You need to get some device credentials and this is done using a utility in the CRMSDK called DEVICEREGISTRATION. Go to \sdk\tools\deviceregistration directory and load deviceregistration.csproj. Compile the application and navigate to the \bin\debug folder. Open a command window and type

deviceregistration.exe /operation:Register

The Device ID and Password that are generated are used in the method GetDeviceCredentials (that comes next).

Preparation work done you can now create your VS 2010 project, in this example I am using a WCF Service (I left the default name of Service)

Copy the CRMServiceHelper.cs file from \sdk\samplecode\cs\helpercode into your project and modify three methods:

GetDeviceCredentials
GetUserLogonCredentials
GetServerConfiguration

I used the code in Deepak Kumar's blog and set my Windows LiveID credentials in the GetUserLogonCredentials method. When you set the endpoints in GetServerConfiguration be aware that endpoints vary depending where you are in the world so see this blog .

Then if you want Intellisense on your custom entities and attributes you need to generate a class with them all in using CRMSVCUTIL.EXE. This tool is in the SDK too in the \sdk\bin directory. Amend your Environment Path so you have a reference to its location. Open a Command window, navigate to where you want the class to be generated then use

CrmSvcUtil.exe /url:https://{organisation}.crm4.dynamics.com/XRMServices/2011/Organization.svc /out:GeneratedCode.cs /username:"windows live id" /password:"live id password" /deviceid:"deviceid" /devicepassword:"device password"

Add the class you just generated to your project. OK, this takes a while and you will have to keep regenerating it every time you add an attribute but think of the time you'll save by having fewer bugs.

You will need to add references to the CRM SDK dlls (microsoft.crm.sdk.proxy.dll, microsoft.xrm.sdk.dll and microsoft.crm.sdk.dll) and then add these using statements

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Crm.Sdk.Messages;


Add 3 declarations in the class Service.
private OrganizationServiceContext context;
private OrganizationServiceProxy proxy;
private IOrganizationService service;


In the class constructor add a call to ConsumeIOrganization().

public Service()
{
ConsumeIOrganization();
}
Then add two methods

public void ConsumeIOrganization()
{
ServerConnection serverConnection = new ServerConnection();
ServerConnection.Configuration serverConfig = serverConnection.GetServerConfiguration();
Connect(serverConfig);
}

public void Connect(ServerConnection.Configuration serverConfig)
{
try
{
using (proxy = new OrganizationServiceProxy(serverConfig.OrganizationUri, serverConfig.HomeRealmUri,
serverConfig.Credentials, serverConfig.DeviceCredentials))
{
proxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
context = new OrganizationServiceContext(proxy);
service = (IOrganizationService)proxy;
}
}
catch (FaultException <microsoft.xrm.sdk.organizationservicefault>)
{
throw;
}
}

Now you can add your code and reference service or context methods depending on which you prefer. Welcome to programming in the cloud!