Friday, May 15, 2009

Public Views Manager For Microsoft Dynamics CRM 4.0 With Own Hands

Following article describes how to create mechanism which provides/restricts access to views of entities.

Idea:
1. Creation two custom entities - Public View Manager(contains name of entity to restrict access to views and Business Unit for which restriction will be operate) and Public View Detail (contains View Name and Bit flag - Access Allowed/Disallowed).
Public View Manager entity is parent to Public View Detail. Both entities are organization owned.
2. Public Entity Manager form scripting.
3. Writing a plugin which will create Public View Detail for just created Public View Manager.
4. Writing a plugin which will handle RetrieveMultiple message on savedquery entity and if restrictions exist - correct filter for data retrieving.


1.1. Creation of Public View Manager Entity:



Here I input Display Name, Plural Name, ownership is organization, name of entity. Mark all details as shown.



I set primary attribute name as new_entityname.



I create required attributes:
new_bu - Display name of Business Unit
new_buid - identifier of Business Unit
new_entitydisplayname - display name of entity (entity schema name will be stored in new_entityname field)
new_entitytypecode - entity type code :)

I design the appearance of form:



Iframe I have added for child details display.
Second tab will be hidden. Here is it:



1.2. Child entity creation:



Here I input Display Name, Plural Name, ownership is organization, name of entity.



I set primary attribute name as new_viewname.





I've added the relationship between Public View Manager and Public View Detail.



I create required attributes:
new_isallowed - attribute show accessibility of view
new_publicviewmanagerid - lookup attribute - to the primaru entity Public View Manager
new_viewid - identifier of view (savedquery)



I configure form appearance.

2. Public View Manager OnLoad script:

//Method converts EntityName field to picklist with entities list
function ConvertEntityToPicklist(fieldName, dataItems)
{
var defaultValue = crmForm.all[fieldName].DataValue;
var table = crmForm.all[fieldName + "_d"];
var select = "<select req='0' id='" + fieldName + "' name='" + fieldName + "' defaultSelected='' class='ms-crm-SelectBox' tabindex='1170'>";
var defaultValueFound = false;

for (var i = 0; i < dataItems.length; i++)
//filter on only Customizable entities
if (dataItems[i].selectSingleNode('IsCustomizable').text == "true")
{
select += "<option value='" + dataItems[i].selectSingleNode('LogicalName').text + "' ";
select += "entitytypeid='"+dataItems[i].selectSingleNode('ObjectTypeCode').text+"'";
if (dataItems[i].selectSingleNode('LogicalName').text == defaultValue)
{
select += " SELECTED";
defaultValueFound = true;
}
select += ">" + dataItems[i].selectSingleNode('DisplayName/LocLabels/LocLabel/Label').text + "</option>";
}

if ((defaultValue != null) && (defaultValue.length > 0) && !defaultValueFound)
{
select += "<option value='" + defaultValue + "' SELECTED>" + defaultValue + "</option>";
}

select += "</select>";
table.innerHTML = select;
}

//Method converts Business Unit field to picklist with Business Unit List
function ConvertBUToPicklist(fieldName, dataItems)
{
var defaultValue = crmForm.all.new_buid.DataValue;

if (defaultValue == null)
{
defaultValue = dataItems[0].selectSingleNode('./q1:businessunitid').nodeTypedValue;
crmForm.all.new_buid.DataValue = defaultValue
}


var table = crmForm.all[fieldName + "_d"];
var select = "<select req='0' id='" + fieldName + "' name='" + fieldName + "' defaultSelected='' class='ms-crm-SelectBox' tabindex='1170'>";
var defaultValueFound = false;

for (var i = 0; i < dataItems.length; i++)
{
select += "<option value='" + dataItems[i].selectSingleNode('./q1:businessunitid').nodeTypedValue + "'";
if (dataItems[i].selectSingleNode('./q1:businessunitid').nodeTypedValue == defaultValue)
{
select += " SELECTED";
defaultValueFound = true;
}
select += ">" + dataItems[i].selectSingleNode('./q1:name').nodeTypedValue + "</option>";
}

if ((defaultValue != null) && (defaultValue.length > 0) && !defaultValueFound)
{
select += "<option value='" + defaultValue + "' SELECTED>" + defaultValue + "</option>";
}

select += "</select>";
table.innerHTML = select;
}

//Hiding tab with hidden controls
crmForm.all.tab1Tab.style.display = 'none';

//Forming the request to the metadata service to retrieve full list of entities
var request = "" +
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
GenerateAuthenticationHeader() +
" <soap:Body>" +
" <Execute xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
" <Request xsi:type=\"RetrieveAllEntitiesRequest\">" +
" <RetrieveAsIfPublished>true</RetrieveAsIfPublished>" +
" <MetadataItems>EntitiesOnly</MetadataItems>" +
" </Request>" +
" </Execute>" +
" </soap:Body>" +
"</soap:Envelope>";

var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");

xmlHttpRequest.Open("POST", "/mscrmservices/2007/MetadataService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/Execute");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", request.length);

//send the request to the metadata service

xmlHttpRequest.send(request);

//and retrieve result

var result = xmlHttpRequest.responseXML;
var schemaNames = result.selectNodes("//CrmMetadata/CrmMetadata");

//call convert method
ConvertEntityToPicklist('new_entityname', schemaNames);


//create a request to Crm Service to retrieve list of business units

request = "<?xml version='1.0' encoding='utf-8'?>"+
"<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'"+
" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'"+
" xmlns:xsd='http://www.w3.org/2001/XMLSchema'>"+
GenerateAuthenticationHeader() +
"<soap:Body>"+
"<RetrieveMultiple xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"+
"<query xmlns:q1='http://schemas.microsoft.com/crm/2006/Query'"+
" xsi:type='q1:QueryExpression'>"+
"<q1:EntityName>businessunit</q1:EntityName>"+
"<q1:ColumnSet xsi:type='q1:ColumnSet'>"+
"<q1:Attributes>"+
"<q1:Attribute>name</q1:Attribute>"+
"</q1:Attributes>"+
"</q1:ColumnSet>"+
"<q1:Distinct>false</q1:Distinct>"+
"</query>"+
"</RetrieveMultiple>"+
"</soap:Body>"+
"</soap:Envelope>";

xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");

xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", request.length);

//send the request
xmlHttpRequest.send(request);

//retrieve data
result = xmlHttpRequest.responseXML;

var BUs = result.getElementsByTagName('BusinessEntity');

//call convert method
ConvertBUToPicklist('new_buid', BUs);

if (crmForm.FormType != 1)
{
//if opened form - is form of already created records - I disable controls
document.getElementById('new_entityname_d').disabled = true;
document.getElementById('new_buid_d').disabled = true;

//form url for IFrame which show child entities
var url = "areas.aspx?";
url += "oId=" + crmFormSubmit.crmFormSubmitId.value;
url += "&oType=" + crmFormSubmit.crmFormSubmitObjectType.value;
url += "&security=" + crmFormSubmit.crmFormSubmitSecurity.value;
url += "&tabSet=new_manager_publicviewdetail";
crmForm.all.IFRAME_Views.src = url;


//hide menubar
crmForm.all.IFRAME_Views.attachEvent("onreadystatechange", Ready);

function Ready()
{
var doc = crmForm.all.IFRAME_Views.contentWindow.document;
if (doc.getElementById("mnuBar1") != null)
doc.getElementById("mnuBar1").style.display = "none";
}

}


Public View Manager OnSave script:

if (crmForm.FormType == 1)
{
crmForm.all.new_bu.DataValue = document.getElementById('new_buid').options[document.getElementById('new_buid').selectedIndex].innerText;

crmForm.all.new_entitydisplayname.DataValue = document.getElementById('new_entityname').options[document.getElementById('new_entityname').selectedIndex].innerText;

crmForm.all.new_entitytypecode.DataValue = document.getElementById('new_entityname').options[document.getElementById('new_entityname').selectedIndex].getAttribute('entitytypeid');
}


3. Plugin for creation Child entities for public view manager. Step must be registered as a Post Syncronous Create to new_publicviewmanager entity. Code of the plugin:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Win32;

namespace PublicViewManager
{
public class PublicViewManagerCreate : IPlugin
{

#region CTOR

public PublicViewManagerCreate(string config, string secureConfig)
{
}

#endregion CTOR

#region IPlugin Members

public void Execute(IPluginExecutionContext context)
{
try
{
if (context.MessageName == MessageName.Create &&
context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is DynamicEntity &&
((DynamicEntity)context.InputParameters["Target"]).Name == "new_publicviewmanager")
{
DynamicEntity pvm = (DynamicEntity)context.InputParameters["Target"];

//name of entity
string entityname = (string)pvm["new_entityname"];


//id of public view manager
Guid pvmId = (Guid)context.OutputParameters["Id"];

CrmService crmService = GetCrmService(context.OrganizationName);

//creation of query to retrieve the list of related to entity public views

ConditionExpression condition1 = new ConditionExpression("isprivate", ConditionOperator.Equal, false);
ConditionExpression condition2 = new ConditionExpression("inproduction", ConditionOperator.Equal, true);
ConditionExpression condition3 = new ConditionExpression("isquickfindquery", ConditionOperator.Equal, false);
ConditionExpression condition4 = new ConditionExpression("returnedtypecode", ConditionOperator.Equal, entityname.ToLower());
ConditionExpression condition5 = new ConditionExpression("querytype", ConditionOperator.Equal, 0);

FilterExpression filter = new FilterExpression();
filter.Conditions.AddRange(new ConditionExpression[] { condition1, condition2, condition3, condition4, condition5 });

QueryExpression query = new QueryExpression(EntityName.savedquery.ToString());
query.ColumnSet = new AllColumns();

query.Criteria = filter;

RetrieveMultipleRequest request = new RetrieveMultipleRequest();
request.Query = query;
request.ReturnDynamicEntities = true;

RetrieveMultipleResponse response = (RetrieveMultipleResponse)crmService.Execute(request);

//response reading and cyclic creation of detail records
foreach (DynamicEntity sq in response.BusinessEntityCollection.BusinessEntities)
{
DynamicEntity detail = new DynamicEntity("new_publicviewdetail");
detail["new_viewname"] = (string)sq["name"];
detail["new_viewid"] = ((Key)sq["savedqueryid"]).Value.ToString();
detail["new_publicviewmanagerid"] = new Lookup("new_publicviewmanager", pvmId);
detail["new_isallowed"] = new CrmBoolean(true);

crmService.Create(detail);
}
}
}
catch (System.Web.Services.Protocols.SoapException exc)
{
throw new Exception(exc.Detail.InnerXml);
}
}

#endregion

protected CrmService GetCrmService(string OrgName)
{
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = AuthenticationType.AD;
token.OrganizationName = OrgName.Replace(" ", "");

CrmService crmService = new CrmService();
crmService.UseDefaultCredentials = true;
crmService.Url = (string)(Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\MSCRM").GetValue("ServerUrl")) + "/2007/crmservice.asmx";
crmService.CrmAuthenticationTokenValue = token;

return crmService;
}

}
}


4. Plugin on RetrieveMultiple entity which will insert additional filters to query. Step must be registered as a Pre Syncronous RetrieveMultiple on savedquery entity.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Win32;

namespace PublicViewManager
{
public class SavedQueryRetrieveHandler : IPlugin
{

#region CTOR

public SavedQueryRetrieveHandler(string config, string secureConfig)
{
}

#endregion CTOR


#region IPlugin Members

public void Execute(IPluginExecutionContext context)
{
if (context.PrimaryEntityName == EntityName.savedquery.ToString() &&
context.InputParameters.Contains("Query") &&
context.InputParameters["Query"] is QueryExpression)
{
QueryExpression resultExpression = (QueryExpression)context.InputParameters["Query"];


//entity type code retrieving
string entityTypeCode = ((ConditionExpression)resultExpression.Criteria.Conditions[0]).Values[0].ToString();

ICrmService crmService = context.CreateCrmService(true);

//reading current user business unit
systemuser user = (systemuser)crmService.Retrieve(EntityName.systemuser.ToString(), context.UserId, new ColumnSet(new string[] { "businessunitid" }));
Guid buid = user.businessunitid.Value;

//creation of query to retrieve existing restrictions
QueryExpression expression = new QueryExpression("new_publicviewdetail");
expression.ColumnSet = new ColumnSet(new string[] { "new_viewid" });
expression.Criteria.AddCondition("new_isallowed", ConditionOperator.Equal, false);

LinkEntity link = expression.AddLink("new_publicviewmanager", "new_publicviewmanagerid", "new_publicviewmanagerid");
link.LinkCriteria.AddCondition("new_entitytypecode", ConditionOperator.Equal, entityTypeCode);
link.LinkCriteria.AddCondition("new_buid", ConditionOperator.Equal, "{" + buid.ToString().ToUpper() + "}");

RetrieveMultipleRequest request = new RetrieveMultipleRequest();
request.Query = expression;
request.ReturnDynamicEntities = true;

//restrictions retrieving
List<BusinessEntity> restictions = ((RetrieveMultipleResponse)crmService.Execute(request)).BusinessEntityCollection.BusinessEntities;

//inserting additional filters to query to filter out all restricted views
foreach (DynamicEntity restriction in restictions)
resultExpression.Criteria.AddCondition("savedqueryid", ConditionOperator.NotEqual, restriction["new_viewid"].ToString());

context.InputParameters["Query"] = resultExpression;
}
}

#endregion IPlugin Members
}
}


And the result. I hope I will put the video here soon. Now screenshots:

Contact views before adding filters:



Creation of a new Public View Manager record to contact entity:



Form of Public View Manager with created details for all views:



New filters to views are added:



And result - three marked views are absent in the views dropdown:



And video. I am sorry for bad video quality.