Friday, October 30, 2009

Adding a New NavBar to an Entity Form

CRM 4.0 does not provide a very good story on customizing entity form navigation. It's acceptable for most configurations, but it would be nice if the left navigation within an entity form could be customized further. At least from the standpoint of adding additional NavBars, instead of having to place relationship and sitemap NavbarItems in one of the four provided by Microsoft (Details, Sales, Service, Marketing).

Below is the Account form with a 1-to-many relationship to an entity named Custom Entity. Through the relationships configuration on the Account entity, the only options we have are to display the link in the Details, Sales, Service, or Marketing areas. These areas can be renamed using the ISV Config file, but we cannot move any of the out-of-the-box entities to different sections. Below is a screen shot of the Account entity.

















What will be demonstrated in this post is how to create a new NavBar on the Account entity and move NavBarItems to it. This solution is not supported due to the form manipulation but is fully functional. Even security applies, meaning, the NavbarItems will only display if the user has proper access, and if the user has no access to any of the NavBarItems, the new NavBar will not display. Below is the end result. The Custom Entities, Sub-Accounts, and Relationships NavBarItems have been moved to a new NavBarItem.
















Below is a JavaScript class that can be used to create new NavBars using the OnLoad event of an entity form.


// =====================================================
// JsNavBar v1.0 - 10/30/2009 (CRM 4.0)
// =====================================================
JsNavBar = function(displayName) {
var navBarDisplayName = displayName;
var navBarItemArray = new Array();

JsNavBar.prototype.AddNavBarItem = function(navBarItemId) {
navBarItemArray[navBarItemArray.length] = document.getElementById(navBarItemId);
};

JsNavBar.prototype.Show = function() {
var crmNavBar = document.getElementById("crmNavBar");
if (crmNavBar != null) {

var navBarItems = document.createElement("UL");
navBarItems.style.display = "inline";
navBarItems.className = "ms-crm-Nav-Group-Subareas";

for (i = 0; i < navBarItemArray.length; i++) {
if (navBarItemArray[i] != null) {
navBarItems.appendChild(navBarItemArray[i]).parentNode;
}
}

if (navBarItems.hasChildNodes()) {
var newNavBar = document.createElement("LI");
newNavBar.className = "ms-crm-Nav-Group";
newNavBar.innerHTML = '' + navBarDisplayName + ': Expanded, click to collapse';
newNavBar.appendChild(navBarItems);

if (crmNavBar.childNodes.length > 1) {
var secondNav = crmNavBar.childNodes[1];
crmNavBar.insertBefore(newNavBar, secondNav);
} else {
crmNavBar.appendChild(newNavBar);
}
}
}
};
};


The next snippet of code shows how to use the above code.

var demoNavBar = new JsNavBar("Demo");
demoNavBar.AddNavBarItem("navSubAct");
demoNavBar.AddNavBarItem("navRelationships");
demoNavBar.Display();


To obtain the id's of the NavBarItems on the form, the IE Developers Toolbar is a great tool and can be downloaded here: (http://www.microsoft.com/downloads/details.aspx?familyid=e59c3964-672d-4511-bb3e-2d5e1db91038&displaylang=en). Once installed, to use it, open a CRM entity form, press CTRL+N. A new window will open with the address and toolbars displayed. Select Tool->Toolbars-->Explorer Bar-->IE Developer Toolbar. It can be used the NavBarItem ids. See below.

Wednesday, October 28, 2009

Querying a Filtered View in a Custom Application with a Double Hop

Have you ever needed to directly query a filtered view in CRM directly from a custom web page? On occasion it may be necessary for performance reasons. One good example is in creating a hierarchical view of accounts. To do this, recursion is needed and doing so using the CRM Service is just not performant. Simply because of all the round trips through the CRM Service. A good solution is to use a SQL Server Common Table Expression (CTE) and ADO.NET. This way all the heavy lifting is done on SQL Server and only a single round trip to the database is needed.

This won't be demonstrated in this blog, but instead a solution for getting around the double-hop rule in a multi-server deployment where CRM and SQL Server are installed on separate machines. Two additional solutions are available; first a domain account with a CRM license can be used as the Application Pool account in IIS. The web application can use this application pool with impersonation disabled, and as long as the account has permission to the data that is queried, things will work fine. However, data row-level security is thrown out the door for the calling user, as the Application Pool account is used. A second solution is to use Kerberos with trusted delegation configured in Active Directory. This is a good solution and maintains data row-level security for the calling user, but it is no easy task to set up.

Another option is to use SQL Server's context_info. More information can be found here (http://msdn.microsoft.com/en-us/library/ms180125.aspx). CRM uses the context_info to set the calling user's systemuserid in double hop scenarios and every filtered view checks the context_info for the systemcuserid of the calling user in the function [fn_FindUserGuid] located in the organization database.

When setting up the custom application in IIS, the application pool can use the Network Service account or whichever service account was used to install CRM. Impersonation needs to be disabled as the custom application will use the Network Service account to login to SQL Server. This is possible as the account is added to the SQL Access Group when CRM is installed. With this basic configuration the custom application can access SQL Server, however, since the account is not a CRM user and not occupying a license it will not have access to any data. This is where the context_info comes into play.

The query below demonstrates how to use the context_info when querying a filtered view.















Pretty simple, except how do we get the calling user's systemuserid? This is where impersonation is used but in code. The authenticated user can be impersonated prior to calling the WhoAmIRequest using the CRM Service. The WhoAmIRequest will return the userid(guid) for the calling user. Impersonation can then be undone in code and prior to the database call.

Below is a webform that demonstrates how to do this.



ASPX:









runat="server" ID="ResultsGrid"
AutoGenerateColumns="true"
DataSourceID="CrmDataSource"
HeaderStyle-Font-Bold="true">


ConnectionString="Data Source=dev-sql02;Initial Catalog=Neudesic_MSCRM;Trusted_Connection=True;">




Code Behind:

namespace PureCRM.Samples.FilteredView
{
public partial class FilteredViewExample : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

protected void ExecuteButton_Click(object sender, EventArgs e)
{
CrmDataSource.SelectCommand = SetContextInfo(QueryText.Value);
ResultsGrid.DataBind();
}

private string SetContextInfo(string sqlQuery)
{
string contextInfoFormat = "declare @userid uniqueidentifier = '{0}' \rset context_info @userid\r{1}";
string systemUserId = GetCrmSystemUserId();
string sql = string.Format(contextInfoFormat, systemUserId, sqlQuery);

return sql;
}

private string GetCrmSystemUserId()
{
WindowsIdentity identity = (WindowsIdentity)Thread.CurrentPrincipal.Identity;
WindowsImpersonationContext impersonationContext = identity.Impersonate();

CrmAuthenticationToken token = new CrmAuthenticationToken()
{
AuthenticationType = AuthenticationType.AD,
OrganizationName = "Neudesic"
};

CrmService service = new CrmService()
{
Url = "http://dev-crm01/MSCRMServices/2007/CrmService.asmx",
UseDefaultCredentials = true,
CrmAuthenticationTokenValue = token
};

WhoAmIRequest request = new WhoAmIRequest();
WhoAmIResponse response = (WhoAmIResponse)service.Execute(request);

impersonationContext.Undo();

return response.UserId.ToString();
}
}
}