A little story about .GetType()

21. May 2010

Last week I was writing some code with a co-worker and a line of code similar to the following came up:

if (object.GetType() == typeof(T))
{
	// ... more code here.
}


The question came up, “doesn’t this use Reflection”?  My response was that the .GetType() method itself doesn’t use reflection, but instead evaluates the object’s metadata from the CLR at runtime and produces the relevant type information.

Then, I ran across this from TweetDeck today:

To the guy ("who is always right") that said GetType() doesn't use reflection: http://msdn.microsoft.com/en-us/library/ms173183(VS.80).aspx

Now, let’s not kid ourselves here, I’m certainly not the guy who is always right.  So, ready and willing to be wrong I started reading the rather innocuous post from MSDN on the subject:

Here's a simple example of reflection using the static method GetType - inherited by all types from the Object base class - to obtain the type of a variable:

C#

// Using GetType to obtain type information:
int i = 42;
System.Type type = i.GetType();
System.Console.WriteLine(type);
 

Indeed, MSDN does say that invoking .GetType() is an example of using Reflection.  My original comment that .GetType() didn’t use Reflection was based on my (inaccurate) definition of reflection – that an object is reflected upon when it becomes loaded into memory for the sole purpose of achieving this reflection.  I suppose the difference is one of perception, really, but here it’s clearly caused confusion.

Then I ran across this post supporting my assertion, that calling .GetType() in fact constructed the type information at runtime directly using a hook into the CLR.  Is this the same as reflection?  My original point that this wasn’t reflection was really one more of performance than the syntax.  I wonder what Ritchie Swann would say now regarding his original comments on the matter:

As for how it works - the GetType() function is marked with the special attribute [MethodImpl(MethodImplOptions.InternalCall)]. This means its method body doesn't contain IL but instead is a hook into the internals of the .NET CLR. In this case, it looks at the binary structure of the object's metadata and constructs a System.Type object around it.

The question of performance, in my mind, can really only be answered by doing a comparative analysis.  How much work does .GetType() do anyway, and is it the same as other methods of Type checking?  I decided to check it out.  Consider the following rather simple class:

namespace TypeReferenceChecking
{
    class Program
    {
        static void Main(string[] args)
        {
            TestIs();
            TestGetTypeIs();
            TestGetTypeEquals();
        }

        static bool TestIs()
        {
            var newObject = new SampleType();

            return newObject is SampleType;
        }

        static bool TestGetTypeIs()
        {
            var newObject = new SampleType();

            return newObject.GetType() is SampleType;
        }

        static bool TestGetTypeEquals()
        {
            var newObject = new SampleType();

            return newObject.GetType() == typeof(SampleType);
        }
    }

    class SampleType
    {
    }
}

Looking at the disassembled C# source for this assembly with Reflector, you’ll see the following:

internal class Program
{
    // Methods
    private static void Main(string[] args)
    {
        TestIs();
        TestGetTypeIs();
        TestGetTypeEquals();
    }

    private static bool TestGetTypeEquals()
    {
        SampleType newObject = new SampleType();
        return (newObject.GetType() == typeof(SampleType));
    }

    private static bool TestGetTypeIs()
    {
        new SampleType().GetType();
        return false;
    }

    private static bool TestIs()
    {
        SampleType newObject = new SampleType();
        return (newObject != null);
    }
}

At first glance, it appears that calling .GetType() in your code, in fact, calls the runtime method .GetType(), which MSDN says is reflection.  But what about the argument that .GetType() retrieves information from the CLR hooks itself, not from an image of the assembly containing the type.  I was dying to find out, so I used Reflector on the .GetType() method itself:

[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public extern Type GetType();

Sure enough, it’s exactly what Ritchie Swann eluded to.  Then I started thinking about the real question.  If .GetType() gets type information from the CLR and constructs a new instance of System.Type, how expensive is that construction? 

[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
protected Type()
{
}

It would be lovely to say that we get more answers here, but we don’t.  There is a static constructor on Type that initializes empty types, filters, etc. but no instance constructor.

So, the million dollar question, that really only Microsoft can say is:

For purposes of evaluating type comparison, is using .GetType() for an object that’s already in memory at runtime the same as evaluating type information from a type that’s not already loaded in memory?  If not, do you call both reflection?  This was the distinction that I really wanted to make the other day in my conversation with my coworker, that doing this runtime check is harmless and doesn’t cause excess work for the CLR (despite what it may be called).

I look forward to some great chats at TechEd with folks who will likely gladly weigh in.  Got your own opinion?  Comment below.

Development

Using SharePoint 2010 Beta 1 REST services on Windows 7 or Windows Server 2008 R2

27. February 2010

Today, I’m presenting this session from PDC at a local code camp.  In setting up my demo, I’ve been trying to get the SharePoint 2010 beta 1 server setup so that it can be queried using WCF Data Services (formerly ADO.Net Data services).  Whatever I try, I continue getting the following error:

WebHost failed to process a request.

Sender Information: System.ServiceModel.Activation.HostedHttpRequestAsyncResult/40906347
Exception: System.ServiceModel.ServiceActivationException: The service '/_vti_bin/listdata.svc' cannot be activated due to an exception during compilation.  The exception message is: Could not load file or assembly 'Microsoft.Data.Services, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' or one of its dependencies. The system cannot find the file specified..

The good news is that there is a solution for this problem.  The ADO.Net Data Services 1.5 CTP 2 runtime needs to be installed as well as a patch.

The runtime can be found at: http://www.microsoft.com/downloads/details.aspx?FamilyID=a71060eb-454e-4475-81a6-e9552b1034fc&displaylang=en#filelist

The patch is available at:
http://www.microsoft.com/downloads/details.aspx?familyid=79d7f6f8-d6e9-4b8c-8640-17f89452148e&displaylang=en#filelist

Development ,

Presenting today: Behind the Scenes of SalesForce.com and Microsoft Dynamics CRM 4.0: The Developer’s Story

12. November 2009

I’m doing a webinar today for my company, Tribridge, entitled “Behind the Scenes of SalesForce.com and Microsoft Dynamics CRM 4.0: The Developer’s Story”.  Long titles aside, I’m super excited about delivering this content to a technical audience. 

This is actually a follow-up presentation from a webinar that my colleague Seth Kircher did a few months back.  If you didn’t see the first part, you may want to watch it before attending today’s webinar.

Register for today’s webinar.

Community Events, Development

Silverlight 3 Linkapalooza

23. September 2009

So I’ve been a very, very busy bee with a bunch of projects, and there’s been no lack of well-deserved teasing for not posting to this blog in 8 months.  I guess the best way to silence the critics would be a good-old-fashioned link-off.  Here’s my list of high quality Silverlight 3 content that you’ve got to check out.

Showcase Sites & Samples

Expression Blend & SketchFlow

Interactivity & Behaviors

Application Lifecycle & Composition

Navigation

DataForm

Data Binding / Data Services (not RIA Services)

RIA Services

WPF / XAML cross-breeds

Connecting with the Silverlight Team

Development , ,

My “Chalkboard” Visual Studio 2008 Theme

27. January 2009

Many people around the office have taken notice of my Visual Studio 2008 theme, which I’ve affectionately called “Chalkboard”.  I put together this theme and then tweaked it over the past few months to make hours in front of Visual Studio 2008 easier on my eyes.  Here’s a screen grab.

The high-contrast colors (dark background, light foreground) take some getting used to, but they’re much easier on the eyes after you do.  For those who are interested, I’ve exported my Font and Color settings as a Visual Studio 2008 theme.

Download my Visual Studio 2008 “Chaklboard” theme.

Technorati Tags: ,

Development

Using Help Filters in Visual Studio

21. January 2009

Last night I was at the Tampa SQL Server User’s Group attending a good intro on VSTS Database Edition GDR presented by Catapult Systems.  After the session, one of the attendees asked me if there was any way to get rid of the SQL Server Compact Edition help from his Visual Studio help collection.  One way to do this is to use the provided help filters, which allow users to set preferences on what content they would like to see in help.  Here’s the link to MSDN that explains the filters:

Using Help Filters in Visual Studio (MSDN)

Another way to do this is to uninstall the specific content that you want removed from the collection.  Simply run the MSDN uninstaller from the Control Panel and modify your installation.

Development

Timeout and Workflow issues when using the Deployment Manager in Dynamics CRM 4.0

12. January 2009

I've been trying to re-deploy a CRM 4.0 installation from a client's site into our development domain so that I can work with some of the data in the system.  Using the provided CRM 4.0 Deployment Manager is pretty easy (instructions found here), but wasn't working. 

The deployment manager would run for a good 30+ minutes and eventually come back to me with a SQL Server Timeout error.  The details of this error follow:

Import Organization (Name={Name}, Id={ID} failed.

The changes made during Import Organization could not be rolled back. Please manually delete the Organization database from the 'SBCRM01' SQL Server if it was not successfully deleted.

System.Data.SqlClient.SqlException: Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
… 
   at Microsoft.Crm.Tools.Admin.ImportOrganizationInstaller.Import(Guid organizationId, String organizationUniqueName, String organizationFriendlyName, String sqlServerName, String databaseName, Uri reportServerUrl, String privilegedUserGroupName, String sqlAccessGroupName, String userGroupName, String reportingGroupName, String privilegedReportingGroupName, ICollection`1 users, MultipleTenancy multipleTenancy)

I Googled, hooked up SQL Server Profiler and searched Microsoft’s knowledge base – desperate for some sort of answer to this problem.  I finally found a forum post online that described a similar scenario being resolved by installing Microsoft Dynamics CRM 4.0 Update Rollup 1

It wasn’t until I looked at the deployment log file that I realized that the timeout expired error was really just a symptom of another problem.  Turns out that below the Timeout exception I found another exception message that describes what’s really going on.

Import Organization (Name={Name}, Id={ID}) failed with Exception:

Microsoft.Crm.CrmException: Unable to find metadata information for attribute {attribute_name}
   at Microsoft.Crm.Workflow.ObjectModel.MetadataProvider.GetAttributeMetadata(EntityMetadata entityMetadata, String attributeName)

   at Microsoft.Crm.Tools.Admin.ImportOrganizationInstaller.Import(Guid organizationId, String organizationUniqueName, String organizationFriendlyName, String sqlServerName, String databaseName, Uri reportServerUrl, String privilegedUserGroupName, String sqlAccessGroupName, String userGroupName, String reportingGroupName, String privilegedReportingGroupName, ICollection`1 users, MultipleTenancy multipleTenancy)

What the heck is this?  Turns out, the issue is caused by having workflow in CRM that references an attribute that is subsequently deleted.  Per Joel CustomerEffective (forum post), here’s some things you can do to resolve the issue:

1. Recreate the missing attribute (you can delete after import).
2. Delete any rows that reference that attribute from the workflowdependencybase table
3. Import the organization into an environment that is pre-rollup 1
4. Wait for KB958571 to be released--they are testing it now.  If you search for it, you will find the KB article for it, but the hotfix has been redacted pending further testing.

Of course, KB958571 talks about a hotfix that isn’t publically available.  Sad – really sad, but have no fear!  After wasting 6 hours today working on this problem, I’m still waiting on getting an answer from Microsoft about the availability of the patch.  I’ve attempted solution #2 (removing the dependencies from the source database before performing the import) without success.  Options #1 and #3 aren’t available to me at the moment.  If you know of a better solution, please post a comment to this blog!

Development ,

Troubleshooting guide for TFS installation

7. January 2009

Looks like the good folks in Redmond got some time to write up a much-needed troubleshooting guide for TFS server installations.  You can find the guide on MSDN using the following link:

Troubleshooting installation of Team Foundation

The guide goes over how to interpret the installer log files, as well as diagnosis for many common TFS errors.  Here's a list:

  1. Error 32000. The Commandline ‘[1]’ returned non-zero value: [2]
  2. Error 29112. Team Foundation Report Server Configuration: Either SQL Reporting Services is not properly configured, or the Reporting Services Web site could not be reached. Use the Reporting Services Configuration tool to confirm that SQL Reporting Services is configured properly and that the Reporting Service Web site can be reached, and then run the installation again. For more information, see the Team Foundation Installation Guide.
  3. Error 29109. Team Foundation Report Server Configuration: SQL Reporting Services configuration encountered an unknown error. Verify that you have sufficient permissions to configure SQL Reporting Services, and try again.
  4. Error 28806. An unexpected error occurred. Verify that SQL Server Reporting Services is installed and running on the Team Foundation app tier and that you have sufficient privileges to access it. For more information, see the setup log.
  5. Error 29000. An unexpected error occurred. Verify that SQL Server Reporting Services is installed and running on the Team Foundation app tier and that you have sufficient privileges to access it. For more information, see the setup log.
  6. Error 28805. The setup program cannot complete the request to the server that is running SQL Server Reporting Services. Verify that SQL Server Reporting Services is installed and running on the Team Foundation app tier and that you have sufficient permissions to access it. For more information, see the setup log.
  7. There is an error with the collation settings for SQL Server. The collation settings are not compatible with Team Foundation Server.


There are also sections for troubleshooting the installation of Team Build or Team Explorer in Visual Studio, as well as some helpful hints for post-installation pains.  This article has now taken the place of a dozen of my favorites -- thanks Microsoft!

Technorati Tags: ,,

Development , ,

10 Developer Resolutions for the New Year

2. January 2009

I'm not a big fan of New Year's Resolutions, but I've got some bad habits (like many of us) that I'd like to break, and some new good habits that I'd like to get into.  Here's my short list of changes for 2009.

10. Start using the keyboard more
Visual Studio has an extremely configurable keyboard command structure, so save a few seconds many times a day and stop reaching for the mouse!  Did you know that Control minus (-) will move the caret to it's previous location after navigating using "Go To Definition" (thanks NikitaP)?  A huge time savor.  There's literally hundreds of these buried throughout Visual Studio.

9. Write all my unit tests (first this time)
This year I started getting serious about writing unit tests for code.  It's so useful to have a known and accepted proof for a block of code, almost like a contract between a caller and a piece of logic.  This year I'll get more agile-like with my approach and start writing tests that spec out code, instead of the other way around.

8. Generate more code with T4
Like many developers, I've been using code generation tools since before .Net, and then with the .Net platform for many years now.  In 2008 I made the switch from CodeSmith to T4 -- and this year I'm going to beef up my usage with even more templates (heck -- maybe even a few from Oleg's T4 toolbox).

7. Remove people from my Twitter list
Image representing Twitter as depicted in Crun...Although Twitter is pretty cool, I follow too many people already.  I get alerts every time a developer dreams up a great new idea.  This year I'm going to save those interesting conversations for the bar and remove them from my desktop and cell phone -- despite the fact that statistics show this type of social behavior actually helps people be more productive.

6. Write more applications in Silverlight 2
Microsoft SilverlightIn 2008 I fell in love with Silverlight.  It really took me until PDC to get there, but I now see the wisdom in having a rich managed code environment on the client in a web application scenario.  Not to mention you can make some really cool-looking and functional UI's with this technology.  This year, I'm going to find more places for Silverlight in the line of business software I write.  The net effect should be a better user experience for our business users, a simpler development experience for developers and a lower cost of investment for stakeholders.

5. Leverage more of VSTS Database Edition
In 2008, Microsoft announced it would merge VSTS Developer and Database Edition SKU's in the next release of Visual Studio (VSTS 2010).  Since we're all mostly impatient, the good folks in Redmond were kind enough to give existing MSDN Subscribers who had access to one SKU access to the additional SKU.  There's so much great functionality in VSTS 2008 Database Edition (post coming soon) that there's no reason to ever hand-jam scripts for deployment scenarios, or copy production databases as test databases.

4. Use more Workflow
A well known acronym but little known technology inside the .Net Framework is Windows Workflow Foundation.  This powerful technology allows any number of processes to be designed in Visual Studio and then managed by the .Net Framework.  What's cool about Workflow is that it does the heavy lifting (managing state, conditional evaluation, etc.) for the developer so s/he can get back to coding the business stuff.

3. Frameworks are out and standards are in
If 2008 taught us anything it's that proprietary software frameworks are out (sorry Framework designers) and standardized open-source technologies are in.  The unfortunate side effect is that this means that there's a limited shelf life for that specialized do-it-yourself OR/M you've been working on for 5 years...  Instead, it's time to look to accepted and standardized approaches to solving these problems (LINQ to SQL, Entity Framework, etc.).  And the scenarios aren't just limited to Data Access.  Think ASP.Net Dynamic Data over your proprietary CMS platform, etc.  This year I'll be re-evaluating where Frameworks fit, how long I should expect to use them and where standards fit better.

2. Application Modeling is in
The next version of Visual Studio (VSTS 2010) contains a modeling definition language affectionately callled "M".  M (even in it's crippled state shipped with the early release bits we've seen so far) promises that architects and application designers will be able to effectively model application scenarios in a functional language that the holds value to the business.  What's this mean to developers?  Yet another paradigm shift on how we think about requirements and implementing details around requirements.  This year, I'll spend more time looking at what technologies will help me understand and build models for applications.

1. Services are in, components are out
It's taken me 5 years to finally get on board with the whole SOA acronym soup mix, but 2009 is the year of the service layer.  Why?  Well, it's taken this long for the client technologies to become sophisticated enough to consume WCF services in a meaningful way.  This year, more technologies exist that make the dream of write it once and use it everywhere a reality, so I'll be taking a long, hard look at traditional architecture patterns and evaluating where service endpoints fit instead of component code.

Do you have developer resolutions for the New Year?  I'd love to hear them.

Technorati Tags: ,

Development

Javascript debugging, intellisense and dynamic script loading in CRM 4.0

6. November 2008

One of the best features and biggest drawbacks to CRM 4.0's entity model is the ability to modify forms in CRM using Javascript.  While this level of customization is something any sizable implementation will need, it's been reduced to pasting a bunch of Javascript code into a text box in CRM.

Solution WindowNot that I've got anything against pasting code in a textbox in CRM, but this isn't exactly an integrated development experience.  In this post, I'll demonstrate how to have the onLoad() script in a CRM entity dynamically load one or more Javascript files (externally) and then run a load script once all the files have completely loaded.

First, let's talk about a little housekeeping.  The image to your left (click to enlarge) represents the solution explorer view of a web project I've created that contains an ISV solution for CRM.  There's some important things to note here.

First, I've got a folder called "Javascript".  This folder houses my site-wide Javascript, and the basis for the OnLoad() handler that I'll bind to each entity.  Let's take a look at some of the files in this folder.

CrmFormHelper.js Houses functions that help handle form events easily.  After loaded by our dynamic scripting engine, the form helper is available to any form in CRM via the document.FormHelper object.
CrmFormIFrame.js Houses functions that allow CRM to manipulate the IFRAME instance where a page appears
CrmWebService.js Houses functions that easily allow CRM web services to be consumed through Javascript.
DiagnosticsWindow.js When enabled, provides a trace window that shows events as they fire in CRM using the Javascript scaffolding in this article
OnLoad.js Provides a stock OnLoad() implementation for your entities
OnSave.js Provides a stock OnSave() implementation for your entities.


For the purposes of this post, I'm going to focus on CrmFormHelper and OnLoad.js.  What's in these fancy scripts, anyhow?  Let's take a look:

CrmFormHelper.js
  1: CrmFormHelper = function() {
  2: 
  3:     //Trace - Start
  4:     var TraceWindowHandle = null; var TraceLastTrace = new Date(); function TraceClear() { if (TraceWindowHandle == null) return; alert(TraceWindowHandle.document.body.innerHTML); TraceWindowHandle.document.body.innerHTML = '<body>'; TraceWindowHandle.document.writeln('<a href="javascript:opener.TraceClear();">Clear Window</a>'); } function Trace(cat, message) { if (typeof TraceWindowEnabled == 'undefined') return; message = message.split('<').join('&lt;'); message = message.split('>').join('&gt;'); if (TraceWindowHandle == null) { TraceWindowHandle = open('', 'TraceWindow', 'width=500,height=500,location=no,menubar=no,resizable=yes,left=200,top=20,directories=no,status=no,scrollbars=yes'); TraceWindowHandle.document.writeln('<a href="javascript:opener.TraceClear();">Clear Window</a>'); } try { date_now = new Date(); elapsedMS = date_now.getTime() - TraceLastTrace.getTime(); TraceLastTrace = date_now; TraceWindowHandle.document.writeln('<br>' + date_now + '(' + elapsedMS + ') ' + ':' + cat + ':' + message); TraceWindowHandle.window.scroll(0, TraceWindowHandle.document.body.scrollHeight + 10); } catch (err) { TraceWindowHandle = null; } } function TraceWriteRaw(markup) { if (typeof TraceWindowEnabled == 'undefined') return; if (TraceWindowHandle == null) { TraceWindowHandle = open('', 'TraceWindow', 'width=500,height=500,location=no,menubar=no,resizable=yes,left=200,top=20,directories=no,status=no,scrollbars=yes'); TraceWindowHandle.document.writeln('<a href="javascript:opener.TraceClear();">Clear Window</a>'); } try { TraceWindowHandle.document.writeln(markup); } catch (err) { TraceWindowHandle = null; } }
  5:     //Trace - End
  6: 
  7:     CrmFormHelper.prototype.IsEditForm = function() {
  8:         var CRM_Form_TYPE_EDIT = 2;
  9: 
 10:         if (crmForm.FormType == CRM_Form_TYPE_EDIT)
 11:             return true;
 12:         else
 13:             return false;
 14:     }
 15: 
 16:     CrmFormHelper.prototype.HandleFormOnLoad = function() {
 17:         Trace('HandleFormOnLoad', 'Starting');
 18:         
 19:         var CRM_Form_TYPE_CREATE = 1;
 20:         var CRM_Form_TYPE_EDIT = 2;
 21: 
 22:         switch (crmForm.FormType) {
 23:             case CRM_Form_TYPE_CREATE:
 24:                 if (this.OnFormLoadCreate != null) {
 25:                     Trace('HandleFormOnLoad', 'Calling OnFormLoadCreate');
 26: 
 27:                     try {
 28:                         this.OnFormLoadCreate();
 29:                     }
 30:                     catch (e) {
 31:                         Trace("HandleFormOnLoad", "Error while calling OnFormLoadCreate: " + e.Description);
 32: 
 33:                         debugger;
 34:                     }
 35: 
 36:                     Trace('HandleFormOnLoad', 'Done Calling OnFormLoadCreate');
 37:                 }
 38: 
 39:                 break;
 40:             case CRM_Form_TYPE_EDIT:
 41:                 if (this.OnFormLoadEdit != null) {
 42:                     Trace('HandleFormOnLoad', 'Calling OnFormLoadEdit');
 43: 
 44:                     try {
 45:                         this.OnFormLoadEdit();
 46:                     }
 47:                     catch (e) {
 48:                         Trace("HandleFormOnLoad", "Error while calling OnFormLoadEdit: " + e.Description);
 49: 
 50:                         debugger;
 51:                     }
 52: 
 53:                     Trace('HandleFormOnLoad', 'Done Calling OnFormLoadEdit');
 54:                 }
 55: 
 56:                 break;
 57:         }
 58: 
 59:         Trace('HandleFormOnLoad', 'Ending');
 60:     }
 61: 
 62:     CrmFormHelper.prototype.OnFormLoadCreate = function() {
 63:         Trace('OnFormLoadCreate', 'Default implementation called - no action taken');
 64:     }
 65: 
 66:     CrmFormHelper.prototype.OnFormLoadEdit = function() {
 67:         Trace('OnFormLoadEdit', 'Default implementation called - no action taken');
 68:     }
 69: 
 70: 
 71:     CrmFormHelper.prototype.HandleFormOnSave = function() {
 72:         Trace('HandleFormOnSave', 'Starting');
 73:         if (crmForm.IsDirty) {
 74:             Trace('HandleFormOnSave', 'Calling OnFormSaveDirty');
 75:             if (!this.OnFormSaveDirty()) {
 76:                 Trace('HandleFormOnSave', 'OnFormSaveDirty setting return value to false, since onsavedirty returned false');
 77:                 // Cancel the save operation.
 78:                 event.returnValue = false;
 79:             }
 80:             Trace('HandleFormOnSave', 'Done Calling OnFormSaveDirty');
 81:         }
 82:         else {
 83:             Trace('HandleFormOnSave', 'Calling OnFormSaveClean');
 84:             this.OnFormSaveClean();
 85:             Trace('HandleFormOnSave', 'Done Calling OnFormSaveClean');
 86:         }
 87:         Trace('HandleFormOnSave', 'Ending');
 88:     }
 89: 
 90:     CrmFormHelper.prototype.OnFormSaveDirty = function() {
 91:         Trace('OnFormSaveDirty', 'Default implementation called - no action taken');
 92:     }
 93: 
 94:     CrmFormHelper.prototype.OnFormSaveClean = function() {
 95:         Trace('OnFormSaveClean', 'Default implementation called - no action taken');
 96:     }
 97: 
 98:     CrmFormHelper.prototype.GetDefaultLookupValue = function(guid, type, label) {
 99:         var lookupItem = new Array();
100:         lookupItem[0] = new LookupControlItem(guid, type, label);
101:         return lookupItem;
102:     }
103: }
104: 
105: document.FormHelper = new CrmFormHelper();


What does all this code do, anyhow?  Well, basically, we're creating a Javascript class that can responsibly handle form events (load and save).  This means that a different function can be called on Create or Edit mode for a form, and a different function can be called based on the form's state (clean or dirty) upon save.  You'll also notice a bit of error handling code on each call, where we'll dump failures to the Trace window and launch the debugger.  In our setup, all of the code above lives in an external Javascript file.  This means, using Visual Studio 2008, you can set a breakpoint in any portion of this code and get full debugging support.

But how do I get this external code to load when my entity's form is displayed?  Basically, you can copy and paste the following 40 lines of code in your entity's onLoad handler.  Let's take a closer look.

  1: function onLoad() {
  2:     window.DynamicScripts = new Array();
  3:     window.DynamicScriptsLoaded = 0;
  4: 
  5:     // *** BEGIN EDITS HERE *** Add any scripts you want to be loaded when the page begins, CrmFormHelper.js is required.
  6:     window.DynamicScripts[0] = "/ISV/Custom/Javascript/CrmFormHelper.js";
  7:     window.DynamicScripts[1] = "/ISV/Custom/Javascript/CrmWebService.js";
  8:     window.DynamicScripts[2] = "/ISV/Custom/Price List/Javascript/PriceListForm.js";
  9:     // *** END EDITS HERE ***
 10: 
 11:     //Trace - Start
 12:     var TraceWindowHandle = null; var TraceLastTrace = new Date(); function TraceClear() { if (TraceWindowHandle == null) return; alert(TraceWindowHandle.document.body.innerHTML); TraceWindowHandle.document.body.innerHTML = '<body>'; TraceWindowHandle.document.writeln('<a href="javascript:opener.TraceClear();">Clear Window</a>'); } function Trace(cat, message) { if (typeof TraceWindowEnabled == 'undefined') return; message = message.split('<').join('&lt;'); message = message.split('>').join('&gt;'); if (TraceWindowHandle == null) { TraceWindowHandle = open('', 'TraceWindow', 'width=500,height=500,location=no,menubar=no,resizable=yes,left=200,top=20,directories=no,status=no,scrollbars=yes'); TraceWindowHandle.document.writeln('<a href="javascript:opener.TraceClear();">Clear Window</a>'); } try { date_now = new Date(); elapsedMS = date_now.getTime() - TraceLastTrace.getTime(); TraceLastTrace = date_now; TraceWindowHandle.document.writeln('<br>' + date_now + '(' + elapsedMS + ') ' + ':' + cat + ':' + message); TraceWindowHandle.window.scroll(0, TraceWindowHandle.document.body.scrollHeight + 10); } catch (err) { TraceWindowHandle = null; } } function TraceWriteRaw(markup) { if (typeof TraceWindowEnabled == 'undefined') return; if (TraceWindowHandle == null) { TraceWindowHandle = open('', 'TraceWindow', 'width=500,height=500,location=no,menubar=no,resizable=yes,left=200,top=20,directories=no,status=no,scrollbars=yes'); TraceWindowHandle.document.writeln('<a href="javascript:opener.TraceClear();">Clear Window</a>'); } try { TraceWindowHandle.document.writeln(markup); } catch (err) { TraceWindowHandle = null; } }
 13:     //Trace - End
 14:         
 15:     TraceWindowEnabled = true;
 16:     Trace("Dynamic Scripts", "Script loading started");
 17:     
 18:     for (i = 0; i < window.DynamicScripts.length; i++) {
 19:         var script = document.createElement("SCRIPT");
 20: 
 21:         script.language = "javascript";
 22:         script.src = window.DynamicScripts[i] + "?nocache=" + Math.random();
 23:         script.onreadystatechange = function() {
 24:             if (this.readyState == "loaded" || this.readyState == "complete") {
 25:                 window.DynamicScriptsLoaded += 1;
 26: 
 27:                 Trace("Dynamic Scripts", this.src + " " + this.readyState);
 28: 
 29:                 if (window.DynamicScriptsLoaded == window.DynamicScripts.length) {
 30:                     Trace("Dynamic Scripts", "Script loading complete");
 31: 
 32:                     if (document.FormHelper != null)
 33:                         document.FormHelper.HandleFormOnLoad();
 34:                 }
 35:             }
 36:         }
 37: 
 38:         document.getElementsByTagName("HEAD")[0].appendChild(script);
 39:     }
 40: }
 41: 


Basically, lines 5-9 of the above snippet specify which external script files should be loaded before the page's CrmFormHelper's load methods are called.  You can add any number of Javascript files here.  Lines 18-36 dynamically build <SCRIPT> tags and add them to the header.  At this point when all the scripts are loaded, then (and only then) is the FormLoad() fired (line 33).  This guarantees that all of your script libraries are available to your OnLoad script.

So, what happens when the page loads?  Looking at line 9 of the above code snippet, we see that one of the external javascript files is "/ISV/Custom/Price List/Javascript/PriceListForm.js".  This external Javascript file is real small and houses 2 different functions that fire when my page loads (one for edit mode, one for creation). 

  1: /// <reference path="../../Javascript/CrmFormHelper.js" />
  2: /// <reference path="../../Javascript/CrmWebService.js" />
  3: /// <reference path="../../Javascript/Intellisense/new_custompricelist.js" />
  4: 
  5: document.FormHelper.OnFormLoadCreate = function() {
  6:     alert("Form create was called");
  7: }
  8: 
  9: document.FormHelper.OnFormLoadEdit = function() {
 10:     alert("Form edit was called");
 11: }


What's most important about this file is the first 3 lines.  Those comments actually tell Visual Studio 2008 to load the specified Javascript files (which are loaded using our DynamicScript above) for interpretation in Intellisense.  Yes, you got the right -- by referencing those external scripts in the first 3 lines of this file, you'll get full Intellisense support.

That concludes this post.  In summary, using external Javascript files gets us a first class debugging experience in Visual Studio.  Try it!

Technorati Tags: ,,

Development , ,