web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Dynamics 365 Community / Blogs / xRMCenter / Hosting Custom WCF Service ...

Hosting Custom WCF Service In Microsoft Dynamic Crm

nghieppham Profile Picture nghieppham 4,755

This post will talk about consuming WCF in client script in Microsoft Dynamic Crm. It is not new to developers, but I received many questions for WCF hosting on Crm Onpremise, hence, I will try to explain clearly .

Can we host WCF in ISV folder?

The answer is “No – definitely No”. 

You can do that in Microsoft Dynamic Crm 4.0 version but this feature has been deprecated from 2013 version.

How can we host WCF and consume it?

  • The answer is “We can do it as other technologies!!!!

Microsoft Crm Online

You can host it on Azure server and consume it.

Microsoft Crm Onpremise

  • You can host it in IIS.
  • Enable WebHttp for consuming in client script.

To prevent Cross Domain calling, you can use the same domain name of Microsoft Dynamic Crm server or simplify to add cross domain caller in your request.

Case study sample

The below sample will demonstrate for Microsoft Dynamic Crm On-premise.

For example, I need to log every thing that users interact with any form without turning on Audit.

Note: from CRM 2015 SP1, 2016 versions, you can do that without making custom WCF, just use the impersonate of Web API.

Step 1: Create new WCF , I will create 1 Data Contract as following class.

    [DataContract]
    public class FormUsageObj
    {
        [DataMember(Name = "Name")]
        public string Name { get; set; }

        [DataMember (Name = "UserId")]
        public string UserId { get; set; }

        [DataMember(Name = "EntityName")]
        public string EntityName { get; set; }

        [DataMember(Name = "RecordId")]
        public string RecordId { get; set; }

        [DataMember(Name = "RecordUrl")]
        public string RecordUrl { get; set; }

        [DataMember(Name = "UsageDateTime")]
        public string UsageDateTime { get; set; }

        [DataMember(Name = "Client")]
        public int Client { get; set; }
    }

Step 2: Define WCF custom service interface

[WebInvoke(Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "LogCrmFormUsageInCrm")]
[OperationContract]
string LogCrmFormUsageInCrm(FormUsageObj frmUsageObj );</pre>
<pre>

Step 3: Implement Wcf service interface

public string LogCrmFormUsageInCrm(FormUsageObj frmUsageObj)
{
try
{
//CrmConnect();
CrmConnectSimpfy();
if (_serviceProxy != null)
{
Entity objFormUsage = new Entity("rbei_entityformreadusage");
//Entity objAccount = new Entity("account");
//objAccount["name"] = "Nguyen Thi Diem Trinh_" + DateTime.Now.ToShortDateString();
objFormUsage["rbei_name"] = frmUsageObj.Name;
objFormUsage["rbei_entityname"] = frmUsageObj.EntityName;
objFormUsage["rbei_recordguid"] = frmUsageObj.RecordId;
objFormUsage["rbei_recurl"] = frmUsageObj.RecordUrl;
objFormUsage["rbei_usagedatetime"] = DateTime.Now;
objFormUsage["rbei_client"] = new OptionSetValue(frmUsageObj.Client);
objFormUsage["rbei_user"] = new EntityReference("systemuser", Guid.Parse(frmUsageObj.UserId));

_serviceProxy.Create(objFormUsage).ToString();

var jsonObj = new JavaScriptSerializer().Serialize(objFormUsage);
return jsonObj;
}

Guid userid = ((WhoAmIResponse)_serviceProxy.Execute(new WhoAmIRequest())).UserId;
return userid.ToString();

}
catch(FaultException ex)
{
throw ex;
}catch(Exception ex)
{
throw ex;
}
}</pre>
<pre>

Step 4: For this sample, we will create sample method to connect to Microsoft Dynamic CRM

private OrganizationServiceProxy CrmConnectSimpfy()
{
#region GET CONFIG KEYS
string authType = ConfigurationManager.AppSettings["AuthType"];
string userName = ConfigurationManager.AppSettings["userName"];
string passWord = ConfigurationManager.AppSettings["password"];
string domainName = ConfigurationManager.AppSettings["domainName"];
string crmServerAddress = ConfigurationManager.AppSettings["serverName"];
string OrgName = ConfigurationManager.AppSettings["orgName"];
bool ssl = bool.Parse(ConfigurationManager.AppSettings["useSSL"]);
//string strCrmConnectionString = ConfigurationManager.ConnectionStrings["CrmServer"].ConnectionString;
#endregion
try
{ string strUri = string.Empty;
//const string strSvv = "XRMServices/2011/Organization.svc";
if (ssl)
strUri = string.Format("https://{0}/{1}/XRMServices/2011/Organization.svc", crmServerAddress, OrgName);
else
strUri = string.Format("http://{0}/{1}/XRMServices/2011/Organization.svc", crmServerAddress, OrgName);

Uri orgUri = new Uri(strUri);

ClientCredentials cred = new ClientCredentials();
cred.Windows.ClientCredential = new System.Net.NetworkCredential(userName.Decrypt(), passWord.Decrypt(), domainName.Decrypt());

_serviceProxy = new OrganizationServiceProxy(orgUri, null, cred, null);
//_serviceProxy.Authenticate();
_serviceProxy.EnableProxyTypes();

return _serviceProxy;
} catch (FaultException ex)
{
throw ex;
} catch (Exception ex)
{
throw ex;
}
}</pre>
<pre>

The config you can define in webconfig of your custom wcf

 <appSettings>
<add key="AuthType" value="AD"/>
<add key="userName" value="gPRK8kmYMVmi4tjfPbbBGQ=="/>
<add key="password" value="1tIxemFSBME3gyovCMkbOg=="/>
<add key="orgName" value="Your Org"/>
<add key="serverName" value="Your Server Include Port"/>
<add key="port" value="No Need"/>
<add key="domainName" value="LMAoocR24PEOikvocpDOhw=="/>
<add key="useSSL" value="true"/>
</appSettings>
After building it. You can publish it and deploy to IIS. It depend on your system, you can deploy it on Https or Http.
I just use the rest protocol to call in javascripy. The follow config is about to do it. You can change to be suitable for your requirement.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="AuthType" value="AD"/>
<add key="userName" value="t5roioaortSfMMDfMFI/vQ=="/>
<add key="password" value="PB8Wgh4rMV3EKsEqA3aheg=="/>
<add key="orgName" value="PACRMQUAL"/>
<add key="serverName" value="Your Crm Include Port"/>
<add key="port" value="No Need"/>
<add key="domainName" value="okcy4YKNvWLuHhLmKVkMpg=="/>
<add key="useSSL" value="true"/>
</appSettings>

<connectionStrings>
<add name="CrmServer" connectionString="AuthType={0};Url={1}; Domain={2}; Username={3}; Password={4}"/>
</connectionStrings>
<!-- <configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="PACRM.QCT.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections> -->
<system.web>
<compilation targetFramework="4.0" debug="true" />
<sessionState mode="Off" />
<identity impersonate="true" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
<handlers>
<remove name="ASPClassic" />
<add name="ASPClassic" path="*.asp" verb="*" modules="IsapiModule" scriptProcessor="%windir%\system32\inetsrv\asp.dll" resourceType="File" requireAccess="Script" />
</handlers>
</system.webServer>
<system.serviceModel>
<protocolMapping>
<add scheme="http" binding="webHttpBinding" />
</protocolMapping>

<bindings>
<webHttpBinding>
<binding name="PaCrm.Web" maxReceivedMessageSize="262144">
<security mode="Transport">
<transport clientCredentialType="None">
</transport>
</security>
</binding>
</webHttpBinding>
</bindings>

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" />
</webHttpEndpoint>
</standardEndpoints>

<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpsGetEnabled="true" policyVersion="Policy12" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="PaCrm.WebBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>

<services>
<service name="PACRM.LogFormUsageInCrm.PACRMServices">
<endpoint name="PaCrm.Web"
address=""
binding="webHttpBinding"
bindingConfiguration="PaCrm.Web"
contract ="PACRM.LogFormUsageInCrm.IPACRMServices"
behaviorConfiguration="PaCrm.WebBehavior">
</endpoint>
</service>
</services>
</system.serviceModel>
</configuration>

I also use encrypt connection information in the config. You can follow or choose the Encrypt or Decrypt by your way.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace PACRM.LogFormUsageInCrm
{
public class CryptHelper
{
string passPhrase = ")G-0haB&amp;amp;amp;;-W&amp;amp;amp;G=eeL=j;RuN}\auw^YSj";
string saltValue = "v:O?Ed;R?y";
string hashAlgorithm = "SHA1";
int passwordIterations = 2;
string initVector = "SxG+i:x&amp;amp;amp;Vu])Q0Od";
int keySize = 256;

public CryptHelper()
{

}

public CryptHelper(string passPhrase, string saltValue, string hashAlgorithm, int passwordIterations, string initVector, int keySize)
{
this.passPhrase = passPhrase;
this.saltValue = saltValue;
this.hashAlgorithm = hashAlgorithm;
this.passwordIterations = passwordIterations;
this.initVector = initVector;
this.keySize = keySize;
}

public string Encrypt(string plainText)
{
return CryptHelper.Encrypt(plainText, this.passPhrase, this.saltValue, this.hashAlgorithm, this.passwordIterations, this.initVector, this.keySize);
}

public static string Encrypt(string plainText, string passPhrase, string saltValue, string hashAlgorithm, int passwordIterations, string initVector, int keySize)
{
try
{
// Convert strings into byte arrays.
// Let us assume that strings only contain ASCII codes.
// If strings include Unicode characters, use Unicode, UTF7, or UTF8
// encoding.
byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);

// Convert our plaintext into a byte array.
// Let us assume that plaintext contains UTF8-encoded characters.
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

// First, we must create a password, from which the key will be derived.
// This password will be generated from the specified passphrase and
// salt value. The password will be created using the specified hash
// algorithm. Password creation can be done in several iterations.
PasswordDeriveBytes password = new PasswordDeriveBytes(
passPhrase,
saltValueBytes,
hashAlgorithm,
passwordIterations);

// Use the password to generate pseudo-random bytes for the encryption
// key. Specify the size of the key in bytes (instead of bits).
byte[] keyBytes = password.GetBytes(keySize / 8);

// Create uninitialized Rijndael encryption object.
RijndaelManaged symmetricKey = new RijndaelManaged();

// It is reasonable to set encryption mode to Cipher Block Chaining
// (CBC). Use default options for other symmetric key parameters.
symmetricKey.Mode = CipherMode.CBC;

// Generate encryptor from the existing key bytes and initialization
// vector. Key size will be defined based on the number of the key
// bytes.
ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
keyBytes,
initVectorBytes);

// Define memory stream which will be used to hold encrypted data.
MemoryStream memoryStream = new MemoryStream();

// Define cryptographic stream (always use Write mode for encryption).
CryptoStream cryptoStream = new CryptoStream(memoryStream,
encryptor,
CryptoStreamMode.Write);
// Start encrypting.
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);

// Finish encrypting.
cryptoStream.FlushFinalBlock();

// Convert our encrypted data from a memory stream into a byte array.
byte[] cipherTextBytes = memoryStream.ToArray();

// Close both streams.
memoryStream.Close();
cryptoStream.Close();

// Convert encrypted data into a base64-encoded string.
string cipherText = Convert.ToBase64String(cipherTextBytes);

// Return encrypted string.
return cipherText;
}
catch (Exception ex)
{
//var message = CrmExceptionHelper.GetErrorMessage(ex, true);
throw new Exception(ex.Message);
}
}

public string Decrypt(string cipherText)
{
return CryptHelper.Decrypt(cipherText, this.passPhrase, this.saltValue, this.hashAlgorithm, this.passwordIterations, this.initVector, this.keySize);
}

public static string Decrypt(string cipherText, string passPhrase, string saltValue, string hashAlgorithm, int passwordIterations, string initVector,int keySize)
{
try
{
// Convert strings defining encryption key characteristics into byte
// arrays. Let us assume that strings only contain ASCII codes.
// If strings include Unicode characters, use Unicode, UTF7, or UTF8
// encoding.
byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);

// Convert our ciphertext into a byte array.
byte[] cipherTextBytes = Convert.FromBase64String(cipherText);

// First, we must create a password, from which the key will be
// derived. This password will be generated from the specified
// passphrase and salt value. The password will be created using
// the specified hash algorithm. Password creation can be done in
// several iterations.
PasswordDeriveBytes password = new PasswordDeriveBytes(
passPhrase,
saltValueBytes,
hashAlgorithm,
passwordIterations);

// Use the password to generate pseudo-random bytes for the encryption
// key. Specify the size of the key in bytes (instead of bits).
byte[] keyBytes = password.GetBytes(keySize / 8);

// Create uninitialized Rijndael encryption object.
RijndaelManaged symmetricKey = new RijndaelManaged();

// It is reasonable to set encryption mode to Cipher Block Chaining
// (CBC). Use default options for other symmetric key parameters.
symmetricKey.Mode = CipherMode.CBC;

// Generate decryptor from the existing key bytes and initialization
// vector. Key size will be defined based on the number of the key
// bytes.
ICryptoTransform decryptor = symmetricKey.CreateDecryptor(
keyBytes,
initVectorBytes);

// Define memory stream which will be used to hold encrypted data.
MemoryStream memoryStream = new MemoryStream(cipherTextBytes);

// Define cryptographic stream (always use Read mode for encryption).
CryptoStream cryptoStream = new CryptoStream(memoryStream,
decryptor,
CryptoStreamMode.Read);

// Since at this point we don't know what the size of decrypted data
// will be, allocate the buffer long enough to hold ciphertext;
// plaintext is never longer than ciphertext.
byte[] plainTextBytes = new byte[cipherTextBytes.Length];

// Start decrypting.
int decryptedByteCount = cryptoStream.Read(plainTextBytes,
0,
plainTextBytes.Length);

// Close both streams.
memoryStream.Close();
cryptoStream.Close();

// Convert decrypted data into a string.
// Let us assume that the original plaintext string was UTF8-encoded.
string plainText = Encoding.UTF8.GetString(plainTextBytes,
0,
decryptedByteCount);

// Return decrypted string.
return plainText;
}
catch (Exception ex)
{
//var message = CrmExceptionHelper.GetErrorMessage(ex, true);
throw new Exception(ex.Message);
}
}
}

public static class Extensions
{
///
&amp;amp;lt;summary&amp;amp;gt;
///
/// &amp;amp;lt;/summary&amp;amp;gt;

/// &amp;amp;lt;param name="str"&amp;amp;gt;&amp;amp;lt;/param&amp;amp;gt;
/// &amp;amp;lt;returns&amp;amp;gt;&amp;amp;lt;/returns&amp;amp;gt;
public static string Decrypt(this string str)
{
if (string.IsNullOrEmpty(str))
return string.Empty;
CryptHelper crypter = new CryptHelper();
return crypter.Decrypt(str);

}

///
&amp;amp;lt;summary&amp;amp;gt;
/// RBEI Encrypt extension
/// &amp;amp;lt;/summary&amp;amp;gt;

/// &amp;amp;lt;param name="str"&amp;amp;gt;&amp;amp;lt;/param&amp;amp;gt;
/// &amp;amp;lt;returns&amp;amp;gt;&amp;amp;lt;/returns&amp;amp;gt;
public static string Encrypt(this string str)
{
if (string.IsNullOrEmpty(str))
return string.Empty;
CryptHelper crypter = new CryptHelper();
return crypter.Encrypt(str);
}
}
}&amp;amp;lt;/pre&amp;amp;gt;
&amp;amp;lt;pre&amp;amp;gt;

 

 Then you can deploy to your IIS. The following image is my wcf after being deployed.
iis_deploy
Final step, call wcf by rest in Crm form. You can define the method as following.
function LogCrmFormUsageByCustomService() {
 //debugger;
 var id = Xrm.Page.data.entity.getId();
 if (id == undefined || id == null)
 return;

 var objMdc = {};
 var ObjectTypeCode = Xrm.Page.context.getQueryStringParameters().etc
 var ServerUrl = Xrm.Page.context.getServerUrl();
 //To get current date time in format yyyymmdd:hhMMssmmm
 var currentDateTime = new Date();

 ////debugger;
 var devUrl = "https://yourwcfUrl:4001/CRM_FormLoggers/PACRMServices.svc/LogCrmFormUsageInCrm";

 var orgName = Xrm.Page.context.getOrgUniqueName();

 var restUrl = '';
 if (orgName.toUpperCase() === 'PACRMDEV')
 restUrl = devUrl;
 else if (orgName.toUpperCase() === 'PACRMQUAL')
 restUrl = qualUrl;
 else
 restUrl = "Production Link";

 var clientValue = 0;
 if (Xrm.Page.context.isOutlookClient()) {
 clientValue = 2
 objMdc.rbei_Client = { Value: 2 }; //value: from, type: "EntityCollection"
 } else {
 clientValue = 1
 }
 var ObjFormUsage = {};
 ObjFormUsage.Client = clientValue;
 ObjFormUsage.EntityName = Xrm.Page.data.entity.getEntityName();
 ObjFormUsage.Name = current + "_" + Xrm.Page.data.entity.getEntityName();
 ObjFormUsage.RecordId = id.toString();
 ObjFormUsage.RecordUrl = ServerUrl + "/main.aspx?etc=" + ObjectTypeCode + "&amp;amp;id=" + id.toLowerCase() + "&amp;amp;pagetype=entityrecord";
 ObjFormUsage.UsageDateTime = currentDateTime.toString();
 ObjFormUsage.UserId = Xrm.Page.context.getUserId();

 if (clientValue == 2 || clientValue == 1) {
 try {
 var req = new XMLHttpRequest();
 req.open("POST", restUrl, false);
 req.setRequestHeader("Accept", "application/json");
 req.setRequestHeader("Content-Type", "application/json; charset=utf-8");

 req.onreadystatechange = function () {
 if (this.readyState === 4) {
 this.onreadystatechange = null;
 if (this.status === 201 || this.status === 200) {
 var result = JSON.parse(this.responseText).d;
 }
 else {
 alert(this.statusText);
 }
 }
 };
 req.send(JSON.stringify(ObjFormUsage));
 }
 catch (error) {
 throw Error(error);
 }
 }
}
&amp;lt;/pre&amp;gt;&amp;lt;pre&amp;gt;

Then you can create 1 javascript webresource and call this method on the form that you want. You can use Fiddler to capture your message, you will see that Crm will call your custom wcf without failure for Cross Domain.
Request 1.png

In this sample, I created 1 custom Entity call xxx_entityformreadusage with follow fields.
xxx_name – text
xxx_entityname – text
xxx_recordguid – Text
xxx_recurl – Text
xxx_usagedatetime – DateTime
xxx_client – Int
x_user – Entity Reference to systemuser entity.

Hope that it will be useful for someone.



This was originally posted here.

Comments

*This post is locked for comments