Monday, June 22, 2009

Personal Default Views Manager for Microsoft Dynamics CRM 4.0

The customer wants to make a personal setting for views according to next scenario.
Default account view is 'Active Accounts' for Tom, and 'My Active Accounts' view is default for Peter.

I've developed a little wizard and I would like to share it.


The following ideas for this wizard are:
1. Create a entity child to systemuser entity in which user can select default view for an entity.
2. To write plugin which will handle RetrieveMultiple message on savedquery entity.

1. Create a Default User View entity:
1.1. Creation of Default User View 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 a relation between my newly created entity and systemuser entity where systemuser is primary entity.



I create required attributes:
new_defaultview - for default view identifier
new_defaultviewdisplayname - display name of default View
new_entitydisplayname - display name of entity (entity schema name will be stored in new_entityname field)
new_entitytypecode - entity type code

Appearance of form:



Second tab will be hide in OnLoad event handler - it contains fields Default View Name, Entity Display Name and Entity Type Code:



OnLoad Script of form:

//function converts text field Entity Name to picklist and fills it with entities
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' onchange='new_entityname_onchange0();'>";
var defaultValueFound = false;

for (var i = 0; i < dataItems.length; i++)
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;
}

//function converts text field Default View to picklist and fills it with Views for entity selected in Entity Name picklist
function CreateQueriesList(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++)
{
select += "<option value='" + dataItems[i].selectSingleNode('./savedqueryid').nodeTypedValue + "' ";
if (dataItems[i].selectSingleNode('./savedqueryid').nodeTypedValue == defaultValue)
{
select += " SELECTED";
defaultValueFound = true;
}
select += ">" + dataItems[i].selectSingleNode('./name').nodeTypedValue + "</option>";
}

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

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

//Hide second tab
crmForm.all.tab1Tab.style.display = 'none';

//Request to retrieve all 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);
xmlHttpRequest.send(request);

var result = xmlHttpRequest.responseXML;

//Retrieve a collection of entities
var schemaNames = result.selectNodes("//CrmMetadata/CrmMetadata");

//Convertion
ConvertEntityToPicklist('new_entityname', schemaNames);

//Request to retrieve all Public Views for entity
var xml = "<?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>"+
"<Fetch xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"+
"<fetchXml>"+
"&lt;fetch mapping='logical'&gt;"+
" &lt;entity name='savedquery'&gt;"+
" &lt;attribute name='name'/&gt;"+
" &lt;attribute name='savedqueryid'/&gt;"+
" &lt;filter type='and'&gt;"+
" &lt;condition attribute='isprivate' operator='eq' value='false'/&gt;"+
" &lt;condition attribute='inproduction' operator='eq' value='true'/&gt;"+
" &lt;condition attribute='isquickfindquery' operator='eq' value='false'/&gt;"+
" &lt;condition attribute='returnedtypecode' operator='eq' value='"+
document.getElementById('new_entityname').options[document.getElementById('new_entityname').selectedIndex].getAttribute('entitytypeid')+
"'/&gt;"+
" &lt;condition attribute='querytype' operator='eq' value='0'/&gt;"+
" &lt;/filter&gt;"+
" &lt;/entity&gt;"+
"&lt;/fetch&gt;"+
"</fetchXml>"+
"</Fetch>"+
"</soap:Body>"+
"</soap:Envelope>";

var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xHReq.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/Fetch");
xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xHReq.setRequestHeader("Content-Length", xml.length);
xHReq.send(xml);
var resultXml = xHReq.responseXML;
var resultSet = resultXml.text;
resultSet.replace('&lt;','<');
resultSet.replace('&gt;','>');

var oXmlDoc = new ActiveXObject("Microsoft.XMLDOM");
oXmlDoc.async = false;
oXmlDoc.loadXML(resultSet);

//Retrieve a collection of Views
var results = oXmlDoc.getElementsByTagName('result');

//Conversion
CreateQueriesList('new_defaultview', results);


OnSave script of Form:

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

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

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


Entity Name OnChange event handler:

//Request to retrieve all Public Views for entity
var xml = "<?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>"+
"<Fetch xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"+
"<fetchXml>"+
"&lt;fetch mapping='logical'&gt;"+
" &lt;entity name='savedquery'&gt;"+
" &lt;attribute name='name'/&gt;"+
" &lt;attribute name='savedqueryid'/&gt;"+
" &lt;filter type='and'&gt;"+
" &lt;condition attribute='isprivate' operator='eq' value='false'/&gt;"+
" &lt;condition attribute='inproduction' operator='eq' value='true'/&gt;"+
" &lt;condition attribute='isquickfindquery' operator='eq' value='false'/&gt;"+
" &lt;condition attribute='returnedtypecode' operator='eq' value='"+
document.getElementById('new_entityname').options[document.getElementById('new_entityname').selectedIndex].getAttribute('entitytypeid')+
"'/&gt;"+
" &lt;condition attribute='querytype' operator='eq' value='0'/&gt;"+
" &lt;/filter&gt;"+
" &lt;/entity&gt;"+
"&lt;/fetch&gt;"+
"</fetchXml>"+
"</Fetch>"+
"</soap:Body>"+
"</soap:Envelope>";

var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xHReq.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/Fetch");
xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xHReq.setRequestHeader("Content-Length", xml.length);
xHReq.send(xml);
var resultXml = xHReq.responseXML;
var resultSet = resultXml.text;
resultSet.replace('&lt;','<');
resultSet.replace('&gt;','>');

var oXmlDoc = new ActiveXObject("Microsoft.XMLDOM");
oXmlDoc.async = false;
oXmlDoc.loadXML(resultSet);

//Results Retrieve
var results = oXmlDoc.getElementsByTagName('result');

var ctrl = document.getElementById('new_defaultview');


//Clear picklist values
for(var i = ctrl.options.length-1; i >=0 ; i--)
{
ctrl.options[i] = null;
}

//Fill picklist with retrieved Views
for (var i = 0; i < results.length; i++)
{
var newoption = document.createElement("option");
newoption.setAttribute("value", results[i].selectSingleNode('./savedqueryid').nodeTypedValue);
newoption.innerHTML = results[i].selectSingleNode('./name').nodeTypedValue;
ctrl.appendChild(newoption);
}


Save and Close Form and publish changes. Give all users privileges at least to read this entity.

2. Plugin source:

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

namespace InternalPVM
{
public class DefaultViewHandler : IPlugin
{
#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"];


//I retrieve entity Type code of entity
string entityname = ((ConditionExpression)resultExpression.Criteria.Conditions[0]).Values[0].ToString();

ICrmService crmsevice = context.CreateCrmService(true);

//I create a request to retrieve default View for current entity
QueryExpression query = new QueryExpression("new_defaultuserview");
query.Criteria.AddCondition(new ConditionExpression("new_userid", ConditionOperator.Equal, context.UserId));
query.Criteria.AddCondition(new ConditionExpression("new_entitytypecode", ConditionOperator.Equal, entityname));
query.ColumnSet = new ColumnSet(new string[] { "new_defaultview" });

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


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

//If there are no settings just exit from this method
if (response.BusinessEntityCollection.BusinessEntities.Count == 0)
return;

//Identifier of Default View
Guid defaultviewid = new Guid(((DynamicEntity)response.BusinessEntityCollection.BusinessEntities[0])["new_defaultview"].ToString());

BusinessEntityCollection result = (BusinessEntityCollection)context.OutputParameters["BusinessEntityCollection"];

//Scroll all Views one-By-One and Set default View from user Setting to be default in resultset
foreach (DynamicEntity de in result.BusinessEntities)
if (((Key)de["savedqueryid"]).Value.Equals(defaultviewid))
de["isdefault"] = new CrmBoolean(true);
else
de["isdefault"] = new CrmBoolean(false);
}
}

#endregion
}
}


Build and register assembly. Register new step - Message RetrieveMultiple, Primary entity - savedquery, Secondary Entity - none, Stage - Post Stage, Execution Mode = Syncronous, Pipeline - Parent Pipeline.

And the demonstration: