Request a topic or
contact an Arke consultant
404-812-3123
August 2010

Arke Systems Blog

Useful technical and business information straight from Arke.

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Archive

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2024

How to do useful things with CRM: Put a button on a page

Sometimes it’s hard to tell where to start with CRM.  So, a goal oriented example: Lets add a button to do something on a CRM form.

First, a plug for two things:

  1. The .chm file in the sdk is fantastic. Read it. Ditto the examples in the example directories.
  2. Developing ISV Applications using Microsoft Dynamics CRM 4.0 has some high level concept overview info when you have some time to read.  It can help you to know what different concepts exist. 

On to our task:

Step 1: Make a decision.  We actually have four different ways we can add a button on a CRM form.  Yes, 4.  Here they are:

  1. Add an iframe pointing to a page under /ISV.  /ISV is a folder MS has designated for Independent Software Vendors to add new pages to CRM.
    1. Advantages:
      • Lets you use all of your normal C#/ASP.NET tools.
      • User is already logged in to the CRM website, so no worry about authentication.
      • Probably the easiest for a C# developer to understand.
    2. Disadvantages:
      • It’s an iframe. Iframes can behave weird, particularly with regard to sizing. They have to be square.
      • Offline Outlook client does not automatically sync files under /ISV.  If you need to support offline users with features backed by /ISV pages, you need to deal with making and distrubing a separate installer package for your pages.
      • Iframe loads separately from your main page.
      • Separately addressable via url, a user can bookmark just your iframe.
      • Might not be an option depending on your hosting.
  2. Add an iframe pointing to a page running in a separate web app
    1. Advantages:
      • A separate site can be easier to configure and set up, you get your own web.config file.  (it’s best to avoid relying on putting things in CRM’s web.config)
      • A separate site can have separate access control, such as be part of a public facing website.
      • Always an option.
    2. Disadvantages:
      • Same as #1, plus:
      • Depending on what your page will be doing, you may have to deal with user authentication in some way.
      • “Same domain security model” – you can cause headaches for yourself if you use a separate webserver.  By default javascript can only talk back to the server it was served from.  If you want to have javascript talk to the CRM server, that javascript needs to be served from the CRM server.   If you want to have javascript talk to a different server, that javascript needs to be served from the other server.  (via a script src= tag).  So you can end up with needing to logically split your code between the CRM form and a separate .js file on a different server, and it can get hard to keep straight.
  3. Add a button to the menu along the top of a CRM form.  This is done by editing isv.config, which is a configuration file MicroSoft provides for you to customize some aspects of CRM appearance and behavior.  You can add a new button in this file, and have it call a snippet of Javascript, and it will automatically show up on your page.
    1. Advantages:
      • Consistent interface, lots of things in CRM work via buttons on the menu at the top of the form.
      • Offline Outlook clients sync up the isv.config file, so you can deal with them consistently this way.
      • Always an option.
    2. Disadvantages:
      • Few choices – you get a button added to the right of the current menu.
      • If you add too many items on the menu, the labels will be removed to make room.
      • Sometimes it isn’t obvious to a new user that they should look up there.  Sometimes you really want a button somewhere in particular in the form.
  4. Add a button via javascript somewhere in the page.  This is done by adding javascript to the form’s onload and directly adjusting the DOM. 
    1. Advantages:
      • You can put the button right where you want in the page.
      • Offline Outlook clients sync up with javascript on the form.
      • Always an option.
    2. Disadvantages:
      • Working in JS directly on the form is hard to test.  Lots of save/publish/reload/wait.
      • JS directly on the form is stored in the DB.  Hard to version control.
      • Relying on the exact layout of things in the DOM is generally unsupported and likely to break one day with an MS upgrade.
      • (You can work around both of these problems: I use a variant of Joris Kalz’s JScript Export Tool to dump jscript to the filesystem where I then check it into revision control.  And Stunnware has a JavaScript Factory that looks great but I haven’t tried it yet.

Ok. Now, knowing a bit about what we’re getting into, we’re ready to start. 

For now, we’re picking option #1, an ISV page, because it’s very very straightforward and probably has the fewest gotchas.

Things to know about an ISV page:

  • /ISV is basically the only spot in CRM that MicroSoft has said that you can put things and they won’t be touched.

Originally you had to put your dlls in the main /bin or in the GAC, but very early on MS fixed it so that you could use /ISV/[yourapp]/bin.  You might still find some old pages saying you have to GAC it or use the main /bin while you are searching.

So, starting with a simple example, setting up.

Life will be easiest if you have a copy of CRM that you can work directly on.  Lacking that, make good backups, and then life will be second easiest if you can at least get filesystem access, say via a mounted drive.

First, start a download of crm sdk.  You’ll want it handy for the help file and examples, they are quite good, and often better than what you’ll find on googling.  I google it every time in case a  new one has come out since I last started a project. 

Now, a new web application project in visual studio.  This will hold our ASP.NET page that will end up in an IFrame when we are done:

tempsnagitfile

Lets do some plumbing:

Add references to the CRM Sdk.  There are three ways to do this.  (MSDN on this topic)  My opinion:

  1. Use the web services by adding a web reference and pointing it to CRM’s wsdl files. 
    1. Advantages:
      • You get static typed classes including all of your customized classes and customized attributes.  (I am strongly on the side of this being a fantastically good thing. Others don’t agree.)
    2. Disadvantages:
      • If you work with plugins, you’re not supposed to use this approach, so you’ll need to learn a separate way to work with DynamicEntity.
      • CRM WSDL uses weird wrapper types like CrmBoolean and have tricky gotchas like you can’t just null things by setting them to null.
  2. Take the CRM SDK dlls from the SDK you downloaded and add them as references.
    1. Advantages:
      • This is what you’ll see for plugins, so if you do it for your webpages too you’ll be used to the syntax.
      • Your code will be more flexible and easier to deploy on new CRM instances with different customizations.  If you want to write a generic tool, don’t pick choice 1.
    2. Disadvantages:
      • You don’t get to use the static typed classes unless you’re willing to limit yourself to the built-in non-customized versions. (Avoid these like the plague because if you try to mix them together you can force it to compile via casting foo, but good luck making it actually work.  If using the CRM SDK dlls, I always stick 100% to using just DynamicEntity.)
      • DynamicEntity is essentially a glorified hashtable with some annoyingly confusing tricks to it.  The compiler can’t catch typos in hashtable key strings for you, so you’ll be relying on more tricks like calling ToString() on enum values.
      • Your code will take longer to write and debug than option #1 or #3.
  3. Feed your CRM WSDL to the latest and greatest CRM SDK tool to get strongly typed entities that are friendlier than method #1 – the weird wrapper types are gone, and you can use LINQ.  Last I checked, however, this still wasn’t meant for plugins, so you still need dynamic entity if you work with plugins.  Personally, I have clients with a lot of plugins, so I haven’t spoiled myself with this API yet because I don’t want to get too used to it.

We’re going to start with option #1, because it’s reasonably quick to develop but we still learn a good bit of CRM guts.  Also we will be able to understand all of the current example code we find on the internet in blogs and in the sdk itself.  The new CRM SDK is still too new to have many good examples available.  Make a promise to yourself now that you’ll learn it in a few months.

There are two web services. One is “CrmSdk”, the other is “MetadataSdk”.  We’ll ignore metadata for now; just know that if you need to learn what values are valid for a given entity, or what values you can choose for a picklist, the metadata is where you would look.  Often if you are doing a client-specific custom app you can completely ignore it; if you are doing a more general purpose app it will be utterly essential.

(See “Accessing the Server Using the Web Service” in the help file.)

(Note: Some versions of Visual Studio have hidden ‘web references’ as an extra link you click under service references. You want to add a web reference.)

temp

CRM publishes its WSDL at http://<servername>[:<port>]/mscrmservices/2007/crmservice.asmx

By convention, name it CrmSdk:

t

Next up, grab the helper classes from the CRM SDK and add them to your project.  They add a bunch of useful utility methods to the CrmSdk for you.  The directions might be out of date.

t[6]

drag_from_sdk_to_solution

The namespace doesn’t match and needs to be fixed up.  If you take a look at your CrmSdk Web Reference, you’ll see it has namespace projectname.WhateverYouTypedOnAddWhichShouldBeCrmSdk.  Search-and-replace on helpers to match that.

namespace

 search_and_replace_namespace

You now have the most basic skeleton for talking to CRM.  You can build to be sure you got it right so far:

build_succeeded

Now we need a web page:

add_new_item

new_web_form

First things first, lets add the Assembly we were told to add back at the very beginning to point to our dll in the bin directory (from “Custom Code Best Practices” page). It needs to be the first directive, even before page.

<%@ Assembly Name="arkecrmexample.dll" %>

 

 

You can drag your example button from the toolbox or manually type it in. I usually manually type the main asp:button id= runat= part, then switch to designer to get properties to add the onclick because I forget the exact parameter types sometimes.  If you doubleclick on the event in the properties:

clickproperty

You get in your codebehind :

protected void btnExample_Click(object sender, EventArgs e)
{

}

Now I want to do something.

Crm Sdk has an example of a utility for getting a reference to the Crm Server in sdk\server\reference\cs\crmserviceutility.cs , but it’s unfortunately rather limited.  We’ll still start with it as the basis for making our own utility class, just like everybody else who has ever started working in Crm. 

Drag a copy to your helpers folder, change the namespace on it, and drop the using CrmSdk at the top since it’s in the right package now.  The two Metadata methods won’t compile unless you add metadata sdk; I comment them out for now.

crmserviceutility

using System.Text;
/* removed using CrmSdk and Metadata Sdk statements */

namespace arkecrmexample.CrmSdk /* used to be Microsoft.Crm.Sdk.Utility*/
{
    public class CrmServiceUtility

Now, lets make our button talk to CRM for something. We’ll send a simple ‘WhoAmI’ request.

Because this is a blocking service call it probably should be done as an async call, but we’ll do it the quick way for now and do a future blog post about changing it to an async page.

Add a using statement for our CrmSdk:

using arkecrmexample.CrmSdk;

See sdk\server\reference\cs\misc\whoami.cs and snag its code, put it into our button click for now.  Remove the MS namespace on your CrmServiceUtility call.

/* used to be Microsoft.Crm.Sdk.Utility.CrmServiceUtility*/
CrmService service = CrmServiceUtility.GetCrmService(crmServerUrl, orgName);
service.PreAuthenticate = true;


// Create the request object.
WhoAmIRequest userRequest = new WhoAmIRequest();

// Execute the request.
WhoAmIResponse user = (WhoAmIResponse)service.Execute(userRequest);

Now we need a server url and an org name.

Note that CRM servers often have two URLs, an internal (“Active Directory” authentication) and an external (“Internet Facing Deployment”, or IFD, which authenticates using asp.net web forms).  There is also different authentication for ‘partner hosted’ and ‘microsoft hosted’

We are going to punt on this as a topic for later and just look at the Active Directory authentication for now.  There are examples in the sdk of the other hosting types.

The easiest way to check your orgName is to load CRM and grab the first directory name.

I am evilly hard-coding these things for now because configuration is very slightly harder than web.config.  We’ll change this to a config file in a future blog post.  For now:

            string crmServerUrl = "arkecrm01";
            string orgName = "ArkeCRM";

Finally, we want to do something with the result. Stock asp.net, I add a literal and set its text.

ASPX:

    <div><asp:Literal ID="output" runat="server" Mode="Encode" /></div>

Code Behind, I added:

if (user != null)
{
    success = true;
    // this tostring is ugly, see http://blog.arkesystems.com/post/2010/08/Microsoft-Dynamics-CRM-40-ndash3b-Guids-ToString-ndash3b-Even-simple-things-can-be-complicated.aspx 
    // for a nice extension method for it.
    output.Text = "User Id is " + user.UserId.ToString("B", System.Globalization.CultureInfo.InvariantCulture).ToUpper(System.Globalization.CultureInfo.InvariantCulture);

}

 

Now, we’re almost done.  A brief word on authentication, then on to putting the page on our server.

The CRM helper method we used authenticated to CRM using ‘System.Net.CredentialCache.DefaultCredentials’, which means it told the CRM SDK that it is whoever is running the code. 

CRM web servers run with identity impersonate="true" in their web.config, which means that the person who is running the code will automatically appear to be whoever is viewing the CRM website.

That is fine for the specific situation we are in here - /ISV page on the CRM server, active directory install.  If we were doing the page outside of /ISV on a separate web server, or dealing with something like Internet Facing Deployment, we would net to get fancier on identifying the user.  One thing you can do for some calls is just always make the call as a particular crm user.  Another thing is CRM has an “Impersonate” feature, where you log in to the server as an admin user but then impersonate the actual user performing an action.  Search the help file for ‘Impersonation’ when you find yourself in a more complicated situation.

Ok.  Time to compile and put this on our web server.

CRM usually runs at c:\inetpub\wwwroot.  If you aren’t sure where it is, take a look at the IIS config on the web server.  If you have multiple CRM web servers, you’ll want to deploy your code to each of them.

We want to make a subdirectory under /ISV.

This subdirectory will contain our aspx and a bin folder.

The bin folder will contain our dll.

Important note: I am *not* copying my web.config.  web.config causes headaches under isv.  If you do copy it, you’ll have to remove some things.  In particular the default

<authentication mode="Windows" />

will cause trouble.  But just avoid your web.config for now.  You’re running under CRM, you have its web.config.  We’ll do application settings in a separate config file somewhere else soon.

So, I’m manually publishing these files for now. Later we’ll look at post-build steps to automate.  I could also just straight copy these files instead of using publish.

I opened up explorer, found my ISV directory, and made a directory for my app. 

Now back in visual studio I *deleted my web config entirely* – it’s trouble and I don’t want it there.  Now I right click on the project and pick publish.

The most important thing here is to triple check that path and be absolutely sure that I am publishing to a ISV\directory and not to the root.  Seriously.  Triple check.  Then go back up CRM.  Then check again.

publish

After checking, again, I publish.

Explorer looks good, just what I expected:

explorer_after_publish

Now I can pull this file up on the server as a quick sanity check.

There are two ways to pull up a URL under CRM:

1) Include org name – goes through CRM virtual path handler.

2) Leave out org name – goes through IIS at a lower level.

#1 is usually what you want, even if it gives you trouble at first, the trouble is usually because you missed a detail.

page

Hooray I have a button!  PUSH BUTTAN!

Would you believe that worked on my first try?

pushbuttan

Take a quick break to pretty things up a bit, snag the style sheets off a view source on the main page.

<link rel="stylesheet" type="text/css" href="/ArkeCRM/_common/styles/global.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/ArkeCRM/_common/styles/global-styles.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/ArkeCRM/_common/styles/global-dynamic-styles.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/ArkeCRM/_common/styles/fonts.aspx?lcid=1033" />    

styled

Publish again or rebuild and recopy your dll to pick up the changes.  

Ok. Now to add my useless button to an iframe on an entity.  Grab an entity to work on.  I’ll make a new one.

newentity

I name my entity, turned off note and activities and added it to the settings tab.  I kept ownership at user because it will be useful for some examples later, but in general if the entity doesn’t involve records being owned by people I would flip it to Organization.

newentity[5]

Save, then add some attributes.  Your attributes will have a different prefix, the default is “new_”.

attributes

Edit the form and add some stuff to it.

Most importantly, add an IFRAME!

If you pick ‘pass record object-type code and unique identifier as parameters’, your URL will have parameters passed into it about the record the form is displaying.  This will be useful later, so I check it.

There is no need to ‘restrict cross-frame scripting’ unless you don’t trust your target frame.  If linking to some 3rd party site, keep it checked.  If it’s your own page, I believe you should be ok unchecking it, it will cause some javascript to not work someday.  Note also you have various formatting options. 

(Dependencies is strictly a protect-users-from-themselves features; if you add a dependency, the dependent field will not be allowed to be removed from the form in the form editor.  That’s all it does.)

add_iframe

Ok, save and close, and publish.

Go make a new one of these to check out your form. (You have to reload the main CRM page before the left hand menu picks up the new options after you make a new entity, so hit f5 on the main window then go to settings.)

 

example 

 

Taking a quick break to go over one really common situation:

Often you don’t know what to set the iframe URL to when the form is first loaded.  Or perhaps you don’t want to display it if you have a create form, you only display the iframe on an update form.  The solution here is to hard-code the url to /_root/Blank.aspx , then change it in javascript in the onload.

You’ll find lots of references on the internet telling you to set it to about:blank.  This can cause problems in odd cases, such as security warnings.  Ignore the about:blank advice and use /_root/Blank.aspx

Now, a quick review of some shortcuts that I took that we’ll want to fix in the near future.  I try not to take shortcuts, but this is long enough as is.  Still, it’s important you know about them:

  1. I hard-coded the org name in various places.  I should build up org name strings instead of hard-coding it.
  2. I didn’t use an external config file to grab my connection to CRM.
  3. I only dealt with one type of CRM installation, the on-premise active directory setup.
  4. My onclick method should call some helper functions – connecting to crm and whoami should be separate calls.
  5. I didn’t use an async call when making a blocking web request. This ties up my app pool threads more than necessary.

Now, a parting picture:

finalexample

 

And thats it, we now have a button on a CRM form that talks to CRM, start to finish everything that you need to know. 


Posted by David Eison on Wednesday, August 25, 2010 2:51 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Elmah and SoapException

A web app I’m working on which uses web service calls a lot is using Elmah for error logging.

Unfortunately web service calls consistently result in the useless ‘Server was unable to process request.’ message.

Originally I thought I could patch Elmah to log session variables then log the detail in the session in a Global.asax application_error handler.  Unfortunately, the Elmah error handler appears to consistently run before the application_error handler, so that doesn’t work (although it’s still nice to have session variables logged).

So I did some quick hacking on Elmah to get it to pull out the useful SoapException.Detail message. 

In Elmah’s Error.cs file, after:

if (httpException != null)
{
    _statusCode = httpException.GetHttpCode();
    _webHostHtmlMessage = Mask.NullString(httpException.GetHtmlErrorMessage());
}

I added:

// If this is a SOAP exception, then replace the message information with more useful info
System.Web.Services.Protocols.SoapException soapException = baseException as System.Web.Services.Protocols.SoapException;
if (soapException != null && soapException.Detail != null)
{
    _detail = baseException.Message 
        + System.Environment.NewLine + System.Environment.NewLine 
        + "Soap Detail: " + Mask.NullString(soapException.Detail.InnerText) 
        + System.Environment.NewLine + System.Environment.NewLine 
        + "Regular detail: " + System.Environment.NewLine + _detail;
    _message = Mask.NullString(soapException.Detail.InnerText);
    // not sure how big these detail strings get, so arbitrarily cap the size on the short message
    if (_message.Length > 100)
    {
        _message = _message.Substring(0, 100);
    }
}

And now I get Elmah error logs like:

elmah_log_new

Instead of:

elmah_log_old

And detail messages like:

elmah_log_newdetail

Instead of:

elmah_log_olddetail


Posted by David Eison on Thursday, August 19, 2010 2:33 PM
Permalink | Comments (0) | Post RSSRSS comment feed

There is no DataBinder.Bind method

Old but useful: http://weblogs.asp.net/leftslipper/archive/2007/06/29/how-asp-net-databinding-deals-with-eval-and-bind-statements.aspx

The “Bind” method for two-way databinding is pure magic, the ASP.NET parser intercepts it and translates it into real code.

This means that if you have a problem with an eval such as “System.InvalidCastException: Specified cast is not valid.”, which you can fix up just fine when using eval by manually casting or parsing the object, you simply won’t be able to use two way binding because there is no way to cast or parse the result of the eval.


Posted by David Eison on Tuesday, August 17, 2010 2:06 AM
Permalink | Comments (0) | Post RSSRSS comment feed

CSS Sprites are finally easy

“Sprite and Image Optimization Framework” http://aspnet.codeplex.com/releases/view/50140

Getting CSS Sprites is now as simple as putting images in a special folder, including a dll and httpmodule in your website, and using a custom asp tag.  The doc file at that link is very clear and well illustrated, I feel like I’d just be wasting time writing an intro here since it’s already covered so well.


Posted by David Eison on Monday, August 16, 2010 3:30 PM
Permalink | Comments (0) | Post RSSRSS comment feed

CodeFile and CodeBehind are not the same thing

A user control with a CodeFile attribute:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="controlname.ascx.cs" Inherits="mynamespace.controls.controlname" %>

will sometimes result in the error message:

"The base class includes the field 'ControlID1', but its type (mynamespace.controls.controlname) is not compatible with the type of control (ASP.controls_controlname_ascx)"

Switching to

CodeBehind="controlname.ascx.cs"

fixes it right up.

Thank god for forums.

Behind the scenes, per stackoverflow, CodeBehind only works with a precompiled dll while CodeFile can compile on the fly.  I assume there is some problem with the compile on the fly feature and custom controls, perhaps when they are registered in web.config, perhaps when they are used from master pages, I don’t know… I just know it doesn’t reliably work, and it can be a real headache figuring out why.


Posted by David Eison on Friday, August 13, 2010 6:29 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Gotchas when loading jquery from a CDN

A client wants to take advantage of browser cache and load jquery from a CDN.

There is always a lot of discussion about whether one should load jquery from a CDN.  Pros include:

  • Some users will have the file cached, saving you 20k of bandwidth and them a bit of load time
  • It’s a very cheap (free) way to serve files from geographically distributed close-to-your-user servers
  • Browsers only simultaneously load a few files per domain name, so using an extra domain name can lead to more parallel file loads
  • You can count on Google to get gzip and expires headers right (provided you request the right specific file)

Drawbacks include:

  • There may be an extra DNS resolution to load from a new domain name, and DNS resolution takes time
  • Google might be down or blocked (particularly on intranet sites)
  • You’re serializing loads of your js anyway, so will you see gains from other parallel loads?

Particularly because some of our users are in controlled office environments where they don’t necessarily have access to the entire web, the concern about ajax.googleapis.com being blocked is a real concern.  So, the script should fallback to load jquery locally if it exists.

There are plenty of blog and forum discussions about this, so this is yet another, but I thought I’d like to focus on some of the gotchas that can be encountered with even such an apparently straightforward task.

This is the most obvious potential solution:

<head>
...
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>  
<script type="text/javascript">   
if (typeof jQuery === 'undefined') {   
   var e = document.createElement('script');   
   e.src = '/js/jquery-1.4.2.min.js';   
   e.type='text/javascript';   
   document.getElementsByTagName("head")[0].appendChild(e);   
}   
</script>  
    <script type="text/javascript" src="/script-that-needs-jquery-loaded.js"></script>
...
</head>

However, don’t use that ‘solution’! 

Do this instead, even though document.write is on the ‘generally avoid this’ list of things not to do:

<head>
...
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
if (typeof jQuery === 'undefined')
{
    document.write(unescape("%3Cscript src='/js/jquery-1.4.2.min.js' type='text/javascript'%3E%3C/script%3E"));
}
</script>
    <script type="text/javascript" src="/script-that-needs-jquery-loaded.js"></script>
...
</head>

 

The first ‘obvious’ solution has a few non-obvious problems:

  1. The first problem is a race condition.  When a browser encounters a script tag in a document, it pauses while the script loads.  This is so that you can chain together a bunch of scripts that rely on each other – the jquery library makes some functions available, the next library you load can use those functions.  It’s a pretty essential feature, and we routinely rely on it all the time without even thinking about it.

    However, look at what our failover case is doing – if jquery doesn’t load, it tacks it in as the last element in the document head in the DOM.

    Elements loaded via adding dynamically to the DOM *don’t* block like regular script elements, they load asynchronously. 
    This means that script-that-needs-jquery-loaded probably won’t have jquery loaded in time.  But maybe it will, due to caching.  We now have a heisenbug – if google is accessible, everything works great.  If google is not accessible, jquery still gets loaded, but without blocking, so now sometimes things later in the page that need jquery will work but other times it won’t have loaded in time.
    The easiest fix is to switch to document.write to include a script tag in the page.  A script tag written with document.write will run and block once your current script block completes.

  2. Next up, we have a caching problem.  When you request http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js , look at this little header:

    Cache-Control: public, must-revalidate, proxy-revalidate, max-age=3600

    The odds of getting a useful document from a visitor’s cache with a max-age of one hour are not that great.
    However, when you request http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js , the cache time is now:

    Cache-Control: public, max-age=31536000

    Google serves up 1.4 with a 1 hour cache expires time, because a new version might be released and they don’t want the old version stuck in your cache forever.  The solution is to instead request a specific version, which is served up with a year long expires time.

  3. Now, on our document.write change: There’s a gotcha here too. You’re technically not allowed to have the string </whatever> in your javascript, even if it’s inside a string.  Some browsers may run it wrong.  Some AV/firewall software will incorrectly dynamically rewrite this.  So

    document.write("<script src='/codescripts/js/jquery-1-3-2-min.js' type='text/javascript'></script>"));

    isn’t legit.  Two solutions here – either split up the /script into two strings added together, or else encode the brackets and use the unescape function to decode.  I think the unescape is more clear:

    document.write(unescape("%3Cscript src='/codescripts/js/jquery-1-3-2-min.js' type='text/javascript'%3E%3C/script%3E"));

  4. Final problem: If using jquery 1.3: Firefox 3.5 doesn’t support document.readyState, so if you put this randomly somewhere in the body of your page and it runs after the page is ready, your onready events may not fire.  There are fixes for this behavior in jquery 1.4 and in Firefox 3.6, but in general to be safest, include your jquery in the document head so it runs before onready fires.

No matter what, test!  On that first script load, temporarily set an invalid filename, temporarily set an invalid domain name, and make sure the fallback works like you’d expect!


Posted by David Eison on Thursday, August 12, 2010 5:38 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Obscure linq to sql issue - don't put 'using' at the of your partial class

Just got burned by this: http://connect.microsoft.com/VisualStudio/feedback/details/361577/mslinqtosqlgenerator-fails-with-partial-class-having-a-using-at-the-top-of-the-file

Symptom:

Linq breaks and exiting/restarting doesn’t fix it.  Your database project is now missing a ‘.designer.cs’ file under your .dbml file.

Cause:

If you put a ‘using’ statement at the top of a partial class for a linq to sql database file (we routinely recommend making these partial classes to replace the default constructor, e.g. http://blog.arkesystems.com/post/2008/03/Using-the-connection-strings-in-your-webconfig-for-LINQ.aspx ) then sometimes Visual Studio deletes your .designer.cs file and so your linq just quits compiling.

Workaround is to move the using statement inside the namespace declaration, then restore your now-deleted .designer.cs file from version control or backups or else right click on the .dbml file and select "Run Custom Tool" to rebuild the file.

Timebomb, one day might kaboom:

using System.Configuration;
namespace Symmedian.SST.Data
{
    partial class MyLinqDataContext {

Perfectly happy and fine:

namespace Symmedian.SST.Data
{
    using System.Configuration;
    partial class MyLinqDataContext {


Categories: ASP.NET | LINQ
Posted by David Eison on Sunday, August 8, 2010 9:46 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Microsoft Dynamics CRM 4.0 – Guids ToString – Even simple things can be complicated

One common need when working with CRM is to convert GUIDs to String.  Say, you want to build up some fetchxml.  No problem, .ToString(), right?

Well, Guids can actually be converted to strings in many different ways – even just the hexadecimal string can be with brackets, without brackets, uppercase, lowercase, with or without hyphens, etc. 

To keep your code as reliable and readable as possible, you’ll want to do this consistently – CRM uses uppercase hexadecimal, with brackets.  Guid has a ToString that takes a format string that you can pass it several different options.  “B” is closest to what we want, but not quite right, because it returns lowercase.  So you should convert to uppercase… but you’ll want to be careful, because converting to uppercase behavior can vary depending on the locale of the box running the code.  Usually your code will run on a server and it won’t matter, but what if you are writing a plugin which is running offline on an outlook client?  I don’t actually know if there is a locale where the hexadecimal characters A-F don’t convert right, but I know that in Turkey the uppercase of i is not I, so I don’t want to push my luck that no culture does something similar with A-F (update: Writing culture-safe managed code says Turkish and Azeri are the only languages with single character case differences, but this points out there may be multiple character case differences, such as Ff in welsh, and then there are some languages that don’t have some letters, such as Cryllic has no C for example…).  So, to be as safe as possible, specify the ‘InvariantCulture’ for any string comparisons or conversions that should deal with strings in a program-readable consistent manner.

Finally, to make this easily accessible to all of your code, you can add a utility method directly to the Guid class as an Extension Method

using System;
using System.Globalization;

namespace arkesystems.crm
{
    public static class CRMUtilExtensionmethods
    {
        public static string ToStringForCRM(this Guid guid)
        {
            return guid.ToString("B", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture);
        }
    }
}

 

Now whenever I want to turn a guid into a string, I just have to have

using arkesystems.crm;

at the top of the class, and my extension method is available directly on the Guid itself (handily also in the autocomplete dropdown):

Guid taskGuid = crmservice.Create(t);
string strTaskGuid = taskGuid.ToStringForCRM();

Posted by David Eison on Friday, August 6, 2010 6:44 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Update Rollup 12 for Dynamics CRM has been released

First off, here's the Knowledge Base Article and the Download page.

A couple of big issues have been resolved with this update. The first of which is the Email Router Rule Deployment Wizard now supports Exchange 2010.  Also, the Quick Find should be faster, as it no longer occasionally runs the search twice.

For those of you having trouble with Report Wizard Reports, the issue where the error  "Query execution failed for data set DSMain." has been resolved!

A lot of good updates in this Rollup, I highly recommendinstalling it soon.


Posted by Wayne Walton on Tuesday, August 3, 2010 9:38 AM
Permalink | Comments (0) | Post RSSRSS comment feed