Tag: CRM

Add custom fetch XML to subgrid lookup

Add custom fetch XML to subgrid lookup

In this blog, I am going to show how we can change subgrid lookup behavior. Basically, I want to control custom N:N contact subgrid lookup, depend on company lookup that is on the contact form.

e.g. A.Datum company have five contacts and if I select contact lookup (custom N:N subgrid inline lookup ) on one of contact form so it’ll only show four contacts in subgrid lookup excluding this contact.

I have an N:N subgrid and company lookup on the contact form.

2

What’s the tricky part for getting subgrid lookup ?

Inspecting DOM

For add event on subgrid lookup, we need to inspect the DOM for subgrid and look out for lookup object.

Have a look below image, It clearly shows how we can find lookup , object by object.(if object key, not the same like below then find lookup with help of object name.)

FYI I am using Dynamics 365 online.

3

After inspecting the DOM structure of the subgrid and carefully understand the mechanics of the lookup filter,we can set filter criteria like in the JavaScript below:

 var RelatedContact = Xrm.Page.getControl('RelatedContact');
 var RelatedContactLookup = RelatedContact.$0_3.$1m_4.$O_4.$38_3.$3_6;
 RelatedContactLookup.addPreSearch(function ()
 {
 if (Xrm.Page.getAttribute("parentcustomerid").getValue())
 {
 var parentCustomer = Xrm.Page.getAttribute("parentcustomerid").getValue()[0].id;
 var strVar = "";
 strVar += "<filter type=\"and\" >";
 strVar +=" <condition attribute=\"statecode\" operator=\"eq\" value=\"0\" />"
 strVar += " <condition attribute=\"parentcustomerid\" 
               operator=\"eq\" value=\"" + parentCustomer + "\" \/>";
 strVar += " <\/filter>";
 strVar += "";
 RelatedContactLookup.addCustomFilter(strVar, "contact");
 }
 })

 

Get the company id from Xrm.Page library  and passes in fetchXML for search criteria.

Our filter criteria code is done but for triggering the above code, we need to attach click function to the subgrid add button, like in JavaScript below :

var RelatedContact_Button = window.parent.document.getElementById("RelatedContact_addImageButton");
 RelatedContact_Button.addEventListener("click", function ()
 {
 ///put your filter code here  
 }

Here is complete code for JavaScript web resource that we need to add on a contact form.

You can also write this code inside a function and call it onload, here I am using self invoking JS function.

(function ()
{
 setTimeout(function ()
 {
 var RelatedContact_Button = window.parent.document.getElementById("RelatedContact_addImageButton");
 RelatedContact_Button.addEventListener("click", function ()
 {
 var RelatedContact = Xrm.Page.getControl('RelatedContact');
 var RelatedContactLookup = RelatedContact.$0_3.$1m_4.$O_4.$38_3.$3_6;
 RelatedContactLookup.addPreSearch(function ()
 {
 if (Xrm.Page.getAttribute("parentcustomerid").getValue())
 {
 var parentCustomer = Xrm.Page.getAttribute("parentcustomerid").getValue()[0].id;
 var strVar = "";
 strVar += "<filter type=\"and\" >";
 strVar += " <condition attribute=\"parentcustomerid\" operator=\"eq\" value=\"" + parentCustomer + "\" \/>";
 strVar += " <\/filter>";
 strVar += "";
 RelatedContactLookup.addCustomFilter(strVar, "contact");
 }
 })
 })
 }, 2000)
}())

You can also download from here.

animation

 

That’s it , Please try and comment below If you found any problem.

Hope it’ll help someone.

 

Note: This is an unsupported way if you really want to go with a supported way then use custom web resource HTML for showing your grid with filter criteria.

Advertisements
CRM Web Portal with Token Based authentication -Part 1

CRM Web Portal with Token Based authentication -Part 1

Sometime we have a requirement to make a web portal with using CRM as back end e.g. ticket management portal. A portal is a custom application that allows external users to access and edit data that is stored in Microsoft Dynamics CRM (a.k.a CRUD – Create, Read, Update and Delete operations). A CRM portal application can be developed with different technologies along with the Dynamics CRM Web Services for data access.

In this article we’ll discuss how we can implement token based authentication in CRM web portal.I’ll divided this article into two parts

  1. CRM Web Portal with JWT authentication -Part 1
  2. Intregate CRM web portal with AngularJS-Part 2(coming soon..)

Token Based Authentication

As I stated before we’ll use token based approach to implement authentication between the front-end application and the CRM API, as we all know the common and old way to implement authentication is the cookie-based approach were the cookie is sent with each request from the client to the server, and on the server it is used to identify the authenticated user.

1. Create a web application (webapi) project in visual studio with no authentication.

create_new_project

create_new_project1

create_new_project2

2.Remove Global.asax from our project and add owin starup class i.e. startup.cs

using System;
using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Security.OAuth;
using Microsoft.Owin.Security;
using System.Web.Http;
using System.Web.Routing;
using System.Web.Mvc;
using System.Net.Http.Headers;
using Microsoft.AspNet.Identity.Owin;

[assembly: OwinStartup(typeof(MSCRMjwtAuth.Startup))]

namespace MSCRMjwtAuth
{
public class Startup
{

public void Configuration(IAppBuilder app)
{
HttpConfiguration httpconfig = new HttpConfiguration();
WebApiConfig.Register(httpconfig);
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
app.UseWebApi(httpconfig);
httpconfig.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue(“text/html”));

// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
}
}
}

3.After that install below nuget packages

Install-Package Microsoft.AspNet.WebApi.Owin

Install-Package Microsoft.Owin.Host.SystemWeb

Install-Package Microsoft.Owin.Security.OAuth

Install-Package Microsoft.AspNet.Identity.Owin

Install-Package Microsoft.CrmSdk.CoreAssemblies

In this blog we’re authenticating user with contact entity, emailaddress1 field as username and custom attribute am_password  for password and we also store password in encrypted format.

4.Create a new solution i.e. JWTaunthentication for all our schema customization.

create_solution

5. CRMHelper class

CRMhelper class contains functions that check user and password from our existing CRM contact records on behalf of input that user provided.

/// <summary>
/// This class contains all crm functions
/// </summary>
public class CRMHelper
{
//Variable
public IOrganizationService _orgService;

//CRM dynamics Connection
CrmConnection connection = CrmConnection.Parse(ConfigurationManager.ConnectionStrings[“CRMConnectionString”].ConnectionString);

public CRMHelper()
{
_orgService = new OrganizationService(connection);
}

/// <summary>
/// Login Function for parent
/// </summary>
/// <param name=”arry”>array of login id and password</param>
/// <returns>Datacollection of contact entity</returns>
public DataCollection<Entity> tryLogin(string[] arry)
{
try
{
QueryExpression parentQuery = new QueryExpression()
{
EntityName = “contact”,
ColumnSet = new ColumnSet(“firstname”, “lastname”),
Criteria =
{
Filters =
{
new FilterExpression(LogicalOperator.And)
{
Conditions={
new ConditionExpression(“emailaddress1”,ConditionOperator.Equal,arry[0].ToString()),
new ConditionExpression(“am_password”,ConditionOperator.Equal,StringCipher.Encrypt(arry[1].ToString()))
}
}
}
}
};
return _orgService.RetrieveMultiple(parentQuery).Entities;
}
catch (Exception ex)
{
throw;
}
}

/// <summary>
/// Get all Cases related to logged in user
/// </summary>
/// <param name=”customerid”>Contact Entity guid</param>
/// <returns>Datacollection of Incident Entity</returns>
public DataCollection<Entity> getCases(Guid customerid)
{
try
{
// Instantiate QueryExpression QEincident
var QEincident = new QueryExpression(“incident”);

// Add columns to QEincident.ColumnSet
QEincident.ColumnSet.AddColumns(“title”);

// Define filter QEincident.Criteria
QEincident.Criteria.AddCondition(“customerid”, ConditionOperator.Equal, customerid);
return _orgService.RetrieveMultiple(QEincident).Entities;
}
catch (Exception ex)
{

throw;
}
}

}

5. OAuth Bearer Tokens Generation

Add a new class i.e. ApplicationOAuthProvider.cs in our web application that support for OAuth Bearer Tokens Generation.

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
CRMHelper helper = new CRMHelper();
if (context.UserName != null && context.Password != null)
{
var collection = helper.tryLogin(new string[] { context.UserName, context.Password });
if (collection.Count > 0)
{
context.OwinContext.Response.Headers.Add(“Access-Control-Allow-Origin”, new[] { “*” });
var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Email, context.UserName));
var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
context.Validated(ticket);
}

}
//return base.GrantClientCredentials(context);
}
}

6. Encryption and Decryption class

public static class StringCipher
{
// This constant string is used as a “salt” value for the PasswordDeriveBytes function calls.
// This size of the IV (in bytes) must = (keysize / 8). Default keysize is 256, so the IV must be
// 32 bytes long. Using a 16 character string here gives us 32 bytes when converted to a byte array.
private static readonly byte[] initVectorBytes = Encoding.ASCII.GetBytes(“tu89geji340t89u2”);

// This constant is used to determine the keysize of the encryption algorithm.
private const int keysize = 256;
private const string passPhrase = “contact”;
public static string Encrypt(string plainText)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null))
{
byte[] keyBytes = password.GetBytes(keysize / 8);
using (RijndaelManaged symmetricKey = new RijndaelManaged())
{
symmetricKey.Mode = CipherMode.CBC;
using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes))
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
byte[] cipherTextBytes = memoryStream.ToArray();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}

public static string Decrypt(string cipherText)
{
byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
using (PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null))
{
byte[] keyBytes = password.GetBytes(keysize / 8);
using (RijndaelManaged symmetricKey = new RijndaelManaged())
{
symmetricKey.Mode = CipherMode.CBC;
using (ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes))
{
using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
byte[] plainTextBytes = new byte[cipherTextBytes.Length];
int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
}

7. Create CRM contact record

Also create a record for contact entity in CRM with amankothari@outlook.com as Email and ChHTqB6783q4J/b5BHD7vQ==  as Password (we stored password in encrypted form).

ContactForm.png

8. Add secured controller for Incident

[RoutePrefix(“api/cases”)]
public class CasesController : ApiController
{
CRMHelper helper = new CRMHelper();
[Authorize]
[HttpGet]
[Route(“”)]
public DataCollection<Entity> Getcases()
{
try
{
var identity = (ClaimsIdentity)User.Identity;
foreach (var a in identity.Claims)
{
if (a.Type == “contactid”)
{
return helper.getCases(new Guid(a.Value));
}}
return null;

}
catch (Exception ex)
{
return null;
}
}
}

Notice how we added the “Authorize” attribute on the method “Get” so if you tried to issue HTTP GET request to the end point “http://localhost:port/api/cases&#8221; you will receive HTTP status code 401 unauthorized because the request you send till this moment doesn’t contain valid authorization header.

9.Modified our startup.cs code for support for OAuth Bearer Tokens.

using System;
using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Security.OAuth;
using Microsoft.Owin.Security;
using System.Web.Http;
using System.Web.Routing;
using System.Web.Mvc;
using System.Net.Http.Headers;
using Microsoft.AspNet.Identity.Owin;

[assembly: OwinStartup(typeof(MSCRMjwtAuth.Startup))]

namespace MSCRMjwtAuth
{
public class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpconfig = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(httpconfig);
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
app.UseWebApi(httpconfig);
httpconfig.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue(“text/html”));

// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(“/oauth/token”),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
AllowInsecureHttp = true,
};

// Token Generation
app.UseOAuthBearerTokens(OAuthOptions);
}
}
}

Assuming that you saved the username “amankothari@outlook.com”  with password “ChHTqB6783q4J/b5BHD7vQ==” (123456) in CRM. In the step below, we’ll use the same username to generate token, so to test this out open your favorite REST client application in order to issue HTTP requests to generate token for user “amankothari@outlook.com”. For me I’ll be using PostMan.

Now we’ll issue a POST request to the endpoint http://localhost:57205/oauth/token the request will be as the image below:

Postman1.PNG

If the username and password are not same then we sent bad request in response.

invalid_token_postman

If all is correct we’ll receive HTTP status 200 along with the secured data in the response body, if you try to change any character with signed token you directly receive HTTP status code 401 unauthorized.

Casescaseresponse

Now our back-end API is ready to be consumed from any front end application or native mobile app.

In my next blog we’ll consumed this API with angularJS application.

Below are the links of solution and web project code respectively.

https://1drv.ms/u/s!AvWbcg6Nl97LiUsVexaWI0gZV2TP

https://1drv.ms/u/s!AvWbcg6Nl97LiUyciNWPn4ehGpNH