When reading the CRM newsgroups you often find questions like "How can I make JavaScript functions available in all forms" or "Is it possible to change the color of a row in a view". There are two types of answers:
If you install an HTTP module, it is invoked on each request, so after all of the server side processing is done, the response stream is passed to all HTTP modules. After all of them have finished, the final result is passed to the client. Each HTTP module can change the content of the stream, so let's start to build a module allowing us to include additional JavaScript include files and links to cascading style sheets.
This configuration file must be placed in /MyConfig/config and must be named config.xml. However you can change that in the Config class:
- Not in any supported way.
- Use an HTTP module.
What is an HTTP module?
An HTTP module is an extension for IIS and it's general purpose is to hook into the stream between IIS and the client to log or modify data. A request to a file on the web server is first served by IIS. It loads the file and if it contains server code (ASP, ASP.NET), the code is executed. Once that's done, the document is sent to the client.If you install an HTTP module, it is invoked on each request, so after all of the server side processing is done, the response stream is passed to all HTTP modules. After all of them have finished, the final result is passed to the client. Each HTTP module can change the content of the stream, so let's start to build a module allowing us to include additional JavaScript include files and links to cascading style sheets.
Implementing the IHttpModule interface
Creating an HTTP module is very easy. All you have to do is
to implement the System.Web.IHttpModule interface. It only has two methods,
Init and Dispose. Unless you have something to dispose, you only need to care
about the Init method. It is called once and serves as the general
initialization routine. You use the context parameter to add event handlers and
they are invoked whenever a request is made. That's said an HTTP module must be
fast, otherwise it may slow down your server dramatically.
Here's the implementation I'm using is this sample:
Our
only subscription is BeginRequest (our OnBeginRequest event handler). Once
applied, the OnBeginRequest method is called for every single file requested by
a client, containing images, JavaScript files, style sheets, HTML controls and
of course ASP.NET pages. We all know that CRM forms are ASP.NET web pages and
they all have an extension of ".aspx". Only these must be evaluated
and therefore we only apply a filter for .aspx pages.
Our only subscription is BeginRequest (our OnBeginRequest event handler). Once applied, the OnBeginRequest method is called for every single file requested by a client, containing images, JavaScript files, style sheets, HTML controls and of course ASP.NET pages. We all know that CRM forms are ASP.NET web pages and they all have an extension of ".aspx". Only these must be evaluated and therefore we only apply a filter for .aspx pages.
What is a filter?
A
filter (the context.Response.Filter) is a Stream. It is not a standard stream,
like a FileStream or a MemoryStream. It is a class deriving from the abstract
Stream class. If a filter is applied, the stream operations are passed to the
filter instead of using the response stream directly.
Here's
the interesting part of the CrmFilter class:
What to include?
The easiest implementation would be to include a single link to a JavaScript file in all ASP.NET pages. However I thought it would be better to differentiate a bit and added a very simple configuration file:This configuration file must be placed in /MyConfig/config and must be named config.xml. However you can change that in the Config class:
string configFilePath = webSitePath + "MyConfig\\config\\config.xml";
The above configuration file defines the following:
- Include files listed in the All section are added to all HTML files. In the sample configuration above, the following line is injected into the head of the html document: <link rel="stylesheet" type="text/css" href="/MyConfig/config/css/styles.css">.
- Include files listed in the AllEntities section are added to all CRM forms. That's the reason why I'm looking for the entity type name when parsing the HTML content. In the sample configuration above, the following line is injected into the head of the html document:<script language="javascript" src="/MyConfig/config/scripts/global.js" />
- Finally, include files listed in the Entities section are added only if the HTML document is a CRM form of the specified type. In the sample configuration above, the following line is injected: <script language="javascript" src="/MyConfig/config/scripts/account.js" />.
You
can add as many entries as you like. The xml structure simply is a serialized
Config class and the "Current" accessor deserializes it. Feel free to
change it to your needs. Also note that I have included some very basic
logging. By default it logs to C:\CRMModule\log.txt and only when running the
debug version. You can change this in the Log class.
How to install it
You have to place the assembly into two locations on your CRM server:
- /bin
- /mscrmservices/bin
If you have modified the web to contain additional bin directories, you may have to copy them in there too. As an alternative you can add the assembly to the GAC. For this reason I added a strong name to the assembly, as unsigned assemblies are not allowed in the GAC.
Finally, open the web.config in the CRM root and make the highlighted changes:
<system.web>
...
<httpModules>
<add name="MyConfig.Crm.HttpModule" type="MyConfig.Crm.HttpModule, MyConfig.Crm.HttpModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1ed2e80199a69f48"/>
</httpModules>
</system.web>
...
<httpModules>
<add name="MyConfig.Crm.HttpModule" type="MyConfig.Crm.HttpModule, MyConfig.Crm.HttpModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1ed2e80199a69f48"/>
</httpModules>
</system.web>
Note
that there may be two system.web sections, one for the main CRM application and
one for the reporting services, which is identified by the following xml
element:
<location
path="Reports">
Be
sure to add the module to the main CRM configuration and do not put it into the
reports section.
And finally: How to test it
Create
a new JavaScript file and add the following function:
function MyConfig_Test(message)
{
alert(message);
}
alert(message);
}
Save
it at /MyConfig/config/scripts/global.js.
You
can of course use any name for your function, but to not conflict with
functions in other include files, I'm using a unique prefix. I leave it up to
you to follow that or not.
Use
the following configuration file (/MyConfig/config/config.xml):
<?xml version="1.0" encoding="utf-8"
?>
<Config xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<All/>
<AllEntities>
<Include href="/MyConfig/config/scripts/global.js" type="js" />
</AllEntities>
<Entities/>
</Config>
<Config xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<All/>
<AllEntities>
<Include href="/MyConfig/config/scripts/global.js" type="js" />
</AllEntities>
<Entities/>
</Config>
Open
a CRM form and add the following to your OnLoad event:
MyConfig_Test("It works!");
If it doesn't work, try
resetting IIS. If it indeed does work and you come across some cool functions,
I would love to see them to build some kind of function repository that
everyone can download (of course the poster will get full credits for it).