Tuesday, January 28, 2014

Change Date Format in Dynamics CRM 2013

Surprisingly I could not find a blog that told me how to change the date format in Dynamics CRM 2013. 

There are plenty of blogs which say how to di it for CRM 2011 but I already knew that.  So what has happened to the Personal Options that was accessible from the File menu?

You will find it in the top right hand corner as the little settings wheel.  that will give you access to the Options and the About box.  Once you have the Personal Options dialog open, then you change the date as you did before.  Go to the Formats tab, and change the current format to whatever you want. 

Sunday, January 26, 2014

Load a Word document into Internet Explorer and set Response.ContentType

About two years ago I wrote some code to construct a PDF document on the fly and display it in the browser. Yesterday I was trying to do the same thing for a Word document. If you've seen earlier posts this month you will see I am using Aspose Words for .Net to perform a mail merge.  I wanted the ability to preview the result before saving the output. 
 
Now when I did this previously for a PDF file I had a web service that returned a memory stream and I was able to load that into the Response.OutputStream object without difficulty. But I immediately ran into a problem with my web service which is built with .Net FW 4.0.  The memory stream I returned became a marshalled object which does not have the same properties for WriteTo() or ToArray() which meant I could not easily load it into Response.OutputStream.
 
I suppose I could have found a solution but instead I thought I would return it as a string instead. Alas that gave a new set of problems which I suspect was down to encoding when converting between the stream and the string.  This morning I tried converting to a base base64 string and that did the trick. 
 
So firstly here is the code in the web service that converts the memory stream to base64.


public string PreviewMerge(DataMergeRequest req)
{   // code to do mailmerge goes here
   MemoryStream msRawData = merge.MergeDataSet(ds, templatelocation);

   string base64;
   // ENCODE TO BASE 64
   base64 = Convert.ToBase64String(msRawData.GetBuffer(), 0, (int)msRawData.Length);

    return base64;
}

On the ASPX page you need to remove everything below the Page directive.
In the Page_Load event you need this code. Note you should NOT use Response.End - there is a known issue with it creating a Threading exception.  Use HttpContext.Current.ApplicationInstance.CompleteRequest() instead. 


Response.Clear();
Response.ClearContent();
Response.ClearHeaders();
Response.Charset = "";

// get results of merge as base 64 encoded stream
string strBase64 = ds.PreviewMerge(req);

// DECODE into memory stream
byte[] raw = Convert.FromBase64String(strBase64);

using (MemoryStream decoded = new MemoryStream(raw))
{
   // load the stream into the Response Output stream
   decoded.WriteTo(Response.OutputStream);
}
// set the content type for docx
Response.ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
Response.Flush();
HttpContext.Current.ApplicationInstance.CompleteRequest();





 

Friday, January 24, 2014

Downloading a Document Template from SharePoint

Since I've wasted a couple of hours on this I'll share my findings.  I'm creating a web service that will do a mail merge for me using Aspose Words. The document template is stored in a SharePoint document library as a content type and the merged document I want to store back into the document library. 

I tackled the upload first.  Since my web service and SharePoint will be installed on separate servers, I call the SharePoint Copy.asmx web service to upload the document. That works fine.  My next task was to use the document template from SharePoint which I need to retrieve as a memory stream. 

The preferred method is to use the GetItem method of the Copy.asmx service. No problem I thought as I already has a reference to it. 

But try as I might I could not manage to download the template.  When you install a content type as a template the path to the document is something like this

DocLib/Forms/Some Letter/SomeLetter.dotx

I suspect it was something to do with this location that it would not work.  If I tried the same code on a document in the library itself it worked fine.  I gave up in frustration and started Googling for alternatives.

I came up with this incredibly simple approach which I share below.  It simply uses the DownloadData method of the WebClient.  Since I know the absolute path of the template, it works a treat.


public MemoryStream DownloadSharePointDocument(string sourceUrl)
{
   string sharePointSiteUrl = ConfigurationManager.AppSettings["sharepointsiteurl"];

   if (!sharePointSiteUrl.EndsWith("/"))
   {
 
       sharePointSiteUrl = sharePointSiteUrl + "/";
   }
   sourceUrl = sharePointSiteUrl + sourceUrl;
 
   WebClient wc = new WebClient();
   wc.UseDefaultCredentials = true;

   byte[] response = wc.DownloadData(sourceUrl);
   MemoryStream ms = HelperMethods.MemoryStreamFromBytes(response);
   return ms;

}

Thursday, January 23, 2014

Convert FetchXML results to XML.

With the earlier versions of Dynamics CRM you could construct FetchXML queries and the results would be returned as XML.  I liked that feature but is has been deprecated in later releases. I had a need for this again recently because I wanted to get data from CRM in a format where it was easy to mail merge.  I'm a big fan of Aspose Words for .Net as I have used it several times in the past with great success. 

Aspose has the advantage of being able to design normal mail merge templates in Word so it is easy for a user to amend the template.  Aspose uses mail merge fields just as the typical mail merge template does but you just add them manually to your template rather than from a data source.

Aspose uses data tables as the data source and I wanted a way to convert the results of a FetchXML query into a data table so it was ready to mail merge.  In fact because Aspose can merge multiple data tables I need to create a dataset which I will convert to an XML string using GetXml().

A key factor was I wanted the code to be entirely ignorant of the columns in the result set.  I want to be able to select any number of columns from a CRM entity, use lookup values and pick lists and columns from related tables.  Unfortunately when querying pick lists you only get the value and not the text. You will need another method to get the text equivalent. 

I am not detailing how I do the Aspose merge in this post. It should be easy enough if you follow their samples.  Just remember for this approach to work, the field names in the data table need to be unique. 

To get started I used the CRMSVCUTL.EXE utility to generate the classes of the custom entities.

crmsvcutil /url:http://localhost:5555/Org1/XRMServices/2011/Organization.svc
/out:Xrm.cs  /namespace:Xrm  /serviceContextName:XrmServiceContext

I added the Xrm.cs to my project and references to Microsoft.Xrm.Sdk.dll and Microsoft.Xrm.Client.dll which can be found in the SDK\bin directory.  I also added System.RunTime.Serialization and System.Data.  The connection string for CRM I added to the app.config. I have tried this code on Dynamics CRM 2011 and CRM 2013 and it works fine.


using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Xrm;
 
       static IOrganizationService _service;
        static OrganizationService _orgService;
        static string FetchExpression()
        {
           
            String connectionString = ConfigurationManager.AppSettings["xrmConnectionString"];

            // Establish a connection to the organization web service using CrmConnection.
            Microsoft.Xrm.Client.CrmConnection connection = CrmConnection.Parse(connectionString);

            // Obtain an organization service proxy.
            // The using statement assures that the service proxy will be properly disposed.
            using (_orgService = new OrganizationService(connection))
            {
                string fetchquery = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
                      <entity name='contact'>
                        <attribute name='fullname' />
                        <attribute name='new_referencenumber' />
                        <attribute name='new_locationid' />
                        <attribute name='contactid' />
                        <attribute name='new_contactmechanism' />
                        <order attribute='fullname' descending='false' />
                        <filter type='and'>
                          <condition attribute='statecode' operator='eq' value='0' />
                          <condition attribute='fullname' operator='not-null' />
                        </filter>
                        <link-entity name='systemuser' from='systemuserid' to='owninguser' visible='false' link-type='outer' alias=user'>
                          <attribute name='domainname' />
                        </link-entity>
                      </entity>
                    </fetch>";

                _service = (IOrganizationService)_orgService;
                RetrieveMultipleRequest req = new RetrieveMultipleRequest();
                
                FetchExpression fetch = new FetchExpression(fetchquery);
                req.Query = fetch;
                RetrieveMultipleResponse resp = (RetrieveMultipleResponse)_service.Execute(req);
               
                // Create a dataset and datatable
                DataSet results = new DataSet("Results");
                DataTable Table1 = new DataTable("SingleRowTable");
                DataColumn column;
                DataRow workRow;


                foreach (Entity entity in resp.EntityCollection.Entities)
                {
                    //create the columns in the data table
                    foreach (KeyValuePair<String, Object> attribute in entity.Attributes)
                    {
                        column = new DataColumn();
                        switch (attribute.Value.GetType().Name)
                        {
                            case "AliasedValue":
                                column.DataType = entity.GetAttributeValue<AliasedValue>(attribute.Key).Value.GetType();
                                break;
                            case  "EntityReference":
                                column.DataType = entity.GetEntityReferenceValue<EntityReference>(attribute.Key).Name.GetType();
                                break;
                            case "OptionSetValue":
                                column.DataType = entity.GetAttributeValue<OptionSetValue>(attribute.Key).Value.GetType();
                                break;
                            default :
                                column.DataType = attribute.Value.GetType();
                                break;
                        }

                        column.ColumnName = attribute.Key;
                        Table1.Columns.Add(column);

                    }
                    // add the values to the row
                    workRow = Table1.NewRow();
                    foreach (KeyValuePair<String, Object> attribute in entity.Attributes)
                    {
                        switch (attribute.Value.GetType().Name)
                        {
                            case "AliasedValue":
                                workRow[attribute.Key] = entity.GetAttributeValue<AliasedValue>(attribute.Key).Value;
                                break;
                            case "EntityReference":
                                workRow[attribute.Key] = entity.GetEntityReferenceValue<EntityReference>(attribute.Key).Name;
                                break;
                            case "OptionSetValue":
                                workRow[attribute.Key] = entity.GetAttributeValue<OptionSetValue>(attribute.Key).Value;
                                break;
                            default:
                                workRow[attribute.Key] = attribute.Value;
                                break;
                        }

                    }
                    Table1.Rows.Add(workRow);
                    // only one row expected so exit
                    break;
                }
                results.Tables.Add(Table1);
                return results.GetXml();
            }
        }

 
The results are in a very simple XML structure and already to use for the mail merge. 

<Results>
  <SingleRowTable>
    <fullname>Adam Ant</fullname>
    <new_referencenumber>PSN-00000286</new_referencenumber>
    <new_locationid>Brighton</new_locationid>
    <contactid>2d31f962-5f44-e211-ae45-00155d010a10</contactid>
    <new_contactmechanism>100000009</new_contactmechanism>
    <user.domainname>MSCRM2011\Administrator</user.domainname>
  </SingleRowTable>
</Results>


Saturday, January 4, 2014

Securing a Web Service with Azure ACS

The Identity and Access Add-In to Visual Studio does a great job of securing a web site with a variety f mechanisms including Azure ACS.  I was bitterly disappointed to find it did not offer the same ability for a Web Service.  

In the end my salvation came by using the Sentinet Service Repository which does allow me to virtualize the web service and can include authentication with ACS.  It does so using binding configuration and this extract does the job of providing ACS authentication.  Note that you need to be using HTTPS protocol.

I've not had a chance yet to add this directly to a web service to see if it works.  My hope is that just adding it will be enough and then all I need to do is pass in the user name and password when I call the web service.  What ACS will do is produce a SAML token which will be encrypted within the SOAP message. 

 <bindings>
  <customBinding>
    <binding name="IssuedToken">
      <security authenticationMode="IssuedToken">
        <issuedTokenParameters>
          <issuerMetadata address="
https://mynamespace.accesscontrol.windows.net/v2/wstrust/mex" />
          <issuer address="
https://mynamespace.accesscontrol.windows.net/v2/wstrust/13/username" binding="ws2007HttpBinding" bindingConfiguration="AcsBinding" />
        </issuedTokenParameters>
      </security>
      <httpTransport />
    </binding>
  </customBinding>
  <ws2007HttpBinding>
    <binding name="AcsBinding">
      <security mode="TransportWithMessageCredential">
        <message clientCredentialType="UserName" negotiateServiceCredential="true" algorithmSuite="Default" establishSecurityContext="false" />
      </security>
    </binding>
  </ws2007HttpBinding>
</bindings>
   

Calling a WCF Web Service secured with Azure ACS


This is some sample code for a client console application calling a WCF web service that is secured with Windows Azure Access Control Service.  In fact all I need is to pass the username and password in the client credentials and my web service will do the authentication for me.  If I supply an incorrect password then a MessageSecurityException is created so I need to ensure I have captured that an handled it in some way.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ACE.TestClient.ACS
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                VirtualService.VirtualInterfaceClient c = new VirtualService.VirtualInterfaceClient();
                c.ClientCredentials.UserName.UserName = "MyService";
                c.ClientCredentials.UserName.Password = "Hydrogen1";

                VirtualService.Event input = new VirtualService.Event() { Date = DateTime.Now, Id = "123", Name = "Peter Rabbit" };
                string result = c.PostEvent(input);
                Console.WriteLine("Service Returned: " + result);
            }
            catch (System.ServiceModel.Security.MessageSecurityException mex)
            {
                Console.WriteLine("Failed to authenticate." );
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Calling a REST Web Service with an X509 certificate

I was recently looking at what client code I needed in order to call a REST web service that was secured with an X509 certificate.

In the first case I assume that the client device (maybe a tablet) has an X509 certificate installed.  So what code do I need to send some JSON to this REST web service and include the X509 certificate.  The simple example below works.  After that you will see the sample code for sending a user name and password with

using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;

namespace TestClientBizTalkService
{
    class Program
    {
        const string JsonPayload = "{\"ns0:Event\":{\"@xmlns:ns0\":\"
http://RESTDEMO.Event\",\"Id\":\"444\",\"Date\":\"1999-05-31\",\"Name\":\"A new event\"}}";
        static void Main(string[] args)
        {
            try
            {
                WebClientWithSslCertificate c = new WebClientWithSslCertificate();
               
                c.Headers[HttpRequestHeader.ContentType] = "application/json";
                string result = c.UploadString("
https://acesentinetpot/SelfHostedNode/BizTalkRestMutualX509", "POST", JsonPayload);
                Console.WriteLine("Service Returned: " + result);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            Console.WriteLine("Done");
            Console.ReadLine();
        }

        class WebClientWithSslCertificate : WebClient
        {
            protected override WebRequest GetWebRequest(Uri address)
            {
                HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
                request.ClientCertificates.Add(GetMyCertificate());
                return request;
            }

            private X509Certificate2 GetMyCertificate()
            {
                X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

                try
                {
                    store.Open(OpenFlags.OpenExistingOnly);
                    X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
                    X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindBySubjectName, "ClientTestCertificate", true);
                    if (fcollection.Count > 0)
                    {
                        return fcollection[0];
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                finally
                {
                    if (store != null)
                    {
                        store.Close();
                    }
                }
               
                return null;
            }
        }
       
    }
}