Call External Cross Domain REST API/SOAP Service from CRM Plugins/Custom Workflows.

In Dynamics 365, if you have any requirement to call cross domain REST API/SOAP UI, we can’t directly call from JavaScript, we need to create a custom workflow and create CRM action on top of it. These CRM Actions can be called from anywhere.

For REST API, it is very simple, crate a custom workflow with following code, and call this custom workflow from any CRM Action. This can be used in Plugins also.

 public string CallRESTAPIService(string restAPIURL, string serviceCredentials)
        {
            string jsonResponse = string.Empty;
            var uri = new Uri(restAPIURL);
            var request = WebRequest.Create(uri);
            request.Method = WebRequestMethods.Http.Get;
            request.ContentType = "application/json";
            request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(serviceCredentials));
            try
            {
                using (var response = request.GetResponse())
                {
                    using (var reader = new StreamReader(response.GetResponseStream()))
                    {
                        jsonResponse = reader.ReadToEnd();
                    }
                }
            }
            catch (System.Exception ex)
            {
                throw ex;
            }
            return jsonResponse;
        }

For SOAP Service, if you have requirement to call within plugin it is simple. If you want to call from JavaScript, you have to convert request and response into JSON format. First you have to convert JSON Request into C# Object and then C# Object Response to JSON Response. There are two options;
• Newtonsoft.Json Nuget library. If you are using, you need to always merge your custom workflow/plugin DLL
• DataContractJsonSerializer- System.Runtime.Serialization assembly should be added to use this. No need to merge the DLL.

 public SOAPServiceRequest DeSerializeJSONRequestToObject(string requestJSON)
        {
            SOAPServiceRequest deserializedRequest = new SOAPServiceRequest();
            MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(requestJSON));
            DataContractJsonSerializer ser = new DataContractJsonSerializer(deserializedRequest.GetType());
            deserializedRequest = ser.ReadObject(ms) as SOAPServiceRequest;
            ms.Close();
            return deserializedRequest;
        }

        public string SerializeResponseObjectToJSON(SOAPServiceResponse reponse)
        {
            string strJSON = string.Empty;
            using (MemoryStream SerializememoryStream = new MemoryStream())
            {
                DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(SOAPServiceResponse));
                serializer.WriteObject(SerializememoryStream, reponse);
                SerializememoryStream.Position = 0;
                StreamReader sr = new StreamReader(SerializememoryStream);
                strJSON = sr.ReadToEnd();
            }
            return strJSON;
        }

        public SOAPServiceResponse CallAddressValidation(ITracingService tracingService, string serviceURL, string serviceUserName, string servicePassword, string crmUserName, string addressLine, string city, string state, string postalCode)
        {
            BasicHttpsBinding binding = new BasicHttpsBinding();
            EndpointAddress address = new EndpointAddress(serviceURL);
            ServiceSoapClient soapClient = new ServiceSoapClient(binding, address);
            soapClient.ClientCredentials.UserName.UserName = serviceUserName;
            soapClient.ClientCredentials.UserName.Password = servicePassword;
            SOAPServiceRequest requestType = new SOAPServiceRequest();    
            using (OperationContextScope scope = new OperationContextScope(soapClient.InnerChannel))
            {
                HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
                httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
                Convert.ToBase64String(Encoding.ASCII.GetBytes(soapClient.ClientCredentials.UserName.UserName + ":" +
                soapClient.ClientCredentials.UserName.Password));
                OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
               //
               //Pass request data
               //
               var serviceResponse = soapClient.ValidateService(requestType);
                return serviceResponse;
            }            
        }
Advertisements

Validate Opportunity CloseDate when it is closed as won or lost in Dynamics CRM 2011

Create a Plugin step with following details
Message: Create
Primary Entity: opportunityclose
Execution Stage: Pre-operation
Execution Mode: Synchronous


// Validate Opportunity CloseDate for preventing Opportunity closed as won or lost in the future.
 public void Execute(IServiceProvider serviceProvider)
        {
            try
            {
                IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

                if (context.Stage == 20) // Pre-Stage
                {
                    if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
                    {
                        Entity entity = (Entity)context.InputParameters["Target"];

                        if (entity.Attributes.Contains("actualend") && entity.Attributes["actualend"] != null)
                        {
                            DateTime closedOn = entity.GetAttributeValue("actualend");

                            if (closedOn.Date > DateTime.Now.Date)
                            {
                                throw new InvalidPluginExecutionException(OperationStatus.Canceled, "Close Date cannot be in the future");
                            }
                        }
                    }
                }
            }
            catch (InvalidPluginExecutionException ex)
            {
                throw ex;
            }
        }

We can also use following JavsScript code to validate Opportunity CloseDate when it is closed as Won or Lost. Call ValidateOpportunityCloseDate() in the OnSave method. In fact, you can use this code to validate other fields(Status Reason, Actual Revenue, Close Date, Competitor) on Close Opportunity dialog.


function ValidateOpportunityCloseDate() {
    var arry = GetCloseOpportunityInformation();
    if (arry[3] != undefined && arry[3] != null) {
        var today = new Date();
        var formattedDate = ('' + arry[3]).replace(/-/g, "/").replace(/[TZ]/g, " ");
        var actualend = new Date(formattedDate);
        if (actualend > today) {
            alert("Close Date cannot be in the future");
            AbortSave();
        }
    }
}
function GetCloseOpportunityInformation() {
    var arr = new Array();
    var arrFields = new Array();
    arrFields[0] = 'statecode';  //1 = Won, 2 = Lost    
    arrFields[1] = 'statuscode'; //Status Reason   
    arrFields[2] = 'actualrevenue'; //Actual Revenue   
    arrFields[3] = 'actualend';  //Close Date   
    arrFields[4] = 'competitorid'; //Competitor 
    arrFields[5] = 'description'; //Description  
    var xml = crmFormSubmit.crActivityXml.value;
    var XmlDoc = new ActiveXObject("Microsoft.XMLDOM");
    XmlDoc.async = false;
    XmlDoc.loadXML(xml);
    for (var i = 0; i < arrFields.length; i++) {
        //get close out information     
        if (i > 1) {
            var xmlnode = XmlDoc.selectSingleNode("//opportunityclose/" + arrFields[i]);
            if (xmlnode != null) {
                arr[i] = xmlnode.nodeTypedValue;
            } else {

                arr[i] = "";
            }
        }
    }
    return arr;
}
function AbortSave() {
    event.returnValue = false;
    return false;
}

Log error details from the plugins in Dynamics CRM 2011

Create a custom entity to save all exception details occurs in Plugins and other CRM related applications.

Entity Name: ls_errordetails

Attributes: Name, Exception Type, Exception Details, Exception Message

Call below method LogErrorInCRM() in your catch blocks to save exception details.

        // This method is used to log error details into CRM entity(ls_errordetails)
        private void LogErrorInCRM(IOrganizationService service, Exception ex)
        {
            try
            {
                Entity ls_errorDetails = new Entity("ls_errordetails");

                ls_errorDetails["ls_name"] = Assembly.GetExecutingAssembly().GetName().Name;

                ls_errorDetails["ls_exceptiontype"] = ex.GetType().Name;

                if (ex.InnerException != null)
                    ls_errorDetails["ls_exceptiondetails"] = ex.StackTrace + "\n" + ex.InnerException.ToString();
                else
                    ls_errorDetails["ls_exceptiondetails"] = ex.StackTrace;

                ls_errorDetails["ls_exceptionmessage"] = ex.Message;

                Guid errorID = service.Create(ls_errorDetails);

                if (errorID == Guid.Empty)
                {
                    throw new InvalidPluginExecutionException("Unable to save Error details in CRM " + ex.Message, ex);
                }
            }
            catch (Exception excn)
            {
                //Log error details into text file,if there is any exception while saving error details into CRM
                LogErrorInTextFile(ex);
                LogErrorInTextFile(excn);
            }
        }

        // This method is used to log error details into Text file
        private static bool LogErrorInTextFile(Exception objException)
        {
            string strException = string.Empty;
            StreamWriter sw;
            string strPathName;
            bool bReturn = false;
            try
            {
                strPathName = GetLogFilePath();

                sw = new StreamWriter(strPathName, true);
                sw.WriteLine("Source        : " + objException.Source.ToString().Trim());
                sw.WriteLine("Method        : " + objException.TargetSite.Name.ToString());
                sw.WriteLine("Date        : " + DateTime.Now.ToLongTimeString());
                sw.WriteLine("Time        : " + DateTime.Now.ToShortDateString());
                sw.WriteLine("Computer    : " + Dns.GetHostName().ToString());
                sw.WriteLine("Error        : " + objException.Message.ToString().Trim());
                sw.WriteLine("Stack Trace    : " + objException.StackTrace.ToString().Trim());
                sw.WriteLine("^^-------------------------------------------------------------------^^");
                sw.Flush();
                sw.Close();
                bReturn = true;
            }
            catch (Exception)
            {
                bReturn = false;
            }
            return bReturn;
        }

        private static string GetLogFilePath()
        {
            String strLogFilePath = "C:\\ErrorLog\\PluginErrorLog.txt";
            try
            {
                // if exists, return the path
                if (File.Exists(strLogFilePath) == true)
                    return strLogFilePath;
                //create a text file
                else
                {
                    if (CheckDirectory(strLogFilePath) == false)
                        return string.Empty;

                    FileStream fs = new FileStream(strLogFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
                    fs.Close();
                }

                return strLogFilePath;
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }

        private static bool CheckDirectory(string strLogPath)
        {
            try
            {
                int nFindSlashPos = strLogPath.Trim().LastIndexOf("\\");
                string strDirectoryname = strLogPath.Trim().Substring(0, nFindSlashPos);

                if (false == Directory.Exists(strDirectoryname))
                    Directory.CreateDirectory(strDirectoryname);
                return true;
            }
            catch (Exception)
            {
                return false;

            }
        }

Call WCF service from plugin in Dynamics CRM 2011

Create a custom entity to maintain all configuration values, In my case I created entity like below and added caseservice details(Name: CaseServiceURL, Column: CaseService, Value: http://localhost/CaseService/Case.svc)

Entity Name: ls_settings

Attributes: Name, Column, Value

After you have created a service, generate the service proxy file using svcutil.exe tool from visual studio command line.

Svcutil.exe http://localhost/CaseService/Case.svc?wsdl

This will generate a configuration file and a code file that contains the client class. Add proxy code file to your plugin project and use the generated client class to call the Service.

Here CaseProxy is ServiceProxy class, ICase is Service interface.

        // send CaseId to other application
        private void SendCase(IOrganizationService service, Guid caseId)
        {
            //Use same binding used in WCF service config
            var binding = new BasicHttpBinding();

            //Get Case service URL
            string endPointAddress = GetEndpointAddress(service);

            if (!string.IsNullOrEmpty(endPointAddress))
            {
                //specify EndpointURL
                var endPoint = new EndpointAddress(endPointAddress);

                //ChannelFactory to specify binding and EndpointURL

                //Here CaseProxy is my ServiceProxy class, ICase is my service interface
                var channelFactory = new ChannelFactory<CaseProxy.ICase>(binding, endPoint);

                //create instance of CaseService
                CaseProxy.ICase caseClient = null;

                try
                {
                    //Create a channel
                    caseClient = channelFactory.CreateChannel();

                    //Call Service Method to send the case
                    caseClient.SendCase(caseId);

                    //Close WCF service
                    ((ICommunicationObject)caseClient).Close();
                }
                catch (Exception ex)
                {
                    if (caseClient != null)
                    {
                        ((ICommunicationObject)caseClient).Abort();
                    }
                    throw new Exception(string.Format("Error occured while sending case request to other application: {0}", ex.Message), ex);
                }
            }
        }

        // Get Case service URL from Settings entity
        private string GetEndpointAddress(IOrganizationService service)
        {
            string endPointAddress = string.Empty;

            string fetchXML = "<fetch version='1.0' output-format='xml-platform' mapping='logical' no-lock='true' distinct='false'>
                                                    <entity name='ls_settings'>
                                                        <attribute name='ls_value' />
                                                        <filter type='and'>
                                                            <condition attribute='ls_column' operator='eq' value='CaseServiceURL'/>
                                                        </filter>
                                                    </entity>
                                                </fetch>";

            var req = new FetchExpression(fetchXML);

            EntityCollection results = service.RetrieveMultiple(req);

            if (results.Entities.Count > 0)
            {
                endPointAddress = results.Entities[0].GetAttributeValue<string>("value");
            }
            return endPointAddress;
        }