Bhoopathi

"Be Somebody Nobody Thought You could Be"

Friday, April 28

IFrames and Relative Paths

IFrames URL Setting in MS CRM for ISV Components:

I ran into this issue again and although I am sure I write about it before, I can’t find a reference to it.
The Scenario:

In Dynamics CRM 2011 it was quite common for companies (on-premise) to have custom ASP.NET pages 
that were shown within the context of the Dynamics CRM web site.  
Since they were always in a specific folder (the ISV folder), 
you could use a relative path to locate that page.
Here is an example of this practice:

image

The Problem

The problem appears after one of the updates to Dynamics CRM 2011 (I can’t remember which), 
but if you edit the URL field, CRM will automatically add http:// to the beginning of the URL.  
This will end up producing a totally invalid URL and your page will not load in the iFrame.

The Solution:

Unfortunately, once the change is made, there is no undoing it.  Here are your options:
1. Add a piece of JavaScript to set the URL.
2. Export, manually edit, and reimport the default solution.
3. Change the path to one that is fully-qualified.

#1 is the easiest to implement.
#2 is a painful, but one-time charge.
#3 will cause issues should you have a multi-server environment (development, test, production) 
and are constantly moving the solution around.

Conclusion:
So this is a fairly simple and innocuous change that many people will not look at twice .

– until they make a change and find that their iFrame no longer works, that is…

Monday, April 10

Programmatically Manage User's Security Roles

Managing CRM User Roles Programmatically::

You often hear CRM users and admin complain about having to manage the system users’ roles too often. It can be a pain if you have a lot of movement in the company, for example people going from one business unit to another, high turnover rate or fast growing company.
I thought I would share a design idea for making user management a little bit easier for your organization. It consists of a few steps, and it requires some coding skills.
  • Define your business roles and match each of them to a set of CRM Security Roles.
Business RoleCRM Security Roles
Sales ManagerSalesPeron; Sales Manager
IT ManagerSystem Customizer
  • On the “User” entity, create an Option Set field representing the business roles capture in step 1
Now the fun begins.
  • Create an XML file to map the business role option set values to a set to CRM Security Role names (see XML format in picture below)
  • Upload the XML file to CRM as a web resource
  • Switching over to the plugin side. You need to build a plugin that runs on update of a User record and that that does the following operations:
    • Read the “Business Role” field value on the User record
    • Retrieve the web resource configuration XML file based on its name or ID
    • Parse the XML to get the CRM roles corresponding to the business role selected
    • For each CRM Security Role found, assign it to the user
    • This is what my plugin code looks like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public override void Execute(IExecutionContext context, IOrganizationService service, ITracingService tracingService)
{
 if (context.MessageName != "Update") return;
 
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
 {
 Entity target = (Entity)context.InputParameters["Target"];
 if (target.Attributes.Contains("sa_userrole") && target.Attributes["sa_userrole"] != null)
 {
 SystemUser user = target.ToEntity<SystemUser>();
 
// Get Security Roles from Configuration
 WebResource wr = GetWebResourceByName(WebResourceName, service);
 string xmlContent = UnicodeEncoding.UTF8.GetString(Convert.FromBase64String(wr.Content));
 
XmlDocument config = new XmlDocument();
 config.LoadXml(xmlContent);
 
XmlNodeList crmRoles = config.SelectNodes("/SystemRoles/SystemRole[@ID=" + user.sa_UserRole.Value + "]/Role");
 foreach (XmlNode role in crmRoles)
 {
 Role crmRole = FindSecurityRole(user, role.InnerText, service);
 AssignSecurityRole(crmRole, user, service);
 }
 }
 }
}
 
/// <summary>
/// Retrieves a CRM Web resource by name
/// </summary>
private WebResource GetWebResourceByName(string webResourceName, IOrganizationService service)
{
 QueryByAttribute requestWebResource = new QueryByAttribute
 {
 EntityName = WebResource.EntityLogicalName,
 ColumnSet = new ColumnSet(true),
 };
 requestWebResource.Attributes.AddRange("name");
 requestWebResource.Values.AddRange(webResourceName);
 
EntityCollection webResourceCollection = service.RetrieveMultiple(requestWebResource);
 return (WebResource)webResourceCollection.Entities.FirstOrDefault();
}
 
/// <summary>
/// Find a Security Role by name and business unit
/// </summary>
private Role FindSecurityRole(SystemUser user, string roleName, IOrganizationService service)
{
 
if (user.BusinessUnitId == null || user.BusinessUnitId.Id == Guid.Empty)
 {
 SystemUser retrieveUser = service.Retrieve(user.LogicalName, user.Id, new ColumnSet("businessunitid")).ToEntity<SystemUser>();
 user.BusinessUnitId = retrieveUser.BusinessUnitId;
 }
 
// Retrieve a role from CRM.
 QueryExpression query = new QueryExpression
 {
 EntityName = Role.EntityLogicalName,
 ColumnSet = new ColumnSet("roleid"),
 Criteria = new FilterExpression
 {
 Conditions =
 {
 new ConditionExpression
 {
 AttributeName = "name",
 Operator = ConditionOperator.Equal,
 Values = {roleName}
 },
 
 new ConditionExpression
 {
 AttributeName = "businessunitid",
 Operator = ConditionOperator.Equal,
 Values = { user.BusinessUnitId.Id }
 }
 }
 }
 };
 
return service.RetrieveMultiple(query).Entities.Cast<Role>().FirstOrDefault();
}
 
/// <summary>
/// Assign Security Role to a CRM User
/// </summary>
private void AssignSecurityRole(Role securityRole, SystemUser user, IOrganizationService service)
{
 service.Associate("systemuser",
 user.Id,
 new Relationship("systemuserroles_association"),
 new EntityReferenceCollection() { securityRole.ToEntityReference() });
}
At this point, all you have to do is change that field on your users’ form and the plugin will handle assigning the security roles automatically. You may want to implement a way to handle the security roles assigned to users before the business role is changed. If you are planning to use this design or something similar, here are some key takeaways:
  • The same logic cannot be processed at the creation of the “User” record. That is because before being able to assign security roles to a user, the system needs additional information that gets created after the user record is created.
  • The plugin must run as an administrator or a user who has enough privileges to create and assign security roles to a user
  • The configuration is stored in a XML web resource but it could be stored anywhere (CRM entity, external database, flat file etc.)
  • Also note that Security Roles are copied to each business unit, so when looking a Security Role in CRM based on its name, it is important to search in the same business unit as the user’s
  • CRM does not allow to edit multiple user records at the same time