Validating Alexa Skill Web Requests in c#

Amazon went a little OCD on the security verification for Alexa skill requests to custom https endpoints. During testing it will work fine if you don’t validate, but they check for this as part of the skill submission process.

I have been developing a custom connector for Alexa to work with the Microsoft Bot Framework, and just recently discovered the security requirements. Now I may be reinventing the wheel here a little bit, but I decided to build my own model classes. Maybe I’m a little OCD.

Anyway, to validate the alexa skill requests you must do the following:
1: Validate that the url supplied in a header for a certificate chain is valid
2: Validate the certificate and its chain
3: Use the certificate to verify the digital signature (which is supplied in another header) against the request body
4: Make sure the timestamp in the request body is within 150 seconds of now

Amazon’s instructions with regard to step 3 are a little misleading. They suggest you “decrypt” the digital signature using the public key. From what I can gather, RSA public keys do not decrypt; they only encrypt. Thankfully the digital signature validation process is something that is already implemented in the .NET Framework, so it’s not terribly difficult.

The two “tricky” parts are getting .NET to read certificates from a PEM container, and checking the signing certificate’s SAN list.

So hopefully this will be helpful to somebody.

First we have a helper class for parsing the PEM and a couple other little things

using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;

namespace AlexaCustomChannel
{
    public static class PemHelper
    {
        static string CertHeader = "-----BEGIN CERTIFICATE-----";
        static string CertFooter = "-----END CERTIFICATE-----";

        static HttpClient _client = new HttpClient();


        public static IEnumerable<string> ParseSujectAlternativeNames(X509Certificate2 cert)
        {
            Regex sanRex = new Regex(@"^DNS Name=(.*)", RegexOptions.Compiled | RegexOptions.CultureInvariant);

            var sanList = from X509Extension ext in cert.Extensions
                          where ext.Oid.FriendlyName.Equals("Subject Alternative Name", StringComparison.Ordinal)
                          let data = new AsnEncodedData(ext.Oid, ext.RawData)
                          let text = data.Format(true)
                          from line in text.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
                          let match = sanRex.Match(line)
                          where match.Success && match.Groups.Count > 0 && !string.IsNullOrEmpty(match.Groups[1].Value)
                          select match.Groups[1].Value;

            return sanList;
        }


        public static bool ValidateCertificateChain(X509Certificate2 certificate, IEnumerable<X509Certificate2> chain)
        {
            using (var verifier = new X509Chain())
            {
                verifier.ChainPolicy.ExtraStore.AddRange(chain.ToArray());
                var result = verifier.Build(certificate);
                return result;
            }
        }

        public static X509Certificate2 ParseCertificate(string base64CertificateText)
        {
            var bytes = Convert.FromBase64String(base64CertificateText);
            X509Certificate2 cert = new X509Certificate2(bytes);
            return cert;
        }

        public static async Task<X509Certificate2[]> DownloadPemCertificatesAsync(string pemUri)
        {
            var pemText = await _client.GetStringAsync(pemUri);
            if (string.IsNullOrEmpty(pemText)) return null;
            return ReadPemCertificates(pemText);
        }


        public static X509Certificate2[] ReadPemCertificates(string pemString)
        {
            var lines = pemString.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            List<string> certList = new List<string>();
            StringBuilder grouper = null;
            for (int i = 0; i < lines.Length; i++)
            {
                var curLine = lines[i];
                if (curLine.Equals(CertHeader, StringComparison.Ordinal))
                {
                    grouper = new StringBuilder();
                }
                else if (curLine.Equals(CertFooter, StringComparison.Ordinal))
                {
                    certList.Add(grouper.ToString());
                    grouper = null;
                }
                else
                {
                    if (grouper != null)
                    {
                        grouper.Append(curLine);
                    }
                }
            }

            List<X509Certificate2> collection = new List<X509Certificate2>();

            foreach (var certText in certList)
            {
                var cert = ParseCertificate(certText);
                collection.Add(cert);
            }

            return collection.ToArray();
        }
    }
}

You will need to change the signature of your controller to accept something that gives you access to the raw request body – such as an HttpRequestMessage.

Then you can call the following method with your request to validate per Amazon’s requirements
(Note the AlexaRequestBody is my custom request model. You just need to get the timestamp from the request)

        static Dictionary<string, X509Certificate2> _validatedCertificateChains = new Dictionary<string, X509Certificate2>();
//...
        async Task ValidateRequestSecurity(HttpRequestMessage httpRequest, byte[] requestBytes, AlexaRequestBody requestBody)
        {
            if (requestBody == null || requestBody.Request == null || requestBody.Request.Timestamp == null)
            {
                throw new InvalidOperationException("Alexa Request Invalid: Request Timestamp Missing");
            }

            var ts = requestBody.Request.Timestamp.Value;
            var tsDiff = (DateTimeOffset.UtcNow - ts).TotalSeconds;

            if (System.Math.Abs(tsDiff) >= 150)
            {
                throw new InvalidOperationException("Alexa Request Invalid: Request Timestamp outside valid range");
            }

            httpRequest.Headers.TryGetValues("SignatureCertChainUrl", out var certUrls);
            httpRequest.Headers.TryGetValues("Signature", out var signatures);

            var certChainUrl = certUrls.FirstOrDefault();
            var signature = signatures.FirstOrDefault();

            if (string.IsNullOrEmpty(certChainUrl))
            {
                throw new InvalidOperationException("Alexa Request Invalid: missing SignatureCertChainUrl header");
            }

            if (string.IsNullOrEmpty(signature))
            {
                throw new InvalidOperationException("Alexa Request Invalid: missing Signature header");
            }

            var uri = new Uri(certChainUrl);

            if (uri.Scheme.ToLower() != "https")
            {
                throw new InvalidOperationException("Alexa Request Invalid: SignatureCertChainUrl bad scheme");
            }

            if (uri.Port != 443)
            {
                throw new InvalidOperationException("Alexa Request Invalid: SignatureCertChainUrl bad port");
            }

            if (uri.Host.ToLower() != "s3.amazonaws.com")
            {
                throw new InvalidOperationException("Alexa Request Invalid: SignatureCertChainUrl bad host");
            }

            if (!uri.AbsolutePath.StartsWith("/echo.api/"))
            {
                throw new InvalidOperationException("Alexa Request Invalid: SignatureCertChainUrl bad path");
            }

            X509Certificate2 signingCertificate = null;

            if (!_validatedCertificateChains.ContainsKey(uri.ToString()))
            {
                System.Diagnostics.Trace.WriteLine("Validating cert URL: " + certChainUrl);

                var certList = await PemHelper.DownloadPemCertificatesAsync(uri.ToString());

                if (certList == null || certList.Length < 2)
                {
                    throw new InvalidOperationException("Alexa Request Invalid: SignatureCertChainUrl download failed or too few certificates");
                }

                var primaryCert = certList[0];

                var subjectAlternativeNameList = PemHelper.ParseSujectAlternativeNames(primaryCert);

                if (!subjectAlternativeNameList.Contains("echo-api.amazon.com"))
                {
                    throw new InvalidOperationException("Alexa Request Invalid: SignatureCertChainUrl certificate missing echo-api.amazon.com from Subject Alternative Names");
                }

                List<X509Certificate2> chainCerts = new List<X509Certificate2>();

                for (int i = 1; i < certList.Length; i++)
                {
                    chainCerts.Add(certList[i]);
                }

                if (!PemHelper.ValidateCertificateChain(primaryCert, chainCerts))
                {
                    throw new InvalidOperationException("Alexa Request Invalid: SignatureCertChainUrl certificate chain validation failed");
                }


                signingCertificate = primaryCert;

                lock (_validatedCertificateChains)
                {
                    if (!_validatedCertificateChains.ContainsKey(uri.ToString()))
                    {
                        System.Diagnostics.Trace.WriteLine("Adding validated cert url: " + uri.ToString());
                        _validatedCertificateChains[uri.ToString()] = primaryCert;
                    }
                    else
                    {
                        System.Diagnostics.Trace.WriteLine("Race condition hit while adding validated cert url: " + uri.ToString());
                    }
                }
            }
            else
            {
                signingCertificate = _validatedCertificateChains[uri.ToString()];
            }

            if (signingCertificate == null)
            {
                throw new InvalidOperationException("Alexa Request Invalid: SignatureCertChainUrl certificate generic failure");
            }


            var signatureBytes = Convert.FromBase64String(signature);

            var thing = signingCertificate.GetRSAPublicKey();
            if (!thing.VerifyData(requestBytes, signatureBytes, System.Security.Cryptography.HashAlgorithmName.SHA1, System.Security.Cryptography.RSASignaturePadding.Pkcs1))
            {
                throw new InvalidOperationException("Alexa Request Invalid: Signature verification failed");
            }
        }

Returning custom HTTP Status codes for WCF SOAP Exceptions

When WCF encounters an unhandled exception, the thrown exception is wrapped up in a FaultException and returned to the client. You can of course throw your own FaultException to have better control over the contents of the error, but one thing you cannot control by default is the HTTP response code. It is always 500.

I had a situation where I needed to return a 503 error under certain circumstances in a WCF SOAP service.

The solution is not as simple as I might like, but it isn’t terrible. There are examples out there for changing the http status code for all exceptions (https://msdn.microsoft.com/es-es/library/ee844556(v=vs.95).aspx), but this was not at all what we want.

The solution uses the same basic framework as the mentioned article however. An Endpoint Behavior Extension registers a Dispatch Message Inspector that watches for faults. In the case of a fault. Inside the BeforeSendReply method, you have access to the reply message. For performance reasons it is best to not unwrap the XML, so we use the SOAP Action header to trigger the HTTP Status code update.

I decided to create a simple custom exception class that sets the SOAP Action to a predefined value

    /// <summary>
    /// Custom Fault Exception that when used with the CustomFaultStatusBehavior Endpoint Behavior
    /// allows returning custom HTTP status codes to the client
    /// </summary>
    public class StatusFaultException : FaultException
    {
        /// <summary>
        /// Create new exception
        /// </summary>
        /// <param name="statusCode">HTTP Status code to be returned to the client</param>
        /// <param name="faultReason">SOAP Fault Reason</param>
        /// <param name="faultCode">SOAP Fault Code</param>
        public StatusFaultException(System.Net.HttpStatusCode statusCode, string faultReason, FaultCode faultCode)
            :base(faultReason, faultCode, "CustomFaultStatus" + ((int)statusCode).ToString())
        {
            //StatusCode is placed in the response Action. Action would be "CustomFaultStatus503" to return a 503 error code
        }
    }

Here is the Custom Behavior that consumes the SOAP Action

    /// <summary>
    /// Endpoint Behavior that allows returning custom HTTP response codes for SOAP Faults 
    /// </summary>
    public class CustomFaultStatusBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        //based on https://msdn.microsoft.com/es-es/library/ee844556(v=vs.95).aspx
        public override Type BehaviorType
        {
            get
            {
                return typeof(CustomFaultStatusBehavior);
            }
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            CustomFaultStatusMessageInspector inspector = new CustomFaultStatusMessageInspector();
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        protected override object CreateBehavior()
        {
            return new CustomFaultStatusBehavior();
        }
    }

    /// <summary>
    /// Message Inspector that updates the HTTP response code for faulted messages with a CustomFaultStatus action
    /// </summary>
    public class CustomFaultStatusMessageInspector : IDispatchMessageInspector
    {
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            if (!reply.IsFault) return;
            if (!reply.Headers.Action.StartsWith("CustomFaultStatus", StringComparison.Ordinal)) return;
            //get the string value for desired response code
            string statusCodeString = reply.Headers.Action.Substring(17);
            //convert to int
            int statusCodeInt;
            if (!int.TryParse(statusCodeString, out statusCodeInt)) return;

            //cast to HttpStatusCode
            System.Net.HttpStatusCode statusCode = System.Net.HttpStatusCode.InternalServerError;
            try
            {
                statusCode = (System.Net.HttpStatusCode)statusCodeInt;
            }
            catch (Exception ex)
            {
                return;
            }

            // Here the response code is changed
            reply.Properties[HttpResponseMessageProperty.Name] = new HttpResponseMessageProperty() { StatusCode = statusCode };
        }
    }

The CustomFaultStatusBehavior must be registered in your web.config as a behavior extension, then it must be referenced in an endpoint behavior. Finally this behavior should be applied to the endpoint using the behaviorConfiguration attribute.

Free Tool: Dynamics CRM User Language Update Tool

Just had an encounter with a user who needed their language updated. The UI Language was set to Korean – I wasn’t able to walk the user through updating it since I don’t read Korean either.

So I whipped up a little tool to do the job for me.
userupdatetool

You supply a connection string for the target CRM Organization (Should support everything – CRM Online, On-Premise – just paste everything in the CRM url up to (but not including) the “/main.aspx”)

You can search for the user by Guid, Full Name or DomainName (aka User Name) (all the search fields auto add wildcards)

The tool returns a list of matching users – select the user to update, select the desired values for language code and click Update. Pretty basic and there are instructions for doing it, but I didn’t see anything so simple out there.

You can get the binaries here: Dynamics CRM User Language Update Tool

And the source code here: Dynamics CRM User Language Update Tool Source Code

(Note that you may need to have Windows Identity Foundation installed – this is a prerequisite of the CRM SDK)

This tool works with Dynamics CRM 2011, Dynamics CRM 2013, Dynamics CRM 2015, Dynamics CRM Online, and the forthcoming Dynamics CRM 2016 (ie everything 2011 and later)

Get the SQL Server and database name from Dynamics CRM

As far as I can tell, there is no way to retrieve the SQL Server name using the Organization Service. If you have the Organization Service URL, there really is no good way to get the database connection string.

(You can use the deployment service to get it, but that only works if you happen to be a deployment administrator)

No good way… but there is a way. When you download a dynamic spreadsheet, the database connection string is embedded in the xml. And you can programmatically download that spreadsheet using an entity that all CRM orgs have.

Using some fiddler experimentation, I was able to make a .NET method that will extract the database connection string, given the organization base url (https://crmserver/orgnam)

        /// <summary>
        /// Given a Dynamics CRM Org URL, retrieve the Database Connection string
        /// </summary>
        /// <param name="crmOrgUrlBase"></param>
        /// <returns></returns>
        private static string GetCrmDatabaseConnectionString(string crmOrgUrlBase)
        {
            string cleanurl = crmOrgUrlBase.ToLowerInvariant().Trim().Replace("/xrmservices/2011/organization.svc", "");
            {
                int mainPos = cleanurl.IndexOf("/main.aspx");
                if (mainPos > 0)
                {
                    cleanurl = cleanurl.Substring(0, mainPos);
                }
            }

            string requestPayload = @"xdpi=96&exportType=list&useSqlQuery=1&fetchXml=%3Cfetch+distinct%3D%22false%22+no-lock%3D%22false%22+mapping%3D%22logical%22+page%3D%221%22+count%3D%2250%22+returntotalrecordcount%3D%22true%22%3E%3Centity+name%3D%22systemuser%22%3E%3Cattribute+name%3D%22systemuserid%22%2F%3E%3Cattribute+name%3D%22fullname%22%2F%3E%3Cattribute+name%3D%22fullname%22%2F%3E%3Corder+attribute%3D%22fullname%22+descending%3D%22false%22%2F%3E%3C%2Fentity%3E%3C%2Ffetch%3E%0D%0A&layoutXml=%3Cgrid+name%3D%22excelGrid%22+select%3D%220%22+icon%3D%220%22+preview%3D%220%22%3E%3Crow+name%3D%22result%22+id%3D%22systemuserid%22%3E%3Ccell+name%3D%22fullname%22+width%3D%22100%22%2F%3E%3C%2Frow%3E%3C%2Fgrid%3E%0D%0A";
            string url = cleanurl + "/_grid/print/export_live.aspx";

            string response = null;


            using (var wc = new System.Net.WebClient())
            {
                wc.UseDefaultCredentials = true;
                wc.Headers.Add("Accept-Encoding", "gzip, deflate");
                wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
                wc.Headers.Add("DNT", "1");
                response = wc.UploadString(url, requestPayload);
            }


            var xe = System.Xml.Linq.XElement.Parse(response);


            System.Xml.Linq.XNamespace nn = "urn:schemas-microsoft-com:office:spreadsheet";
            System.Xml.Linq.XNamespace nn2 = "urn:schemas-microsoft-com:office:excel";


            var connectionEl = xe.Element(nn + "Worksheet").Element(nn2 + "QueryTable").Element(nn2 + "QuerySource").Element(nn2 + "Connection");

            string rawString = connectionEl.Value;

            Regex rex = new Regex("SERVER=([^;]+);DATABASE=([^;]+)", RegexOptions.Compiled);

            var m = rex.Match(rawString);

            string server = m.Groups[1].Value;
            string database = m.Groups[2].Value;

            return "DATA SOURCE=" + server +";INITIAL CATALOG=" + database + ";Integrated Security=SSPI";
        }

It is technically unsupported to access export_live.aspx directly, so this may stop working in future releases of CRM.
I have tested this on CRM 2011 UR 11 and UR 17.

Dirt simple method of limiting System.Threading.Task concurrency (max concurrent threads)

Microsoft has an article about creating a Task Scheduler that limits maximum concurrency:
How to: Create a Task Scheduler That Limits the Degree of Concurrency

The provided example class is clunky and kinda hard to grasp. I’ve been searching for a simple way to create a “thread pool” with limited concurrency. I previously experimented with ConcurrentBags of BackgroundWorkers – this worked, but it was also fairy complex and cumbersome.

The solution I came up with uses a Semaphore (or rather the lighter weight SemaphoreSlim type) to manage concurrency.

Simply put – you create a semaphore with a maxCount (and initialCount) equal to the max concurrency you desire. Then when you want to fire off a Task, first call semaphore.Wait(), then call semaphore.Relase() in a ContinueWith().

Contrived example:

    public class LimitedAsync
    {
        private SemaphoreSlim _semaphore;

        public LimitedAsync(int maxConcurrency)
        {
            // Create semaphore with maxConcurrency slots
            _semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
        }

        public void DoSomethingAsync(string param)
        {
            //Wait for semaphore to have availablilty (blocks if semaphore is full)
            _semaphore.Wait();
            //Run DoSomething in a task, then release slot in semaphore
            //ContinueWith is called even if DoSomething faults
            Task.Factory.StartNew(() => DoSomething(param)).ContinueWith((x) => _semaphore.Release());
        }

        public void DoSomething(string param)
        {
            System.Threading.Thread.Sleep(500);
        }
    }

Generating a single flattened WSDL from an existing WCF Service

UPDATE 4/12/2013: Tool included
Download WSDL Flattener Tool
I’m not including source because it’s messy (I prefer to cleanup my code before sharing…) but feel free to decompile with ILSpy if you don’t trust me.

UPDATE 8/11/2015: Command line tool included
WSDL Flattener Command Line Utility
Somehow I forgot to save my update – thanks Luke for pointing it out!

WCF is great – its flexible, standards compliant, super easy to work with, and just altogether nice. That is, until you try to use it with, say, Oracle products. WCF generates so-called modular wsdl files, with separate xsd files for types, and even sometimes multiple wsdl files. Not technically in violation of any standards, but kind-of pushing the limits.

There are plenty of articles out there about modifying a WCF service to generate a flat WSDL. Also if you upgrade to .NET 4.5, you can use the URL parameter ?singleWsdl to get a flattened WSDL.

But what if you don’t have control of the web service? What if you cannot upgrade or modify the code?

For some odd reason, Microsoft added the internal ability to generate a single WSDL but only exposed it through the WCF Service implementation. They didn’t add it to svcutil.exe, and they didn’t expose it in the Framework… publicly that is *evil grin*

Turns out, in the System.ServiceModel.Description namespace, there is an internal static class called “WsdlHelper”, and this class has a public static Method called “GetSingleWsdl” that takes a MetadataSet for a parameter, and returns a System.Web.Services.Description.ServiceDescription object representing the Wsdl as a single entity. All you have to do is call .Write(filename) and you have your file.

“But I don’t see any WsdlHelper class!!!!!!”

It’s internal – you have to use reflection to invoke it.
First, you have to load the metadata from the existing WCF service into a MetadataSet using a MetadataExchangeClient. (Hint: you will probably need to use a custom binding). Then you run that set into something like this:

            var asy = System.Reflection.Assembly.GetAssembly(typeof(WsdlExporter));
            Type t = asy.GetType("System.ServiceModel.Description.WsdlHelper", true);
            var method = t.GetMethod("GetSingleWsdl", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

            object retVal = method.Invoke(null, new object[] { metadataSet });
            //cast retVal into System.Web.Services.Description.ServiceDescription, and call the .Write() method. All done

Using this, I was able to create a tool that takes a WCF url, and allows you to save a single .wsdl file. You want the tool? Later perhaps. For now put your thinking cap and Google gloves on, and write your own.

Windows 8 Store Application Development: AppBar WTF

I have a great idea for an app that I intend to submit to the Windows 8 store. I am a fairly proficient .NET developer, and I already have a nice little class library all ready to go, I just need a “Modern” UI. This shouldn’t be too hard, right?

I create my project, and start adding controls. I realize I will need that settings thing that pops up when you swipe up from the bottom of the screen. But it isn’t called “settings” – that is something different. Instead it is called the “AppBar”. Now given that I am using a tool called VISUAL Studio, the primary innovation being the ability to rapidly, visually design user interfaces, then write code for interaction. So I figure adding an AppBar should be as simple as adding a menubar was in windows forms, right?

Wrong. Dead Wrong.

An AppBar is like an iFrame. You can put whatever you want in it, and it is completely NON-obvious, and non-intuitive to do so in a meaningful fashion. The Microsoft Quick Start guide only tells you how to do it with some XAML code that they neglect to tell you where to place. Want to follow along? Here is the article: http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh781232

 

Continue reading “Windows 8 Store Application Development: AppBar WTF”

Dynamics CRM 4.0 truly calculated fields

Microsoft has a real gem with Dynamics CRM. It is both an application and a platform, highly customizable without writing a line of code, and once you throw in the code you can do most anything (depending on how unsupported you want to go)

Internally, CRM has a number of calculated fields. The customer lookup, for example is defined in the database as COALESCE(accountid, contactid). If you wanted to be really bold, I supposed you could create a column then modify it to be a function within the database, but that would be an extremely bad idea. The correct way is to use plugins and / or javascript.

Form JavaScript is fine when you are only considered with maintaining the value when a use opens the form, but what if they print? Or use the field in a view? Or another application uses the web services to retrieve the value?

You need a plugin, and that plugin has a lot of work to do.

I recently created a plugin that permits the creation of lookups that target multiple entities. After finally handling (or chosing not to handle) all the contingent scenarios I learned a few things.

1. Your plugin has to handle the Execute message, as this is the message used for FetchXml. You will have to parse the fetchxml, determine if your entity is in it, determine if your column is in it (and you have to recurse through the link-entities).
You can either register on the post stage and perform your own retrievals to get the data for the calculation, or you can sit on the pre and post stages and modify the fetchxml in the pre stage to include the extra columns needed for your calculated field. This is MUCH MUCH faster. You will need to determine what the ResultXml looks like based on how the fetch is designed.
2. Despite what the Microsoft Documentation suggests, you do not have to register the ReteieveMultiple message for all entities – just the entity in question.
(Unless you intend to handle your calculated field being used in a filter condition. This could get extremely complicated and it is better to just make the field non-searchable)
3. You should make sure your calculated field is configured as non-searchable. This prevents it being used as a filter condition from the UI (advanced find and the like)
4. The same performance recommendation holds for the Retrieve message – register pre and post, modify the ColumnSet to contain the fields needed for the calculation, then modify the result set.
5. CRM doesn’t care if extra columns show up in the result set.
6. If your field is editable (the calculation is two-way) you will need to handle the create and update, filtered for your calculated field. Add the changes to the underlying fields to the InputParameters Target propertybag.

A couple notes about CRM 2011:
I haven’t found explicit documentation of this fact, but Fetch requests no longer use the Execute message – they are instead RetrieveMultiple.
There are two reasons I have inferred for this:
1: It is now possible because the QueryExpression in CRM 2011 allows LinkEntities to have a ColumnSet – thus providing feature parity between Queries and Fetch.
2: Plugins generally now execute within the CRM transaction, so most likely the FetchXml is parsed into a QueryExpression before the plugin is fired.
This is good and bad – it reduces the number of messages you have to listen for, but it means you do have to hook the global RetrieveMultiple.

Since CRM 4.0 is pretty dated I’m guessing there isn’t too much demand for a multilookup akin to the Customer lookups – but I have one, and I have managed to retool the basic functionality to work in CRM 2011 as well. (Who knows, it may end up as a solution!)

Changing a Word Mail Merge Field in C#

The mail merge fields in Microsoft Word are actually just a specific type of the more general Fields. Fields can be used to make fill-in forms, autogenerated text, dates, etc… They typically are displayed surrounded by funny looking square brackets, like <<first_name>>.

Inside the Word Xml, the Field and the display value are stored separately – the field looks like “ MERGEFIELD User_Title “, then in a separate range, the display of “«User_Title»” is stored.

Using the Word Object Model, Document.MailMerge.Fields contains MailMergeField objects for each instance of a merge field. The .Code member contains the range object, of which the .Text member will contain the field code (aka “ MERGEFIELD User_Title “). If you do a replace on this text field, it will update the merge field, but the display will remain the same.

You next have to call Document.Fields.Update() to get the display matching.

Here is some code:

        private void RenameMergeField(Word.Document doc, string oldName, string newName)
        {
            foreach (Word.MailMergeField field in doc.MailMerge.Fields)
            {
                if (field.Code.Text.Contains(oldName))
                {
                    field.Code.Text = field.Code.Text.Replace(oldName, newName);
                }
            }
            doc.Fields.Update();
        }

Handling Invalid Enum values in a DataContractSerializer

This all started when I was trying to serialize a CodeDom graph (actually a CodeNamespace object) using a DataContractSerializer. I hit the dreaded “Enum value ‘20482’ is invalid for type…”

image

(Note: this error was the showstopper after many other errors)

The problem: System.CodeDom.MemberAttributes should have the [Flags] attribute, but it does not. the value of 20482 is the bitwise OR of MemberAttributes.Final (2) and MemberAttributes.Private (20480). One solution was to go through all object in the CodeDom tree with attributes and force it to a single value – this is not really acceptable. Unfortunately, since we are dealing with a .NET Framework type, there is really no way to fix the type itself.

The solution: Fix the serializer… Read on for details.

Continue reading “Handling Invalid Enum values in a DataContractSerializer”