Inside Microsoft Dynamics CRM 3.0

Arne Janning

  Home  |   Contact  |   Syndication    |   Login
  3 Posts | 0 Stories | 14 Comments | 36 Trackbacks

News

Archives

MSCRM blogs I read

Saturday, January 28, 2006 #

So... I didn't post for a long time, I have been on holidays since new year, didn't have an internet connection for a long time - I actually write this from an internet cafe.

In my last post I promised to show how to get inside CRM and do customizations that go far beyond of what is supported. I also already mentioned the main entry point of the whole CRM-web-application, which is the Microsoft.Crm.MainApplication.Application_OnStart()-method in Microsoft.Crm.Application.Pages.dll. Microsoft.Crm.MainApplication is referenced in the global.asax-file in the CRM-webroot:

<%@ Application language="c#" Inherits="Microsoft.Crm.MainApplication" CodeBehind="Microsoft.Crm.Application.Pages.dll"%>

<%@ Application language="c#" Inherits="Microsoft.Crm.MainApplication" CodeBehind="Microsoft.Crm.Application.Pages.dll"%>

<%@ Application language="c#" Inherits="Microsoft.Crm.MainApplication" CodeBehind="Microsoft.Crm.Application.Pages.dll"%>

<%@ Application language="c#" Inherits="Microsoft.Crm.MainApplication.Application" CodeBehind="Microsoft.Crm.Application.Pages.dll"%>

We will write our own CRM-host to get inside CRM and get access to the internal CRM-object model at runtime. This is fairly easy.

First of all, create class library project in Visual Studio .NET 2003 - it must be .NET 1.1. In my example the project has the name "Janning.Crm.Host".

Then make sure you have added the following references:

  • System.dll
  • System.Data.dll
  • System.XML.dll
  • System.Drawing.dll
  • System.Web.dll
  • Microsoft.Crm.dll (from the GAC)
  • Microsoft.Crm.Application.Components.Application.dll (from \bin)
  • Microsoft.Crm.Application.Components.Core.dll (from \bin)
  • Microsoft.Crm.Application.Components.Platform.dll (from \bin)
  • Microsoft.Crm.Platform.Sdk.dll (from the GAC)

There is a small trick to copy assemblies from the GAC : Press Start --> Run and then enter C:\Windows\assembly\gac. The shell extension which is normally running on the assembly-folder won't show up then and you can easily copy and paste the CRM-assemblies and reference them from VS.NET.

Then you have to add the following code into a codefile:

using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security.Principal;
using System.Web;
using System.Web.SessionState;
using System.Xml;

using Microsoft.Crm;
using Microsoft.Crm.Application.Platform;
using Microsoft.Crm.Errors;
using Microsoft.Crm.Metadata;
using Microsoft.Crm.Security;
using Microsoft.Crm.Utility;

namespace Janning.Crm.Host
{
 public class CustomCrmApplicationHost : HttpApplication
 {
  protected void Application_Start(Object sender, EventArgs e)
  {
   RegControl.LoadLibraries();
   NotificationManager.ExtraParameter = HttpContext.Current;
   MetadataCacheConfig.LoadMethod = LoadMethod.Database;
   NotificationManager.StartNotificationsThread(new Notification());
  }

  protected void Application_AuthenticateRequest(Object sender, EventArgs e)
  {
   try
   {    
    UserCache.GetCurrentUser();
   }
   catch (Exception ex)
   {
    COMException comex = ex as COMException;
    string errorCode = "0xffffffff";
    if (comex != null)
    {
     errorCode = comex.ErrorCode.ToString("x", CultureInfo.InvariantCulture);
    }
    string redirectPath = string.Format(
     CultureInfo.InvariantCulture,
     "/_common/error/authenticationError.htm?0x{0}&{1}",
     errorCode,
     base.Request.Url.AbsoluteUri);
    base.Response.Redirect(redirectPath);
    base.Response.End();
    CrmTrace.TraceFormat(
     TraceCategory.Application,
     TraceLevel.Error,
     "An error occurred during the Application_OnAuthenticateRequest : \nError: {0} \nStack Trace:{1}",
     ex.Message,
     ex.StackTrace);
   }
  }

  protected void Application_Error(Object sender, EventArgs e)
  {
   base.Response.Clear();
   Exception ex = base.Server.GetLastError();
   ErrorInformation info = new ErrorInformation(ex, base.Request.Url);
   if (ConfigurationSettings.AppSettings["DevErrors"] != "On")
   {
    info.StackTrace = string.Empty;
   }
   if (info.Source == "XML")
   {
    base.Response.ContentType = "text/xml";
    ErrorInformation.XmlSerializer.Serialize(base.Response.OutputStream, info);
    base.Response.End();
   }
   else if (info.Source == "SOAP")
   {
    base.Response.Clear();
    base.Response.ContentType = "text/xml";
    base.Response.StatusCode = 500;
    XmlTextWriter writer = new XmlTextWriter(base.Response.Output);
    writer.WriteStartDocument();
    writer.WriteStartElement("soap", "Envelope", "
http://schemas.xmlsoap.org/soap/envelope/");
    writer.WriteStartElement("Body", "
http://schemas.xmlsoap.org/soap/envelope/");
    writer.WriteStartElement("Fault", "
http://schemas.xmlsoap.org/soap/envelope/");
    writer.WriteElementString("faultcode", "Server");
    writer.WriteElementString("faultstring", info.Description);
    writer.WriteStartElement("detail");
    ErrorInformation.XmlSerializer.Serialize(writer, info);
    writer.WriteEndDocument();
    base.Response.End();
   }
   else
   {
    HttpException httpEx = ex as HttpException;
    string maxRequestLenghText = "Maximum request length exceeded.";
    string pathFileName = Path.GetFileName(base.Request.Path);
    if ((pathFileName == "print_data.aspx") || (base.Request.Path == "/crmreports/download.aspx"))
    {
     base.Response.ClearHeaders();
    }
    if ((pathFileName == "importFieldMap.aspx") && (httpEx.ErrorCode == -2147467259) && ((ex.InnerException != null) ? (maxRequestLenghText == ex.InnerException.Message) : true))
    {
     base.Server.ClearError();
     base.Response.Redirect("/Tools/BulkImport/importFileChoose.aspx?errorMessage=BulkImport_Error_Exceed_MaxFileSize");
     base.Response.End();
    }
    else if ((pathFileName == "upload.aspx") && (httpEx.ErrorCode == -2147467259))
    {
     base.Server.ClearError();
     base.Response.Redirect("/_common/error/uploadFailure.aspx?hr=0x80043e08");
     base.Response.End();
    }
    else if (pathFileName == "print_data.aspx")
    {
     base.Server.ClearError();
     base.Response.Redirect("/_common/error/popuperror.aspx?hr=" +
      httpEx.ErrorCode.ToString());
     base.Response.End();
    }
    else if (ConfigurationSettings.AppSettings["DevErrors"] == "On")
    {
     //left this out for the moment
    }
    else
    {
     base.Response.Redirect(
      "/_common/error/errorhandler.aspx?errNum=" +
      HttpUtility.UrlEncode(info.Code) +
      "&errMessage=" +
      HttpUtility.UrlEncode(info.Description));
     base.Response.End();
    }
   }
  }
 }
}

Compile the code into an assembly, copy the assembly into the \bin-folder, make a backup of the global.asax-file and change the global.asax-file to this:

<%@ Application language="c#" Inherits="Janning.Crm.Host.CustomCrmApplicationHost" CodeBehind="Janning.Crm.Host.dll"%>

<%@ Application language="c#" Inherits="Janning.Crm.Host.CustomCrmApplicationHost" CodeBehind="Janning.Crm.Host.dll"%>

<%@ Application language="c#" Inherits="Janning.Crm.Host.CustomCrmApplicationHost" CodeBehind="Janning.Crm.Host.dll"%>

<%@ Application language="c#" Inherits="Janning.Crm.Host.CustomCrmApplicationHost" CodeBehind="Janning.Crm.Host.dll"%>

(If your assembly-name or namespace is different you have to change this of course)

Make an iisreset (Start --> Run --> iisreset) and open CRM again: http://yourCrmServerOrIP

Everything just works exactly like it did before, there is only one difference: if you use a tool like Process Explorer and look which dlls are loaded into the w3wp.exe-process you'll see that the Janning.Crm.Host.dll is actually running in the process. As the code in this assembly which is running in the most central part of CRM is now completely under your control you can do pretty much anything - if you know the internal object model. I'll write about this soon in detail and give you a couple of examples.

I should mention again that all this is of course completely unsupported by Microsoft, me or my future employer.

If someone wants to have the complete VS.NET-solution-files then just leave a comment here and I'll send it via email. I have no access to an FTP-server at the moment so I can't offer a download - perhaps someone has some empty space for me? :-)

(And please, could somebody explain me how to use the font sizes in .Text-Admin - I simply don't get it)