<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1686741264350458430</id><updated>2011-11-03T12:20:20.956-06:00</updated><category term='articles'/><category term='support'/><category term='installation'/><category term='Currency'/><category term='javascript'/><category term='workflow'/><category term='callout'/><category term='OpenSocial'/><category term='CRM Online'/><category term='Integration'/><category term='Security'/><category term='Opportunity'/><category term='case studies'/><category term='Mappings'/><category term='CRM 4.0'/><category term='Permission'/><category term='hiding tabs'/><category term='Dynamics'/><category term='Salesforce.com'/><category term='hiding locked fields'/><category term='form customization'/><category term='plugin'/><category term='sdk'/><category term='Siebel'/><category term='custom code'/><category term='LinkedIn'/><category term='sdk library ABetterCRM'/><category term='tdd'/><category term='MOSS'/><category term='Error'/><category term='Documents'/><category term='MS-CRM'/><category term='Facebook'/><category term='Dynamics CRM'/><category term='SageCRM'/><category term='Social Networking'/><category term='Mobile'/><category term='Quotes'/><category term='CRM'/><category term='attribute'/><category term='SharePoint'/><category term='Plaxo'/><category term='Contracts'/><category term='EAM'/><category term='Oracle CRM'/><category term='MySpace'/><category term='sample'/><category term='API'/><category term='WSS'/><category term='Google'/><category term='Price List'/><category term='Publish'/><category term='Opporutnity-Product'/><category term='sales stage'/><category term='SalesLogix'/><category term='Enterprise Application Mashup'/><title type='text'>Statera CRM Blog</title><subtitle type='html'>News and information related to the CRM industry. Tips and tricks on implementing MS-CRM, SalesForce.com, Oracle CRM (Siebel), Netsuite, Zoho, SugarCRM and more...</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Seth</name><uri>http://www.blogger.com/profile/06707729282980294252</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>48</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-7618622956890114921</id><published>2011-10-20T09:43:00.002-06:00</published><updated>2011-10-21T11:45:43.843-06:00</updated><title type='text'>STATERA CRM</title><content type='html'>&lt;b&gt;&lt;h1&gt;This blog is no longer active, to learn more about Statera and what we are currently working on please visit our website.&lt;br /&gt;&lt;/b&gt; &lt;a href="http://www.statera.com/services/Applications/Pages/CRM.aspx"&gt;www.STATERA.com&lt;/a&gt;&lt;/h1&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-7618622956890114921?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/7618622956890114921/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=7618622956890114921' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7618622956890114921'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7618622956890114921'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2011/10/statera-crm.html' title='STATERA CRM'/><author><name>AaronX</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://4.bp.blogspot.com/_aOTFKWWgmrg/SUp6TLTz4TI/AAAAAAAAAAk/c7_xUf6kJmQ/S220/aaron.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-6805328366541594987</id><published>2009-06-17T09:53:00.003-06:00</published><updated>2009-06-17T10:56:27.960-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Quotes'/><category scheme='http://www.blogger.com/atom/ns#' term='Contracts'/><category scheme='http://www.blogger.com/atom/ns#' term='SharePoint'/><category scheme='http://www.blogger.com/atom/ns#' term='WSS'/><category scheme='http://www.blogger.com/atom/ns#' term='MOSS'/><category scheme='http://www.blogger.com/atom/ns#' term='Dynamics CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='Documents'/><title type='text'>Managing Sales Documents in MS-CRM</title><content type='html'>One of the most common questions we get from users is how to store sales and service documents in Microsoft Dynamics CRM.  These documents could be sales related like quotes, work orders, purchase requests, proposals, etc or service related like field service reports, error logs, or other documentation related to a case.&lt;br /&gt;&lt;br /&gt;The answer to the questions, of course, is the often missed upload attachment feature&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_VCtOSK4xjA0/SjkX-70m-GI/AAAAAAAAA4Q/r2A91dVqTUA/s1600-h/paperclip.gif"&gt;&lt;img src="http://1.bp.blogspot.com/_VCtOSK4xjA0/SjkX-70m-GI/AAAAAAAAA4Q/r2A91dVqTUA/s200/paperclip.gif" alt="CRM Documents" id="BLOGGER_PHOTO_ID_5348332402456852578" border="0" /&gt;&lt;/a&gt; .&lt;br /&gt;Unfortunately this feature does not work for most Microsoft Dynamics CRM document storage needs, for the following reasons.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;There is no version control or document management features.&lt;/li&gt;&lt;li&gt;There is no ability to search the text of documents that are stored&lt;/li&gt;&lt;li&gt;The documents uploaded are stored in the notes section and not visible when browsing the record and it is often not intuitive to look in the notes section for documents. &lt;/li&gt;&lt;li&gt;The attach document icon (the paperclip) is placed in the form header, not where people think to look to upload documents.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Further storing documents in MS-CRM will bloat the Microsoft Dynamics CRM database causing serious performance problems.&lt;br /&gt;&lt;br /&gt;A better solution is to leverage Microsoft SharePoint for document management within Dynamics CRM.   In this way, you get all of the document management features of SharePoint without leaving CRM. Statera offers &lt;a href="http://stratus.statera.com/"&gt;Stratus&lt;/a&gt;, &lt;a href="http://stateracrm.blogspot.com/2009/06/simple-integration-of-ms-crm-and.html"&gt;a web based MS-CRM and Sharepoint integration platform&lt;/a&gt; to do this for you.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://stratus.statera.com/site/images/crm-screen1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 1020px; height: 652px;" src="http://stratus.statera.com/site/images/crm-screen1.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Stratus takes a few minutes to setup and is currently available for free from &lt;a href="http://stratus.statera.com/"&gt;stratus.statera.com&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-6805328366541594987?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/6805328366541594987/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=6805328366541594987' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/6805328366541594987'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/6805328366541594987'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2009/06/managing-sales-documents-in-ms-crm.html' title='Managing Sales Documents in MS-CRM'/><author><name>Host-Party.com Staff</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_VCtOSK4xjA0/SjkX-70m-GI/AAAAAAAAA4Q/r2A91dVqTUA/s72-c/paperclip.gif' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-1147265921445879485</id><published>2009-06-01T09:33:00.007-06:00</published><updated>2009-06-01T09:50:45.266-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='EAM'/><category scheme='http://www.blogger.com/atom/ns#' term='Enterprise Application Mashup'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM Online'/><category scheme='http://www.blogger.com/atom/ns#' term='SharePoint'/><category scheme='http://www.blogger.com/atom/ns#' term='WSS'/><category scheme='http://www.blogger.com/atom/ns#' term='Integration'/><category scheme='http://www.blogger.com/atom/ns#' term='MOSS'/><category scheme='http://www.blogger.com/atom/ns#' term='Dynamics CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM 4.0'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Simple Integration of MS-CRM and SharePoint</title><content type='html'>Statera just released an easy to use enterprise application mashup called Stratus to simplify the integration of MS-CRM and SharePoint. Stratus can be setup in a few minutes and will allow you to leverage SharePoint's document management features within Microsoft Dynamics CRM (both online and on premise versions).&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;/div&gt;&lt;img id="BLOGGER_PHOTO_ID_5342383599543503538" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 400px; CURSOR: hand; HEIGHT: 256px; TEXT-ALIGN: center" alt="" src="http://3.bp.blogspot.com/_VCtOSK4xjA0/SiP1lCCpyrI/AAAAAAAAA4A/gI3YP86Te9A/s400/crm-screen1.png" border="0" /&gt;Once activated, Stratus adds a new tab to Accounts, Opportunities and Cases that will allow you to create Sharepoint sites and add/view documents to and from those sites. &lt;p&gt;In addition Stratus gives you visibility to key CRM information directly from within Sharepoint.&lt;/p&gt;&lt;img id="BLOGGER_PHOTO_ID_5342384414880456722" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 400px; CURSOR: hand; HEIGHT: 382px; TEXT-ALIGN: center" alt="" src="http://3.bp.blogspot.com/_VCtOSK4xjA0/SiP2UfaIYBI/AAAAAAAAA4I/DaXlDcFsfgE/s400/sp-screen1.png" border="0" /&gt;&lt;br /&gt;&lt;div&gt;&lt;/div&gt;To learn more about Statera's &lt;a href="http://stratus.statera.com/"&gt;MS-CRM and SharePoint Integration &lt;/a&gt;tool, go to &lt;a href="http://stratus.statera.com/"&gt;stratus.statera.com&lt;/a&gt;.&lt;br /&gt;&lt;div&gt; &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-1147265921445879485?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/1147265921445879485/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=1147265921445879485' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1147265921445879485'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1147265921445879485'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2009/06/simple-integration-of-ms-crm-and.html' title='Simple Integration of MS-CRM and SharePoint'/><author><name>Host-Party.com Staff</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_VCtOSK4xjA0/SiP1lCCpyrI/AAAAAAAAA4A/gI3YP86Te9A/s72-c/crm-screen1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-4125972986560444481</id><published>2009-03-24T09:39:00.002-06:00</published><updated>2009-03-24T09:40:45.298-06:00</updated><title type='text'>CRM 4.0 issue with tracking emails</title><content type='html'>Issue: Emails are getting tracked to CRM records automatically and are not always accurate.&lt;br /&gt;&lt;br /&gt;Cause: SmartTracking is setting the regarding on emails since Tracking Tokens have been disabled&lt;br /&gt;&lt;br /&gt;Microsoft’s Suggested Resolution: Manually set the regarding record to the emails that were not properly tracked or turn on Tracking Tokens.&lt;br /&gt;&lt;br /&gt;Our Temporary Solution:  Our Company has tried tracking tokens before but the users didn’t like the CRM number visible in the email subject line.  So turning on tracking tokens will not be our fix.  So for now I have given end users instructions to manually untrack the email and then track it to the correct CRM record.&lt;br /&gt;&lt;br /&gt;Good News: There have been rumors of the ability to turn Smart Tracking off in a future Update Rollup, and from what I have heard it may be Update Rollup 4.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-4125972986560444481?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/4125972986560444481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=4125972986560444481' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4125972986560444481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4125972986560444481'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2009/03/crm-40-issue-with-tracking-emails.html' title='CRM 4.0 issue with tracking emails'/><author><name>Laura</name><uri>http://www.blogger.com/profile/16946216058550720064</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-2038405409108136961</id><published>2009-02-05T14:35:00.003-07:00</published><updated>2009-02-05T14:42:57.625-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='hiding tabs'/><category scheme='http://www.blogger.com/atom/ns#' term='hiding locked fields'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM 4.0'/><title type='text'>Hiding Locked Fields (and hiding tabs)</title><content type='html'>It's been well posted about hiding fields and tabs using javascript:&lt;br /&gt;Hiding the 2nd tab can be done with js in the onload event:&lt;br /&gt;crmForm.all.tab1Tab.style.display = 'none';&lt;br /&gt;&lt;br /&gt;Hiding a field (must know the field ID):&lt;br /&gt;crmForm.all.FIELDID.style.display = 'none';&lt;br /&gt;&lt;br /&gt;I did a bunch of this for various reasons including getting fields to conditionally appear/disappear, hiding tabs that can't be removed for some reason (but are of no use to the implementation) and so forth.&lt;br /&gt;&lt;br /&gt;At one point I needed the "Price Per Unit" field.  It's locked on the form and the only way I could see to hide it was with javascript.  The field hid well, but then I couldn't remove the "$".  It was an element called "priceperunit_sys".  I tried lots of things, then decided to move it to a new tab and simply hide the tab. &lt;br /&gt;&lt;br /&gt;I realized this was a MUCH BETTER solution than hiding each field individually.  In this way, I now have a "Hidden" tab that holds all the fields I don't want to display to my users.  The advantage is that if I (or anyone else) needs to bring the fields back, they don't need to muck around in the code.  They simply move them to a visible tab.&lt;br /&gt;&lt;br /&gt;Of course...most fields can be removed, some you may just want hidden because they're used for calculations and may never be shown, but I just stumbled across this and wanted to share.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-2038405409108136961?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/2038405409108136961/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=2038405409108136961' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2038405409108136961'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2038405409108136961'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2009/02/hiding-locked-fields-and-hiding-tabs.html' title='Hiding Locked Fields (and hiding tabs)'/><author><name>Seth</name><uri>http://www.blogger.com/profile/06707729282980294252</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-7525104671995651122</id><published>2009-02-05T14:02:00.003-07:00</published><updated>2009-02-05T14:35:18.248-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Dynamics'/><category scheme='http://www.blogger.com/atom/ns#' term='Mappings'/><category scheme='http://www.blogger.com/atom/ns#' term='Opporutnity-Product'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM 4.0'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><title type='text'>Attribute mapping - a newbie description</title><content type='html'>I read several blogs about "attribute mapping" (see credits below).  The problem is that neither gave me (as being "new" to mappings) a good understanding of what was happening.  I'll try and explain a little more.  This was the info I needed:&lt;br /&gt;&lt;br /&gt;When you promote an Opportunity Product to a Quote Line Item specific field values are mapped over.  But what about custom Opportunity Product fields you might have created?  They don't map out of the box.  You need to do something - that falls under the 1:N relationship mappings.&lt;br /&gt;&lt;br /&gt;Account to Opportunity is one that works out of the box.  If you create a custom field on Account (call it "Platform") and on every Opportunity you create from that Account you want the value of "Platform" to be populated (default) with the "Platform" field of Opportunity.  You need to go to the 1:N relationship on the Account entity.  The one you use has the Primary entity of Account and the Related entity of Opportunity (In this case the name is "opportunity_primary_accounts").  Then you select the Mappings link along the left nav bar.  Here you can add all the custom field mappings you want.&lt;br /&gt;&lt;br /&gt;Okay, that's long, but all well and good.  Back to my Opportunity-Product to Quote Line Item mapping.  On the Opportunity Product entity 1:N relationships, there IS NO MAPPINGS link.  But that's what I need...well, someone smarter than I figured out how to "find it".  That's what the blogs below explain.&lt;br /&gt;&lt;br /&gt;You'll need the GUID for the relationship and once you get your URL sorted out you'll get the mappings that you didn't have.&lt;br /&gt;&lt;br /&gt;URL: http://yourservernamehere/Tools/SystemCustomization/Relationships/Mappings/mappingList.aspx?mappingId=&lt;br /&gt;&lt;br /&gt;Sorry if this is long winded, but I read at least 2 blogs that had this solution and I couldn't understand that it was my solution (till I tried it).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Solution blogs I used should get credit.  Both blog solutions I found give the explanation of the problem and similar (or the same) solutions:&lt;br /&gt;http://www.techtalkz.com/microsoft-dynamics-crm/483211-crm-4-0-opportunity-product-quote-product-attribute-mapping.html&lt;br /&gt;&lt;br /&gt;http://blogs.inetium.com/blogs/jmiley/archive/2008/09/27/map-custom-attributes-from-opportunity-product-to-quote-product-to-order-product-and-so-on-in-microsoft-crm-4-0.aspx&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-7525104671995651122?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/7525104671995651122/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=7525104671995651122' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7525104671995651122'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7525104671995651122'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2009/02/attribute-mapping-newbie-description.html' title='Attribute mapping - a newbie description'/><author><name>Seth</name><uri>http://www.blogger.com/profile/06707729282980294252</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-2352526670101116647</id><published>2009-02-03T15:54:00.003-07:00</published><updated>2009-02-03T16:12:09.937-07:00</updated><title type='text'>Resolution to workflows not publishing</title><content type='html'>CRM 4.0 rollup 2 seems to be the cause for workflows no longer publishing. So follow these steps to resolve the issue.&lt;br /&gt;Find the CRM web.config file and make a copy/backup of it.&lt;br /&gt;Open the web.config file and browse to the AuthorizedTypes section&lt;br /&gt;Paste in the following text:&lt;br /&gt;&lt;authorizedtype assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" namespace="System.Globalization" typename="CultureInfo" authorized="True"&gt;&lt;br /&gt;Save and close the web.config file. That is it. Microsoft mentioned they should have a KB artical about this soon.&lt;br /&gt;&lt;br /&gt;-Laura&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-2352526670101116647?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/2352526670101116647/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=2352526670101116647' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2352526670101116647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2352526670101116647'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2009/02/resolution-to-workflows-not-publishing.html' title='Resolution to workflows not publishing'/><author><name>Laura</name><uri>http://www.blogger.com/profile/16946216058550720064</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-1808815109530216380</id><published>2009-01-05T10:00:00.003-07:00</published><updated>2009-01-05T15:57:03.683-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Error'/><category scheme='http://www.blogger.com/atom/ns#' term='Publish'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>How not to mess up your environments (or learn from my mistakes)</title><content type='html'>Here is a fun one:&lt;br /&gt;&lt;span id="_ctl0_MainContent_PostFlatView"&gt;&lt;span&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:Arial;"&gt;&lt;span style="color: rgb(0, 0, 255);font-family:Verdana;" &gt;An error has occurred.&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 255);font-family:Verdana, Arial, Helvetica;font-size:85%;"  &gt;Try this action again. If the problem&lt;br /&gt;continues, check the Microsoft Dynamics CRM Community for solutions or&lt;br /&gt;contact your organization's Microsoft Dynamics CRM Administrator. Finally,&lt;br /&gt;you can contact Microsoft Support. &lt;/span&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:Arial;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Right, lots of info there.  Troubleshooting?  Let's not even talk about what I tried to do.  What happened?  Well, I broke it.  Good thing this was  development environment and not production.  I did what everyone tells you not to do, but at least I had the forethought to "TRY" it out first.&lt;br /&gt;&lt;br /&gt;I had been working with a development system that was quite old.  No one really knew when it was set up, if it was a snapshot of production, when the last time data or configurations had been migrated...all we knew was that it wasn't 100% perfect, but it was good enough.  I'd been using it for a couple of weeks, trying some configurations out, code, entities, all sorts of stuff.&lt;br /&gt;&lt;br /&gt;Suddenly, we were given a new environment built on virtuals - "Yeah!" yelled the developers.  This sounded great.  It was a recent snapshot of production (minus data), but it worked (for the most part).  Minor inconsistencies (that I wont elaborate on) aside, it was good.&lt;br /&gt;&lt;br /&gt;At this point, I needed to move any new developments from the old dev system to the new one.  No big deal.  This would be a good test for moving changes we'd made to the production system...Here goes.  First, back up the customizations from the dev virtual, then we dove in.  Made a few changes, added some objects, then pulled 3 or 4 entities from the old dev and imported to the virtual. &lt;br /&gt;&lt;br /&gt;Let's publish...and get an error.  No biggie...we get those.  But no solution to be found. &lt;br /&gt;&lt;br /&gt;After searching and looking, importing backups and everything it came down to this...&lt;br /&gt;&lt;br /&gt;Creating an object in CRM assigns a GUID that is carried over (within the database) when exporting and importing to new environments.  If you create a new object in 1 enviroment that has some 1:N or N:1 or N:N relationship to another, then recreate that object in a new environment EVEN IF YOU DON'T IMPORT THAT OBJECT those N:1, 1:N, N:N relationships reference the GUID...if you import an object that is related to the one you created...you're dead in the water.  You can't "un-import", even importing the backup entity doesn't fix the issue. &lt;br /&gt;&lt;br /&gt;I may explore the configuration database and see if I can manually remove/update, but I think that will just create other issues.  I can't delete those objects in my new dev and try to recreate them with an import because the relationships somehow prevent this and they (the relationships) cannot be removed.&lt;br /&gt;&lt;br /&gt;Anyway, this has rambled on longer than I wanted to, but wanted to get this info out as I find it important to know.&lt;br /&gt;&lt;br /&gt;Again, really good that we found this out now rather than doing it in production :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-1808815109530216380?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/1808815109530216380/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=1808815109530216380' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1808815109530216380'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1808815109530216380'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2009/01/how-not-to-mess-up-your-environments-or.html' title='How not to mess up your environments (or learn from my mistakes)'/><author><name>Seth</name><uri>http://www.blogger.com/profile/06707729282980294252</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-4422287889818611542</id><published>2008-12-19T15:18:00.003-07:00</published><updated>2009-01-05T09:59:54.085-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Error'/><category scheme='http://www.blogger.com/atom/ns#' term='Currency'/><category scheme='http://www.blogger.com/atom/ns#' term='Price List'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='Opportunity'/><title type='text'>Script behind the Price List Field</title><content type='html'>I thought this was worth a post.  This deals with MS-CRM 4.0.&lt;br /&gt;&lt;br /&gt;At some point, the Price List field was removed from the Opportunity form.  Once you put it back on, you can't actually select a price list.  The fix deals with a script that has to be MANUALLY added back through the object XML file.  (Thanks to http://dmcrm.blogspot.com/2008/03/removing-price-list-field-from.html).&lt;br /&gt;&lt;br /&gt;Maybe you ran into my problem?  After importing the customization, when I try to access an Opportunity object I would get an error.  I looked and looked, but couldn't figure it out. I must have edited that XML 12 times to be sure I had it right.  Anyway, the simple solution was that you must have the Currency field on the form as well.  You don't get any sort of error that points you to this and I don't believe there's anything in the script, but this resolved the issue.  Currency is read only, so I don't see any other issues.&lt;br /&gt;&lt;br /&gt;That said...more posts to come.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-4422287889818611542?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/4422287889818611542/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=4422287889818611542' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4422287889818611542'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4422287889818611542'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/12/script-behind-price-list-field.html' title='Script behind the Price List Field'/><author><name>Seth</name><uri>http://www.blogger.com/profile/06707729282980294252</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-5358113128052398288</id><published>2008-08-07T12:58:00.002-06:00</published><updated>2008-08-07T14:26:19.564-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='installation'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Cleaning up old CRM Active Directory groups</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;For anyone who has done multiple re-installations of MSCRM in the same domain and wants to figure out which AD groups can be deleted based on GUID, run this query and look at the GUID in the PrivUserGroup column:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;select * from dbo.OrganizationBase&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-5358113128052398288?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/5358113128052398288/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=5358113128052398288' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5358113128052398288'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5358113128052398288'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/08/cleaning-up-old-crm-active-directory.html' title='Cleaning up old CRM Active Directory groups'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-7515346044960662901</id><published>2008-07-29T15:54:00.004-06:00</published><updated>2008-07-29T16:05:42.740-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='sdk'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><title type='text'>TDDing a CRM 4.0 Plugin - Part 7</title><content type='html'>Well, hopefully that goes over all the basics of TDDing a CRM 4.0 Plugin.&lt;br /&gt;&lt;br /&gt;After the first post I stopped doing some refactoring. There are also a LOT of tests cases not hit here. I'm not going to code them all out, but I will at least show you the resulting refactored code:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;using System;&lt;br /&gt;using System.IO;&lt;br /&gt;using System.Reflection;&lt;br /&gt;using System.Xml.Serialization;&lt;br /&gt;using Microsoft.Crm.Sdk;&lt;br /&gt;using NMock2;&lt;br /&gt;using NMock2.Monitoring;&lt;br /&gt;using NUnit.Framework;&lt;br /&gt;&lt;br /&gt;namespace TDD_with_CRM_Plugin&lt;br /&gt;{&lt;br /&gt;    [TestFixture]&lt;br /&gt;    public class PluginTestCases&lt;br /&gt;    {&lt;br /&gt;        #region Setup/Teardown&lt;br /&gt;&lt;br /&gt;        [SetUp] // executes at the beginning of each test case. Executed only once per TextFixture&lt;br /&gt;        public void Setup()&lt;br /&gt;        {&lt;br /&gt;            mock = new Mockery();&lt;br /&gt;            webService = mock.NewMock&lt;IWebServiceWrapper&gt;();&lt;br /&gt;            context = mock.NewMock&lt;IPluginExecutionContext&gt;();&lt;br /&gt;            crmService = mock.NewMock&lt;ICrmService&gt;();&lt;br /&gt;            correlationID = Guid.NewGuid();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        #endregion&lt;br /&gt;&lt;br /&gt;        private Mockery mock;&lt;br /&gt;        private IWebServiceWrapper webService;&lt;br /&gt;        private IPluginExecutionContext context;&lt;br /&gt;        private Guid correlationID;&lt;br /&gt;        private string resultingUrl = "http://someurl";&lt;br /&gt;        private ICrmService crmService;&lt;br /&gt;&lt;br /&gt;        private void SetupExpectationThatCreateCrmServiceIsCalled()&lt;br /&gt;        {&lt;br /&gt;            Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private void SetupExpectationThatCRMWebServiceWillBeCalledWithUpdatedDynamicEntity(&lt;br /&gt;            DynamicEntity dynamicEntityWithURL)&lt;br /&gt;        {&lt;br /&gt;            Expect.Once.On(crmService).Method("Update").With(new DynamicEntityMatcher(dynamicEntityWithURL)).Will(&lt;br /&gt;                new PluginRecursionAction(context));&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private DynamicEntity CreateAndSetupExpectationsThatDynamicEntityIsUpdatedWithURLFromWebService(&lt;br /&gt;            DynamicEntity account)&lt;br /&gt;        {&lt;br /&gt;            DynamicEntity dynamicEntityWithURL = CloneDynamicEntity(account);&lt;br /&gt;            dynamicEntityWithURL["new_sharepointurl"] = resultingUrl;&lt;br /&gt;            return dynamicEntityWithURL;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private void SetupExpectationThatCustomWebServiceIsCalledWithAccount(DynamicEntity account)&lt;br /&gt;        {&lt;br /&gt;            Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value(resultingUrl));&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private void SetupExpectationsThatCorrelationIDWillBeRead()&lt;br /&gt;        {&lt;br /&gt;            Expect.Once.On(context).GetProperty("CorrelationId").Will(Return.Value(correlationID));&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public DynamicEntity GetSampleAccount()&lt;br /&gt;        {&lt;br /&gt;            XmlSerializer xmlSerializer = new XmlSerializer(typeof (DynamicEntity));&lt;br /&gt;            return&lt;br /&gt;                (DynamicEntity)&lt;br /&gt;                xmlSerializer.Deserialize(new StreamReader(@"Sample Files\account.sample.xml").BaseStream);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private DynamicEntity CloneDynamicEntity(DynamicEntity account)&lt;br /&gt;        {&lt;br /&gt;            if (account == null)&lt;br /&gt;                throw new ArgumentNullException("account", "The supplied DynamicEntity cannot be null.");&lt;br /&gt;            if (String.IsNullOrEmpty(account.Name))&lt;br /&gt;                throw new ArgumentOutOfRangeException("account",&lt;br /&gt;                                                      "The name of the DynamicEntity can not be null or blank.");&lt;br /&gt;            XmlSerializer xmlSerializer = new XmlSerializer(typeof (DynamicEntity));&lt;br /&gt;            MemoryStream memoryStream = new MemoryStream();&lt;br /&gt;            xmlSerializer.Serialize(memoryStream, account);&lt;br /&gt;            memoryStream.Seek(0, SeekOrigin.Begin);&lt;br /&gt;            DynamicEntity clonedDynamicEntity = (DynamicEntity) xmlSerializer.Deserialize(memoryStream);&lt;br /&gt;&lt;br /&gt;            return clonedDynamicEntity;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private DemoPlugin GetDependencyInjectedPlugin()&lt;br /&gt;        {&lt;br /&gt;            return new DemoPlugin(webService);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private void SetupExpectationsThatPostEntityImagePropertyBagIsRead(PropertyBag postEntityImages)&lt;br /&gt;        {&lt;br /&gt;            Expect.Once.On(context).GetProperty("PostEntityImages").Will(Return.Value(postEntityImages));&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private PropertyBag CreatePropertyBagWithAccount(DynamicEntity account)&lt;br /&gt;        {&lt;br /&gt;            PropertyBag postEntityImages = new PropertyBag();&lt;br /&gt;            postEntityImages["postImage"] = account;&lt;br /&gt;            return postEntityImages;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private DynamicEntity CreateSampleAccountDEWithPopulatedValues()&lt;br /&gt;        {&lt;br /&gt;            // the account entity that fired the update&lt;br /&gt;            DynamicEntity account = new DynamicEntity("account");&lt;br /&gt;            account["accountid"] = new Key(Guid.NewGuid());&lt;br /&gt;            account["accountnumber"] = "someAccountNumber";&lt;br /&gt;            account["name"] = "accountName";&lt;br /&gt;            return account;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void Execute_HappyPathExecutesAsExpected()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = GetDependencyInjectedPlugin();&lt;br /&gt;&lt;br /&gt;            SetupExpectationsThatCorrelationIDWillBeRead();&lt;br /&gt;&lt;br /&gt;            DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();&lt;br /&gt;            PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);&lt;br /&gt;&lt;br /&gt;            SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);&lt;br /&gt;            SetupExpectationThatCustomWebServiceIsCalledWithAccount(account);&lt;br /&gt;            SetupExpectationThatCreateCrmServiceIsCalled();&lt;br /&gt;&lt;br /&gt;            DynamicEntity dynamicEntityWithURL =&lt;br /&gt;                CreateAndSetupExpectationsThatDynamicEntityIsUpdatedWithURLFromWebService(account);&lt;br /&gt;&lt;br /&gt;            SetupExpectationsThatCorrelationIDWillBeRead();&lt;br /&gt;            SetupExpectationThatCRMWebServiceWillBeCalledWithUpdatedDynamicEntity(dynamicEntityWithURL);&lt;br /&gt;&lt;br /&gt;            plugin.Execute(context);&lt;br /&gt;            mock.VerifyAllExpectationsHaveBeenMet();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void New_DefaultDependencyIsCreatedCorrectly()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = new DemoPlugin();&lt;br /&gt;            Assert.IsInstanceOfType(typeof (MyWebService), plugin.webService);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void New_DependencyInjectionTakes()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = new DemoPlugin(webService);&lt;br /&gt;            Assert.AreEqual(webService, plugin.webService);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public class DynamicEntityMatcher : Matcher&lt;br /&gt;    {&lt;br /&gt;        private readonly DynamicEntity leftSide;&lt;br /&gt;&lt;br /&gt;        public DynamicEntityMatcher(DynamicEntity leftSide)&lt;br /&gt;        {&lt;br /&gt;            this.leftSide = leftSide;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public override bool Matches(object o)&lt;br /&gt;        {&lt;br /&gt;            if (!(o is DynamicEntity))&lt;br /&gt;                return false;&lt;br /&gt;&lt;br /&gt;            DynamicEntity rightSide = (DynamicEntity) o;&lt;br /&gt;            if (leftSide.Name != rightSide.Name)&lt;br /&gt;                return false;&lt;br /&gt;&lt;br /&gt;            foreach (Property property in leftSide.Properties)&lt;br /&gt;            {&lt;br /&gt;                if (!(rightSide.Properties.Contains(property.Name)))&lt;br /&gt;                    return false;&lt;br /&gt;                if (!(PropertiesAreEqual(leftSide[property.Name], rightSide[property.Name])))&lt;br /&gt;                    return false;&lt;br /&gt;            }&lt;br /&gt;            return true;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private bool PropertiesAreEqual(object leftSideProperty,&lt;br /&gt;                                        object rightSideProperty)&lt;br /&gt;        {&lt;br /&gt;            if (leftSideProperty == null &amp;&amp; rightSideProperty == null)&lt;br /&gt;                return true;&lt;br /&gt;            if (leftSideProperty == null)&lt;br /&gt;                return false;&lt;br /&gt;&lt;br /&gt;            if (leftSideProperty.GetType() != rightSideProperty.GetType())&lt;br /&gt;                return false;&lt;br /&gt;            if (leftSideProperty.GetType() == typeof (string))&lt;br /&gt;                return (string) leftSideProperty == (string) rightSideProperty;&lt;br /&gt;&lt;br /&gt;            PropertyInfo[] properties =&lt;br /&gt;                leftSideProperty.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);&lt;br /&gt;            foreach (PropertyInfo property in properties)&lt;br /&gt;            {&lt;br /&gt;                object leftValue = property.GetValue(leftSideProperty, null);&lt;br /&gt;                object rightValue = property.GetValue(rightSideProperty, null);&lt;br /&gt;                if (leftValue == null &amp;&amp; rightValue != null)&lt;br /&gt;                    return false;&lt;br /&gt;                if (leftValue != null &amp;&amp; !leftValue.Equals(rightValue))&lt;br /&gt;                    return false;&lt;br /&gt;            }&lt;br /&gt;            return true;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public override void DescribeTo(TextWriter writer)&lt;br /&gt;        {&lt;br /&gt;            writer.Write("DynamicEntities are equal");&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public class PluginRecursionAction : IAction&lt;br /&gt;    {&lt;br /&gt;        private readonly IPluginExecutionContext context;&lt;br /&gt;&lt;br /&gt;        public PluginRecursionAction(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            this.context = context;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        #region IAction Members&lt;br /&gt;&lt;br /&gt;        public void Invoke(Invocation invocation)&lt;br /&gt;        {&lt;br /&gt;            new DemoPlugin().Execute(context);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public void DescribeTo(TextWriter writer)&lt;br /&gt;        {&lt;br /&gt;            writer.Write("Plugin will recurse");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        #endregion&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the actual plugin class, refactored into it's own file:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;using System;&lt;br /&gt;using Microsoft.Crm.Sdk;&lt;br /&gt;&lt;br /&gt;namespace TDD_with_CRM_Plugin&lt;br /&gt;{&lt;br /&gt;    public class DemoPlugin : IPlugin&lt;br /&gt;    {&lt;br /&gt;        private static Guid correlationID;&lt;br /&gt;        internal IWebServiceWrapper webService;&lt;br /&gt;&lt;br /&gt;        public DemoPlugin() : this(new MyWebService())&lt;br /&gt;        {&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public DemoPlugin(IWebServiceWrapper webService)&lt;br /&gt;        {&lt;br /&gt;            this.webService = webService;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        #region IPlugin Members&lt;br /&gt;&lt;br /&gt;        public void Execute(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            if (IsRecursiveCall(context))&lt;br /&gt;                return;&lt;br /&gt;            DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];&lt;br /&gt;            string resultingUrl = webService.UpdateAccount(account);&lt;br /&gt;            ICrmService crmService = context.CreateCrmService(true);&lt;br /&gt;            account["new_sharepointurl"] = resultingUrl;&lt;br /&gt;            crmService.Update(account);&lt;br /&gt;            ClearCorrelationID();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        #endregion&lt;br /&gt;&lt;br /&gt;        private void ClearCorrelationID()&lt;br /&gt;        {&lt;br /&gt;            correlationID = Guid.Empty;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private bool IsRecursiveCall(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            if (correlationID == Guid.Empty)&lt;br /&gt;                correlationID = context.CorrelationId;&lt;br /&gt;            else if (correlationID == context.CorrelationId)&lt;br /&gt;                return true;&lt;br /&gt;            return false;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the IWebServiceWrapper interface and implementation put into their own class. Notice here that we never tested this class. We don't care about it's functionality with these tests. We just care that it's called.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;using Microsoft.Crm.Sdk;&lt;br /&gt;&lt;br /&gt;namespace TDD_with_CRM_Plugin&lt;br /&gt;{&lt;br /&gt;&lt;br /&gt;    public interface IWebServiceWrapper&lt;br /&gt;    {&lt;br /&gt;        string UpdateAccount(DynamicEntity account);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public class MyWebService : IWebServiceWrapper&lt;br /&gt;    {&lt;br /&gt;        #region IWebServiceWrapper Members&lt;br /&gt;&lt;br /&gt;        public string UpdateAccount(DynamicEntity account)&lt;br /&gt;        {&lt;br /&gt;            return "";&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        #endregion&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;You'll probably notice rather quickly that there is a lot more code in our test than our actual plugin. Don't be turned off by this. It just means you are thoroughly testing your code. If you make a change to your plugin you can quickly verify it all functions as like you expected without having to deploy and run through a full regression test suite through the CRM UI, which can be significanly more painful than this up-front coding.&lt;br /&gt;&lt;br /&gt;I hope this was useful to someone. If you have any questions or comments, please let me know.&lt;br /&gt;&lt;br /&gt;=-}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-7515346044960662901?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/7515346044960662901/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=7515346044960662901' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7515346044960662901'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7515346044960662901'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-7.html' title='TDDing a CRM 4.0 Plugin - Part 7'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-4537227480134219263</id><published>2008-07-29T15:10:00.004-06:00</published><updated>2008-07-29T15:53:14.678-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='sdk'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><title type='text'>TDDing a CRM 4.0 Plugin - Part 6</title><content type='html'>One thing that I haven't touched on so far is the testing of a read from the CRM WebService. We've sent it a record to update, but we haven't read from it yet. &lt;br /&gt;&lt;br /&gt;There are a few different approaches you can take to this:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Use a plugin that serializes the DynamicEntity in the Image PropertyBags to write to a file. You can then deserialize that file in test cases and use that as your sample objects. The downside to this is that you can only do this for DynamicEntity classes since that's all that is available in the plugins. If you are working with "core" objects, this does you no good.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Create your own sample objects from scratch. This can be very tedious (less so if you're working with core objects, which I don't recommend) and prone to errors as it's likely you'll use the wrong Property type with the wrong attribute (LookupProperty with the "owner" attribute).&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Use an Object Builder/Mother to create random objects at runtime. The problem here is that we're talking "random" and that's usually not good unless you want to put significant coding behind making them "smart random". In which case, why not just write the samples from scratch.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;The #1 approach is my favorite as it makes for less coding and more realiable objects. However, it means that anytime you want to retrieve objects from the CRM WebService you can't use the standard Retrieve and RetrieveMultiple commands as they return "core" objects by default.&lt;br /&gt;&lt;br /&gt;#2 is pretty straight forward and we've already done it before:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;DynamicEntity account = new DynamicEntity("account");&lt;br /&gt;account["accountid"] = new Key(Guid.NewGuid());&lt;br /&gt;account["accountnumber"] = "someAccountNumber";&lt;br /&gt;account["name"] = "accountName";&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;#3, well, I don't even want to get into that. Have fun with it though. =-} &lt;br /&gt;&lt;br /&gt;Creating a plugin that will serialize your objects is pretty easy:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="C#"&gt;&lt;br /&gt;    public class SerializeEntityPlugin : IPlugin&lt;br /&gt;    {&lt;br /&gt;        public void Execute(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            DynamicEntity entity = (DynamicEntity) context.PostEntityImages["PostImage"];&lt;br /&gt;            XmlSerializer xmlSerializer = new XmlSerializer(typeof(DynamicEntity));&lt;br /&gt;            xmlSerializer.Serialize(new StreamWriter(@"c:\temp\" + GetFilename(entity), false), entity);&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private string GetFilename(DynamicEntity entity)&lt;br /&gt;        {&lt;br /&gt;            foreach (Property property in entity.Properties)&lt;br /&gt;            {&lt;br /&gt;                if (property.GetType() == typeof(KeyProperty))&lt;br /&gt;                {&lt;br /&gt;                    return entity.Name + "--" + ((KeyProperty) property).Value.Value + ".xml";&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            string returnValue = entity.Name + "--Unknown Primary Key.xml";&lt;br /&gt;            return returnValue;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Register this against an account and it's update event and then update an account through the UI (make sure the "C:\temp" directory exists). You should get a file called "account--&lt;someguid&gt;.xml".&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now we've got a serialized DynamicEntity object. We can deserialize this object and use it in our tests. To do that, first copy the resulting file to your solution. Make sure you setup the file to be copied to your output directory so that it's easily available during the runtime of the tests (I would also suggest renaming the file to not include the GUID so that you don't have to worry about it when referencing the filename).&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;        public DynamicEntity GetSampleAccount()&lt;br /&gt;        {&lt;br /&gt;            XmlSerializer xmlSerializer = new XmlSerializer(typeof (DynamicEntity));&lt;br /&gt;            return (DynamicEntity) xmlSerializer.Deserialize(new StreamReader(@"Sample Files\account.sample.xml").BaseStream) ;&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You could also refactor this out or create a wrapping method to get a variety of different objects for your testing. &lt;br /&gt;&lt;br /&gt;In the end, I recommend both techniques (not the 3rd one). You can get a long way with just creating your own objects but if your code depends on all the various CRM value types (Picklist, Lookup, CrmDateTime), then it can be pretty tedious to create the testing objects, whereas this is pretty simple if you don't mind a little plugin deployment.&lt;br /&gt;&lt;br /&gt;=-}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-4537227480134219263?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/4537227480134219263/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=4537227480134219263' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4537227480134219263'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4537227480134219263'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-6.html' title='TDDing a CRM 4.0 Plugin - Part 6'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-5640654315055509405</id><published>2008-07-29T14:27:00.008-06:00</published><updated>2008-07-29T15:52:46.391-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='sdk'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><title type='text'>TDDing a CRM 4.0 Plugin - Part 5</title><content type='html'>At the end of &lt;a href="http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-4.html"&gt;Part 4&lt;/a&gt; I mentioned a critical flaw with the current code of our plugin. It will naturally recurse and cause a stack overflow. This is because the plugin that triggers off of the updating of an account also updates that account.&lt;br /&gt;&lt;br /&gt;There are a few different ways you can prevent plugin recursion from happening. There are numerous sources that you can google for about how to do this. I'll be using the CorrelationID in the plugin context to catch this.&lt;br /&gt;&lt;br /&gt;However, we're more curious about how to test this to make sure it doesn't happen in our production code.&lt;br /&gt;&lt;br /&gt;At this point we have to get into specifics of the NMock library. While I'll discuss and show you the specific implementation of the test, I'll go over enough of the concepts that you should be able to use nearly any mocking framework to do the same.&lt;br /&gt;&lt;br /&gt;First, let's review the test:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    Expect.Once.On(crmService).Method("Update").With(new DynamicEntityMatcher(dynamicEntityWithURL));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here we're telling the NMock framework that we "expect" that the crmService will be called with a specific instance of the DynamicEntity object (which is checked by our special DynamicEntityMatcher class we created during the last post).&lt;br /&gt;&lt;br /&gt;We're going to modify this to tell NMock that when this happens, we expect something else to occur as a result. If we look at the definition of the With method we see it returns an IMatchSyntax interface. One of the methods on that interface is the Will method. The Will method takes an IAction interface. We've used an instance of this interface before:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here our IAction is the result of the Return.Value() method and we're saying our method call (CreateCrmService) will return a specific value (crmService). So we just need to create a new IAction:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    public class PluginRecursionAction : IAction&lt;br /&gt;    {&lt;br /&gt;        private readonly IPluginExecutionContext context;&lt;br /&gt;&lt;br /&gt;        public PluginRecursionAction(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            this.context = context;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public void Invoke(Invocation invocation)&lt;br /&gt;        {&lt;br /&gt;            new DemoPlugin().Execute(context);   &lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public void DescribeTo(TextWriter writer)&lt;br /&gt;        {&lt;br /&gt;            writer.Write("Plugin will recurse");&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And then update our previous Expect to use this action:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;Expect.Once.On(crmService).Method("Update").With(new DynamicEntityMatcher(dynamicEntityWithURL)).Will(new PluginRecursionAction(context));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now if we run our test case we'll see something we saw a while back:&lt;br /&gt;&lt;br /&gt;NMock2.Internal.ExpectationException: unexpected invocation of pluginExecutionContext.PostEntityImages&lt;br /&gt;&lt;br /&gt;Knowing our code, this means it's going through and trying to run a second time. Success!&lt;br /&gt;&lt;br /&gt;Let's now make our code a little smarter. Since we're going to use the CorrelationID on the context to determine if we're in a recursive call we need to setup the expectation that it will be read from our mock object:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    Guid correlationID = Guid.NewGuid();&lt;br /&gt;    Expect.Once.On(context).GetProperty("CorrelationId").Will(Return.Value(correlationID));&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In fact, we expect that it will be read twice. Once for the initial call and a second time for the result of our custom IAction. So our final code:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    Guid correlationID = Guid.NewGuid();&lt;br /&gt;    Expect.Once.On(context).GetProperty("CorrelationId").Will(Return.Value(correlationID));&lt;br /&gt;    Expect.Once.On(context).GetProperty("CorrelationId").Will(Return.Value(correlationID));&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We can run our test again, although it doesn't exactly provide useful information. It's a good habit though... You should see an unexpected call to the PostEntityImages property and two unexecuted expectations for the CorrelationID.&lt;br /&gt;&lt;br /&gt;Now it's time to write the code. The basic principle is that I'll have a static variable in my plugin called "correlationID". I'll set this if it's empty to the current context's CorrelationID. If it's not empty, then I'll check to see if it matches the current context's CorrelationID. If it does, then I'm calling myself, if not, then it's something else.&lt;br /&gt;&lt;br /&gt;Here is the final code I end up with. Review it at your own pace. Now, this is from a previous plugin I worked with. I just noticed that there is a case where if two updates are occuring at the same time or nearly the same time, it could be problematic. I need to work through some tests for this and see what happens. I would not take this code to be production worthy at this point. Again, I'm teaching TDD with CRM plugins here, not proper CRM plugin development. =-}&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;        public void Execute(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            if (IsRecursiveCall(context))&lt;br /&gt;                return;&lt;br /&gt;            DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];&lt;br /&gt;            string resultingUrl = webService.UpdateAccount(account);&lt;br /&gt;            ICrmService crmService = context.CreateCrmService(true);&lt;br /&gt;            account["new_sharepointurl"] = resultingUrl;&lt;br /&gt;            crmService.Update(account);&lt;br /&gt;            ClearCorrelationID();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private void ClearCorrelationID()&lt;br /&gt;        {&lt;br /&gt;            correlationID = Guid.Empty;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private bool IsRecursiveCall(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            if (correlationID == Guid.Empty)&lt;br /&gt;                correlationID = context.CorrelationId;&lt;br /&gt;            else if (correlationID == context.CorrelationId)&lt;br /&gt;                return true;&lt;br /&gt;            return false;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now with this code in place and I re-run the test, it passes. I'm reasonably happy at this point (with the caveat already mentioned) that this plugin should not recurse.&lt;br /&gt;&lt;br /&gt;Stay tuned for the (possibly last) post in the series regarding how to mock the retrieval of an object from the CRM WebService.&lt;br /&gt;&lt;br /&gt;=-}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-5640654315055509405?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/5640654315055509405/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=5640654315055509405' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5640654315055509405'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5640654315055509405'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-5.html' title='TDDing a CRM 4.0 Plugin - Part 5'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-7501488184981871191</id><published>2008-07-29T10:19:00.004-06:00</published><updated>2008-07-29T11:33:52.220-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='sdk'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><title type='text'>TDDing a CRM 4.0 Plugin - Part 4</title><content type='html'>In &lt;a href="http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-3.html"&gt;Part 3&lt;/a&gt;, I showed you how to make the simple call to our external webservice. In this part, we'll expand upon that and show you how to make calls to the CRM WebService with information returned from the other webservice.&lt;br /&gt;&lt;br /&gt;However, let's add some context to all of this. Let's say we've got a nicely integrated system between CRM and Sharepoint 2007 (MOSS). Our webservice that we've called in Part 3 takes the account information we supplied and determines if a Sharepoint site needs to be provisioned, and if so, creates that site and populates it with some data. Again, for simplicity I'm not considering all the various issues involved with the integration. We're making this plugin very simple. Assume that all the serious logic and code is all in the webservice that we aren't developing here.&lt;br /&gt;&lt;br /&gt;This webservice is called and if a site is provisioned it returns a URL that we can store in CRM and provide as a link to the user so they can easily access the Sharepoint site.  For our example here we'll assume the site is provisioned and a URL is returned. We now need to update CRM with that information.&lt;br /&gt;&lt;br /&gt;First, let's expand our "Happy Path" test to have the Expect clause include a return value from the webservice call:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value("http://someurl"));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;But of course, before this will work at all, we also need to update our method signature in our interface and the implementing class to have a return value:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    public class MyWebService : IWebServiceWrapper &lt;br /&gt;    {&lt;br /&gt;        public string UpdateAccount(DynamicEntity account)&lt;br /&gt;        {&lt;br /&gt;            return "";&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public interface IWebServiceWrapper&lt;br /&gt;    {&lt;br /&gt;        string UpdateAccount(DynamicEntity account);&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;At this point we could run our tests again, but there's no point except to see we didn't break the code (from compiling). The test will still pass because all of our assertions (including the "VerifyAllExpectations...") were met.&lt;br /&gt;&lt;br /&gt;Now comes the fun part... we need to make sure the test verifies that the CRM webservice is called and sent the correct data.&lt;br /&gt;&lt;br /&gt;But wait! We don't have a CRM WebService. Ah, but we do! It's in the plugin context. And, thankfully, it's an interface, so we can mock it easily:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void Execute_HappyPathExecutesAsExpected()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = GetDependencyInjectedPlugin();&lt;br /&gt;            &lt;br /&gt;            DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();&lt;br /&gt;            PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);&lt;br /&gt;            SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);&lt;br /&gt;&lt;br /&gt;            Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value("http://someurl"));&lt;br /&gt;            &lt;br /&gt;            ICrmService crmService = mock.NewMock&lt;ICrmService&gt;();&lt;br /&gt;            Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));&lt;br /&gt;&lt;br /&gt;            plugin.Execute(context);&lt;br /&gt;            mock.VerifyAllExpectationsHaveBeenMet();&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We've said that the "context" object will have the CreateCrmService method called with "true" passed in as a parameter and it will return the crmService object we have mocked.&lt;br /&gt;&lt;br /&gt;Now comes the somewhat tricky part... we have this DynamicEntity object that represents our account. We want to update that to reflect the new website URL we got back from our custom webservice and then call the CRM webservice with that object. But remember that all of this code we're setting up is irrelevant of the actual execution of the code. &lt;br /&gt;&lt;br /&gt;In other words, even though I've laid out my test code to represent what's happening in the production code, this has no meaning in terms of the actual execution. If I setup this DynamicEntity object, setup all the expects, and then later update a property on it to reflect the new URL, the first usages of the DynamicEntity object will have the website url in it. So, I have to create a second instance of the DynamicEntity object that will represent the object with an updated URL.&lt;br /&gt;&lt;br /&gt;Let's get some code in front of you and perhaps this will make more sense:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void Execute_HappyPathExecutesAsExpected()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = GetDependencyInjectedPlugin();&lt;br /&gt;            &lt;br /&gt;            DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();&lt;br /&gt;            PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);&lt;br /&gt;            SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);&lt;br /&gt;&lt;br /&gt;            string resultingUrl = "http://someurl";&lt;br /&gt;            Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value(resultingUrl));&lt;br /&gt;            &lt;br /&gt;            ICrmService crmService = mock.NewMock&lt;ICrmService&gt;();&lt;br /&gt;            Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));&lt;br /&gt;&lt;br /&gt;            DynamicEntity dynamicEntityWithURL = CloneDynamicEntity(account);&lt;br /&gt;            dynamicEntityWithURL["new_sharepointUrl"] = resultingUrl;&lt;br /&gt;&lt;br /&gt;            Expect.Once.On(crmService).Method("Update").With(dynamicEntityWithURL);&lt;br /&gt;&lt;br /&gt;            plugin.Execute(context);&lt;br /&gt;            mock.VerifyAllExpectationsHaveBeenMet();&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So I've created a clone of the "account" object we used before and set the property on it with the url from our custom webservice. I've cheated here and used a CloneDynamicEntity method I developed for the ABetterCRM library. I then setup the expectation that it will be used in the Update method of our CRM WebService.&lt;br /&gt;&lt;br /&gt;Note: I'm assuming here the Account entity in CRM has been expanded to include a string attribute called "new_sharepointUrl"&lt;br /&gt;&lt;br /&gt;Run the test, and we get a failed test because not all expectations were called. Excellent. Let's write the production code now:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;        public void Execute(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];&lt;br /&gt;            string resultingUrl = webService.UpdateAccount(account);&lt;br /&gt;            ICrmService crmService = context.CreateCrmService(true);&lt;br /&gt;            account["new_sharepointurl"] = resultingUrl;&lt;br /&gt;            crmService.Update(account);&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Excellent. Now, we run our test and... WHAT?! It failed? &lt;br /&gt;&lt;br /&gt;"NMock2.Internal.ExpectationException: unexpected invocation of crmService.Update(&lt;Microsoft.Crm.Sdk.DynamicEntity&gt;)&lt;br /&gt;Expected:&lt;br /&gt;  1 time: crmService.Update(equal to &lt;Microsoft.Crm.Sdk.DynamicEntity&gt;) [called 0 times]"&lt;br /&gt;&lt;br /&gt;That's weird! We can double check out code 100 times and it looks right. &lt;br /&gt;&lt;br /&gt;The trick here is how CRM's DynamicEntity object and how NMock work together. NMock will use the object's Equals method by default to determine if objects are equal. However, if you read earlier &lt;a href="http://stateracrm.blogspot.com/2008/07/crm-dynamicentity-limitations-part-1.html"&gt;posts&lt;/a&gt; of mine you'll see that two DynamicEntity objects with the same values do not equate. So, how do we get around this?&lt;br /&gt;&lt;br /&gt;There are a couple of options here. You could create a new class that inherits from DynamicEntity and properly overrides the Equals method (my preference). However, if you run across this for other objects then you'll have to keep overriding. If you ever run across a sealed class, you'll be stuck. Let's see if we can take advantage of the NMock library.&lt;br /&gt;&lt;br /&gt;If we take a look at the NMock With method we see one option for the parameter is a Matcher class. So let's create our own version of this. Forgive me for not going into details here, but this technique will depend largely on the mocking library you use and each one has a different process for doing this. I don't want this to be too specific to NMock so I'll just give you the code for the matcher:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    public class DynamicEntityMatcher: Matcher&lt;br /&gt;    {&lt;br /&gt;        private readonly DynamicEntity leftSide;&lt;br /&gt;&lt;br /&gt;        public DynamicEntityMatcher(DynamicEntity leftSide)&lt;br /&gt;        {&lt;br /&gt;            this.leftSide = leftSide;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public override bool Matches(object o)&lt;br /&gt;        {&lt;br /&gt;            if (!(o is DynamicEntity))&lt;br /&gt;                return false;&lt;br /&gt;            &lt;br /&gt;            DynamicEntity rightSide = (DynamicEntity) o;&lt;br /&gt;            if (leftSide.Name != rightSide.Name)&lt;br /&gt;                return false;&lt;br /&gt;            &lt;br /&gt;            foreach (Property property in leftSide.Properties)&lt;br /&gt;            {&lt;br /&gt;                if (!(rightSide.Properties.Contains(property.Name)))&lt;br /&gt;                    return false;&lt;br /&gt;                if (!(PropertiesAreEqual(leftSide[property.Name], rightSide[property.Name])))&lt;br /&gt;                    return false;&lt;br /&gt;            }&lt;br /&gt;            return true;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private bool PropertiesAreEqual(object leftSideProperty,&lt;br /&gt;                                           object rightSideProperty)&lt;br /&gt;        {&lt;br /&gt;            if (leftSideProperty == null &amp;&amp; rightSideProperty == null)&lt;br /&gt;                return true;&lt;br /&gt;            if (leftSideProperty == null)&lt;br /&gt;                return false;&lt;br /&gt;&lt;br /&gt;            if (leftSideProperty.GetType() != rightSideProperty.GetType())&lt;br /&gt;                return false;&lt;br /&gt;            if (leftSideProperty.GetType() == typeof(string))&lt;br /&gt;                return (string)leftSideProperty == (string)rightSideProperty;&lt;br /&gt;&lt;br /&gt;            PropertyInfo[] properties = leftSideProperty.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);&lt;br /&gt;            foreach (PropertyInfo property in properties)&lt;br /&gt;            {&lt;br /&gt;                object leftValue = property.GetValue(leftSideProperty, null);&lt;br /&gt;                object rightValue = property.GetValue(rightSideProperty, null);&lt;br /&gt;                if (leftValue == null &amp;&amp; rightValue != null)&lt;br /&gt;                    return false;&lt;br /&gt;                if (leftValue != null &amp;&amp; !leftValue.Equals(rightValue))&lt;br /&gt;                    return false;&lt;br /&gt;            }&lt;br /&gt;            return true;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public override void DescribeTo(TextWriter writer)&lt;br /&gt;        {&lt;br /&gt;            writer.Write("DynamicEntities are equal");&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Without going line by line, it first compares the names of the two DynamicEntities, then it compares each of the properties using reflection.&lt;br /&gt;&lt;br /&gt;Now, with using this Matcher we get a passed test! Yay!&lt;br /&gt;&lt;br /&gt;However, there is something here that is very wrong and will cause huge problems. Recursion. This plugin will be registered on the post-update event of the account object. We just performed an update of the account object. This plugin will cause itself to fire. If we don't do anything to catch this, we will get a stack overflow very quickly.&lt;br /&gt;&lt;br /&gt;I will discuss how to handle this in Part 5.&lt;br /&gt;&lt;br /&gt;=-}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-7501488184981871191?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/7501488184981871191/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=7501488184981871191' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7501488184981871191'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7501488184981871191'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-4.html' title='TDDing a CRM 4.0 Plugin - Part 4'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-1109044700030199970</id><published>2008-07-29T09:54:00.006-06:00</published><updated>2008-07-29T11:33:48.157-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='sdk'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><title type='text'>TDDing a CRM 4.0 Plugin - Part 3</title><content type='html'>In &lt;a href="http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-2.html"&gt;Part 2&lt;/a&gt; I showed you how to TDD the reading of the PostImage entity images from the plugin context. This part will go over how to mock the call to the webservice.&lt;br /&gt;&lt;br /&gt;To review, at this point we have gotten the DynamicEntity object out of the plugin context. Now we need to do something useful with it. We will hand it off to a custom built webservice (this is not the CRM webservice) for external processing. There are many different ways to handle an integration like this. I'd prefer something a bit more fail-safe like having a Biztalk server receive the message and process it. In our case though, it couldn't matter less.&lt;br /&gt;&lt;br /&gt;So let's start with our test case to make sure our webservice method is called. The first question I always have when starting a new test case is whether to create a new one, or expand on an old one. In this case, we're adding to the basic "happy path" of the plugin. We could create a new test case but it would largely contain all the same code as before. Moreover, as soon as I added in the additional code, I'd have to add the test code (like the Expect calls) to the first test case. In the end, I'd end up with two identical test cases. I don't want to get into the debates here as they're off-track from the discussion at hand. For simplicity, I'm going to add to the old test case.&lt;br /&gt;&lt;br /&gt;I need to add a new "Expect" call to the test case to make sure the webservice is getting called.  Since the intent of the test is changing, let's rename the method to:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;public void Execute_HappyPathExecutesAsExpected()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I've already mocked the object out (IWebServiceWrapper webService) so no need to add additional code there.  So all there is to do is add the new expect clause:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void Execute_HappyPathExecutesAsExpected()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = GetDependencyInjectedPlugin();&lt;br /&gt;            &lt;br /&gt;            DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();&lt;br /&gt;            PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);&lt;br /&gt;            SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);&lt;br /&gt;&lt;br /&gt;            // new code&lt;br /&gt;            Expect.Once.On(webService).Method("UpdateAccount").With(account);&lt;br /&gt;            // end new code&lt;br /&gt;&lt;br /&gt;            plugin.Execute(context);&lt;br /&gt;            mock.VerifyAllExpectationsHaveBeenMet();&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If I run the test, I can see it fails because our method called "UpdateAccount" doesn't exist yet. We should resolve this error first because the test case fails in validating the test case code, not because the production code isn't written yet. So, add the UpdateAccount method to the IWebServiceWrapper interface and the implementing class (so the project compiles):&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;    public class MyWebService : IWebServiceWrapper &lt;br /&gt;    {&lt;br /&gt;        public void UpdateAccount(DynamicEntity account)&lt;br /&gt;        {&lt;br /&gt;            &lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public interface IWebServiceWrapper&lt;br /&gt;    {&lt;br /&gt;        void UpdateAccount(DynamicEntity account);&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Good, now when we run the test we see that not all of our expectations were met. Perfect! Now that we know our test is valid, we can write the code that makes the test pass:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="c#"&gt;&lt;br /&gt;        public void Execute(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];&lt;br /&gt;            webService.UpdateAccount(account);&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And when we run out test we can see it now passes!&lt;br /&gt;&lt;br /&gt;We'll skip the refactoring this time around as there isn't much to do other then just throwing the new Expect call into it's own method.&lt;br /&gt;&lt;br /&gt;Stay tuned for Part 4, where I'll show you how to simulate calls to the CRM WebService.&lt;br /&gt;&lt;br /&gt;=-}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-1109044700030199970?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/1109044700030199970/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=1109044700030199970' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1109044700030199970'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1109044700030199970'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-3.html' title='TDDing a CRM 4.0 Plugin - Part 3'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-5873318548977214067</id><published>2008-07-29T09:16:00.003-06:00</published><updated>2008-07-29T09:28:05.181-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='articles'/><title type='text'>MSCRM 4.0 Roadmap</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;Microsoft released a whitepaper which is essentially a roadmap for MSCRM 4.0 into 2009.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;a href="https://mbs.microsoft.com/downloads/customer/documentation/whitepapers/MD_CRM_StatementOfDirection_July08_FINAL.pdf"&gt;&lt;span style="font-family:trebuchet ms;"&gt;https://mbs.microsoft.com/downloads/customer/documentation/whitepapers/MD_CRM_StatementOfDirection_July08_FINAL.pdf&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;Some highlights include details around Mobility, Accelerators, BizTalk Integration and SharePoint web parts.  Many of these will be coming out in the second half of 2008.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;One interesting point is Microsoft's release updates for CRM Online which will include search engine keyword research and bidding as well as campaign management dashboards.  Between these enhancements and the forthcoming accelerators it will put MS in a much better competitive position against salesforce.com although far from the huge library in App Exchange.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-5873318548977214067?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/5873318548977214067/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=5873318548977214067' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5873318548977214067'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5873318548977214067'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/mscrm-40-roadmap.html' title='MSCRM 4.0 Roadmap'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-7981584383265541851</id><published>2008-07-29T08:51:00.004-06:00</published><updated>2008-07-29T09:08:17.102-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mobile'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>CRM Mobile Express for 4.0</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;I'm sure everyone has seen CRM Mobile Express for MSCRM v.3.0 which was released in August of 2006. Unfortunately there is no mobile version yet for MSCRM v.4.0 but I have seen a lot of discussion around whether Mobile Express will run with v.4.0. The good news is...yes! Since the v.3.0 web-services still exist in v.4.0 everything works as it did in v.3.0.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;There is only one small tweak you have to make to the Sitemap.xml code that is given in the Mobile Express documentation. Wherever you see "/MobileClient/HomePage.aspx" make sure you put "../.." in front of it (e.g. "../../MobileClient/HomePage.aspx"). The reason for this is the way the CRM application is referenced due to multi-tenancy. The SiteMap for v.4.0 needs to crawl up to the top-level virtual directory to access the web-service.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;You also have to put the Microsoft.Crm.Webservice.dll in the GAC on the CRM server.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;You can download CRM Mobile Express here: &lt;/span&gt;&lt;a href="http://www.codeplex.com/crmmobileexpress"&gt;&lt;span style="font-family:trebuchet ms;"&gt;http://www.codeplex.com/crmmobileexpress&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;If anyone has had less than favorable results with CRM Mobile Express using CRM v.4.0, let us know.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-7981584383265541851?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/7981584383265541851/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=7981584383265541851' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7981584383265541851'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7981584383265541851'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/crm-mobile-express-for-40.html' title='CRM Mobile Express for 4.0'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-8952215379581772850</id><published>2008-07-28T12:50:00.003-06:00</published><updated>2008-07-29T09:07:56.130-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='support'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>CRM 4.0 Solution Center</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;Microsoft has released a support site that lists some of the top issues, how-to and readme files. The site is far from a wealth of information but it is a great start:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;a href="http://support.microsoft.com/ph/12976"&gt;&lt;span style="font-family:trebuchet ms;"&gt;http://support.microsoft.com/ph/12976&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;Some helpful links on this page that I commonly run into are:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;a id="L_153740" onclick="return MS_HandleClick(this,'C_76104', true);" href="http://support.microsoft.com/kb/947423"&gt;&lt;span style="font-family:trebuchet ms;"&gt;How to change the Web site port after Dynamics CRM 4.0 is installed &lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a id="L_152499" onclick="return MS_HandleClick(this,'C_76103', true);" href="http://www.microsoft.com/downloads/details.aspx?FamilyID=3861e56d-b5ed-4f7f-b2fd-5a53bc71dafc&amp;amp;DisplayLang=en"&gt;&lt;span style="font-family:trebuchet ms;"&gt;Internet facing deployment scenarios &lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a id="L_153753" onclick="return MS_HandleClick(this,'C_76103', true);" href="http://support.microsoft.com/kb/946677"&gt;&lt;span style="font-family:trebuchet ms;"&gt;Installing Dynamics CRM 4.0 with the minimum required permissions &lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://support.microsoft.com/ph/12976"&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-8952215379581772850?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/8952215379581772850/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=8952215379581772850' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8952215379581772850'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8952215379581772850'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/crm-40-solution-center.html' title='CRM 4.0 Solution Center'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-8487899831908559848</id><published>2008-07-17T15:40:00.003-06:00</published><updated>2008-07-29T08:24:08.599-06:00</updated><title type='text'>TDDing a CRM 4.0 Plugin</title><content type='html'>To anyone reading, I just noticed that my screenshots of code are not &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;clickable&lt;/span&gt; and are entirely too small. I will look into a better way of showing you code and update the posts. Sorry for the &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_1"&gt;inconvenience&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;UPDATE: I've done a quick change to scale up some of the pics, but they look pretty bad still. I'll see what I can do to get real code in there instead of just pics.&lt;br /&gt;&lt;br /&gt;UPDATE 2: OK, I updated Part 2 of the TDD with better source code. If you like, please comment and I'll go back and update Part 1 as well.&lt;br /&gt;&lt;br /&gt;=-{&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-8487899831908559848?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/8487899831908559848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=8487899831908559848' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8487899831908559848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8487899831908559848'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin_17.html' title='TDDing a CRM 4.0 Plugin'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-1576431569211235001</id><published>2008-07-17T11:25:00.003-06:00</published><updated>2008-07-17T11:32:51.952-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Permission'/><category scheme='http://www.blogger.com/atom/ns#' term='Security'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Change Business Unit Permission</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;Ah, the CRM security model. Nothing is what it seems. I spent about an hour today trying to figure out what controlled the "Change Business Unit" permission and from what I can tell it is not the Business Management tab of a security role that controls this, but rather I had to give full permission on the Service Management tab for the following entities - Calendar, My Work Hours and Service as well as Search Availability and Browse Availability. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;The security model continues to amaze me at times.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_p6aAUJV-Iyo/SH-CB4uPDvI/AAAAAAAAAEM/s_Vs7S8F_Cw/s1600-h/security.jpg"&gt;&lt;img id="BLOGGER_PHOTO_ID_5224037061690855154" style="CURSOR: hand" alt="" src="http://4.bp.blogspot.com/_p6aAUJV-Iyo/SH-CB4uPDvI/AAAAAAAAAEM/s_Vs7S8F_Cw/s400/security.jpg" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div&gt;&lt;div&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-1576431569211235001?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/1576431569211235001/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=1576431569211235001' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1576431569211235001'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1576431569211235001'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/change-business-unit-permission.html' title='Change Business Unit Permission'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_p6aAUJV-Iyo/SH-CB4uPDvI/AAAAAAAAAEM/s_Vs7S8F_Cw/s72-c/security.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-2685281883425365620</id><published>2008-07-16T07:07:00.010-06:00</published><updated>2008-07-29T08:29:11.901-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='sdk'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><title type='text'>TDDing a CRM 4.0 Plugin - Part 2</title><content type='html'>&lt;p&gt;In &lt;a href="http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin.html"&gt;Part 1&lt;/a&gt;, I gave a quick introduction into the world of TDD. Nothing I did was specific to &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;CRM&lt;/span&gt;. During this post, I'll talk more about specific issues with &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;CRM&lt;/span&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;Plugin&lt;/span&gt; development.&lt;/p&gt;&lt;br /&gt;Let's start by taking a look at the existing code from Part 1:&lt;br /&gt;&lt;pre name="code" class="c-sharp"&gt;&lt;br /&gt;        public class DemoPlugin { }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Notice here that we haven't setup our &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;plugin&lt;/span&gt; to implement the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;IPlugin&lt;/span&gt; interface that is required in a &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;CRM&lt;/span&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;plugin&lt;/span&gt;. Since we hadn't tested anything around this we hadn't written the production code yet.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Let's update our class to implement the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_8"&gt;IPlugin&lt;/span&gt; interface:&lt;/p&gt;&lt;br /&gt;&lt;pre name="code" class="CSharp"&gt;   &lt;br /&gt;        public void Execute(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            throw new NotImplementedException();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We also need to start testing this code. But, what does this code do!? If you recall from Part 1, this code will fire when an account is created or updated and call a &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_9"&gt;webservice&lt;/span&gt;, passing vital information to that &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_10"&gt;webservice&lt;/span&gt;. We're considering vital information the account's Name, &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_11"&gt;GUID&lt;/span&gt; and Account Number. For the sake of this example we don't care what the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_12"&gt;webservice&lt;/span&gt; does with this information since we're mocking out that interface.&lt;br /&gt;&lt;br /&gt;So there are a few vital processes here:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Retrieve account information from the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_13"&gt;EntityImage&lt;/span&gt; passed in through the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_14"&gt;plugin&lt;/span&gt; context&lt;/li&gt;&lt;li&gt;Call the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_15"&gt;webservice&lt;/span&gt; with specific information&lt;/li&gt;&lt;/ul&gt;So now we need to start with writing out test case. The first thing we'll do is mock out the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_16"&gt;plugin&lt;/span&gt; context and tell &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_17"&gt;NMock&lt;/span&gt; that we'll retrieve the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_18"&gt;EntityImage&lt;/span&gt; information from it.&lt;br /&gt;&lt;br /&gt;I'm going to start here in skipping past a lot of the detail code analysis and things specific to TDD. I want to focus more on details regarding TDD with &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_19"&gt;CRM&lt;/span&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_20"&gt;Plugins&lt;/span&gt;. There are tons of good references out there and just a quick &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_21"&gt;google&lt;/span&gt; search for them will provide tons of good examples.&lt;br /&gt;&lt;br /&gt;One thing I will say is that I tend to setup my test cases in a specific format. The top portion sets up any mock objects. The next session sets up all expectations in order in which I expect them to occur in code. Next is the method call that I'm testing and then finally any assertions or, in this case, just verifying that all my expectations were met.&lt;br /&gt;&lt;br /&gt;This is my test case expecting that the entity image will be read from the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_22"&gt;plugin&lt;/span&gt; context:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="CSharp"&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void Execute_EntityImageIsReadFromContext()&lt;br /&gt;        {&lt;br /&gt;            Mockery mock = new Mockery();&lt;br /&gt;            IWebServiceWrapper webService = mock.NewMock&lt;IWebServiceWrapper&gt;();&lt;br /&gt;            IPluginExecutionContext context = mock.NewMock&lt;IPluginExecutionContext&gt;();&lt;br /&gt;            &lt;br /&gt;            DemoPlugin plugin = GetDependencyInjectedPlugin();&lt;br /&gt;&lt;br /&gt;            DynamicEntity account = new DynamicEntity("account");&lt;br /&gt;            account["accountid"] = new Key(Guid.NewGuid());&lt;br /&gt;            account["accountnumber"] = "someAccountNumber";&lt;br /&gt;            account["name"] = "accountName";&lt;br /&gt;&lt;br /&gt;            PropertyBag postEntityImages = new PropertyBag();&lt;br /&gt;            postEntityImages["postImage"] = account;&lt;br /&gt;&lt;br /&gt;            Expect.Once.On(context).GetProperty("PostEntityImages").Will(Return.Value(postEntityImages));&lt;br /&gt;&lt;br /&gt;            plugin.Execute(context);&lt;br /&gt;            mock.VerifyAllExpectationsHaveBeenMet();&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br/&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I don't want to dwell into the details here because it's specific to the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_23"&gt;NMock&lt;/span&gt; and &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_24"&gt;NUnit&lt;/span&gt; frameworks. But let's cover a few bigger points&lt;/p&gt;&lt;p&gt;First is I create a simple &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_25"&gt;DynamicEntity&lt;/span&gt; object that will represent the account record as it's passed into the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_26"&gt;plugin&lt;/span&gt; context. Notice here that I'm putting values in. This will be part of the "happy path" tests that will be written. Later on I'll show you how to test for non-happy-paths.&lt;/p&gt;&lt;p&gt;Next, I create a &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_27"&gt;PropertyBag&lt;/span&gt; object which will hold the account object created above.&lt;/p&gt;&lt;p&gt;Finally, I tell the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_28"&gt;NMock&lt;/span&gt; framework (that has mocked out the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_29"&gt;IPluginExecutionContext&lt;/span&gt; object) that I &lt;strong&gt;Expect&lt;/strong&gt; the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_30"&gt;PostEntityImages&lt;/span&gt; property to be read. When it is, I want to return the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_31"&gt;PropertyBag&lt;/span&gt; object I created above.&lt;/p&gt;&lt;p&gt;I'll skip the boring screenshot showing that my test doesn't pass because all the invocations weren't happening. &lt;/p&gt;&lt;p&gt;No, if I just implement one line of code I get a passing test:&lt;/p&gt;&lt;br /&gt;&lt;pre name="code" class="CSharp"&gt;&lt;br /&gt;        public void Execute(IPluginExecutionContext context)&lt;br /&gt;        {&lt;br /&gt;            DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br/&gt;&lt;br /&gt;&lt;p&gt;So that sure seemed like a lot of work in writing that test case just for just one line of code. I beg you not to look at it like this even though such an observation is inevitable. This is not about writing less code, this is about writing better code and more verifiable code. Later on we may change our code and not realize we accidentally got rid of this one line. Our unit test will show us quickly that we have whereas we'd otherwise build the assembly, register the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_32"&gt;plugin&lt;/span&gt;, go into &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_33"&gt;CRM&lt;/span&gt; and update an account before we got an error and a little upfront code in a unit test will save you lots in the end. If I didn't believe this I would be spending all this time blogging about it.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Let's continue, shall we?&lt;/p&gt;&lt;br /&gt;&lt;p&gt;We've got a passing test, but first I want to &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_34"&gt;refactor&lt;/span&gt; some things. As I mentioned before, &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_35"&gt;refactoring&lt;/span&gt; is important in TDD. Not only for your production code but &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_36"&gt;refactoring&lt;/span&gt; your test cases is immensely important in having easily maintainable test cases. You'll find yourself often wondering why you're writing test cases when you could just write the code and "be done" and having unmanageable test cases will only exacerbate that.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_37"&gt;Refactoring&lt;/span&gt; is as much an art as anything. I do not pretend that I'm skillful at it but after some simple &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_38"&gt;refactorings&lt;/span&gt; I get the following test cases (I have no need to &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_39"&gt;refactor&lt;/span&gt; my &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_40"&gt;plugin&lt;/span&gt; yet, except for maybe putting it in a proper file but I'll do that later):&lt;/p&gt;&lt;br /&gt;&lt;pre name="code" class="CSharp"&gt;&lt;br /&gt;    [TestFixture]&lt;br /&gt;    public class PluginTestCases&lt;br /&gt;    {&lt;br /&gt;        private Mockery mock;&lt;br /&gt;        private IWebServiceWrapper webService;&lt;br /&gt;        private IPluginExecutionContext context;&lt;br /&gt;&lt;br /&gt;        [SetUp]  // executes at the beginning of each test case. Executed only once per TextFixture&lt;br /&gt;        public void Setup()&lt;br /&gt;        {&lt;br /&gt;            mock = new Mockery();&lt;br /&gt;            webService = mock.NewMock&lt;IWebServiceWrapper&gt;();&lt;br /&gt;            context = mock.NewMock&lt;IPluginExecutionContext&gt;();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void New_DefaultDependencyIsCreatedCorrectly()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = new DemoPlugin();&lt;br /&gt;            Assert.IsInstanceOfType(typeof (MyWebService), plugin.webService);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void New_DependencyInjectionTakes()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = new DemoPlugin(webService);&lt;br /&gt;            Assert.AreEqual(webService, plugin.webService);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [Test]&lt;br /&gt;        public void Execute_EntityImageIsReadFromContext()&lt;br /&gt;        {&lt;br /&gt;            DemoPlugin plugin = GetDependencyInjectedPlugin();&lt;br /&gt;            &lt;br /&gt;            DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();&lt;br /&gt;            PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);&lt;br /&gt;            SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);&lt;br /&gt;&lt;br /&gt;            plugin.Execute(context);&lt;br /&gt;            mock.VerifyAllExpectationsHaveBeenMet();&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private DemoPlugin GetDependencyInjectedPlugin()&lt;br /&gt;        {&lt;br /&gt;            return new DemoPlugin(webService);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private void SetupExpectationsThatPostEntityImagePropertyBagIsRead(PropertyBag postEntityImages)&lt;br /&gt;        {&lt;br /&gt;            Expect.Once.On(context).GetProperty("PostEntityImages").Will(Return.Value(postEntityImages));&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private PropertyBag CreatePropertyBagWithAccount(DynamicEntity account)&lt;br /&gt;        {&lt;br /&gt;            PropertyBag postEntityImages = new PropertyBag();&lt;br /&gt;            postEntityImages["postImage"] = account;&lt;br /&gt;            return postEntityImages;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private DynamicEntity CreateSampleAccountDEWithPopulatedValues()&lt;br /&gt;        {&lt;br /&gt;            // the account entity that fired the update&lt;br /&gt;            DynamicEntity account = new DynamicEntity("account");&lt;br /&gt;            account["accountid"] = new Key(Guid.NewGuid());&lt;br /&gt;            account["accountnumber"] = "someAccountNumber";&lt;br /&gt;            account["name"] = "accountName";&lt;br /&gt;            return account;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br/&gt;&lt;br /&gt;&lt;p&gt;All I did here was rearrange some code to make it more readable. I can verify this by running all my tests again and seeing that they still all pass. A good &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_41"&gt;refactoring&lt;/span&gt; tool like &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_42"&gt;Resharper&lt;/span&gt; will greatly help in &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_43"&gt;refactoring&lt;/span&gt; and make you much more likely to do it.&lt;/p&gt;&lt;p&gt;A few points here that can be &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_44"&gt;refactored&lt;/span&gt;/improved upon:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The key value for the property bag "&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_45"&gt;postImage&lt;/span&gt;" should probably be pulled into either a configurable option or a &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_46"&gt;const&lt;/span&gt; value. Configurable is nice but it's probably something you just need in documentation for the &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_47"&gt;implementer&lt;/span&gt; of the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_48"&gt;plugin&lt;/span&gt; so that when they register it in the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_49"&gt;CRM&lt;/span&gt; system they know what images to setup the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_50"&gt;plugin&lt;/span&gt; to receive.&lt;/li&gt;&lt;li&gt;Regarding deployment (this isn't TDD specific, just a point I like to follow), when developing your &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_51"&gt;plugin&lt;/span&gt; you will likely be testing against &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_52"&gt;CRM&lt;/span&gt; directly (remember, TDD is a development tool, not a testing tool). You'll have to use one of the registration tools. It's a good idea to export your registration settings into an XML file and store that in your project for reference. This can also be included easily then in a deployment package (&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_53"&gt;msi&lt;/span&gt; file or otherwise) to ease deployment on the &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_54"&gt;implementer&lt;/span&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;That's it for this part. Stay tuned for part 3 where I show the testing of the call to the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_55"&gt;webservice&lt;/span&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;=-}&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-2685281883425365620?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/2685281883425365620/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=2685281883425365620' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2685281883425365620'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2685281883425365620'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin-part-2.html' title='TDDing a CRM 4.0 Plugin - Part 2'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-1841023876247422318</id><published>2008-07-14T13:24:00.016-06:00</published><updated>2008-07-29T11:34:30.542-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='sdk'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><title type='text'>TDDing a CRM 4.0 Plugin - Part 1</title><content type='html'>I was recently introduced to the wonderful world of TDD and I'll never turn back. I've spent less time with headaches, bugs and waiting for an IISReset to complete.&lt;br /&gt;&lt;br /&gt;However, when I got back into the world of CRM I was surprised to find little about how to TDD a CRM Plugin.&lt;br /&gt;&lt;br /&gt;If you are unfamiliar with TDD, you can find a useful primer over at &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;wikipedia&lt;/a&gt;. I'll cover the basics of TDD here in the first post and then cover CRM specifics in later posts.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;For this example I will be using &lt;a href="http://www.nunit.org/index.php"&gt;NUnit&lt;/a&gt; as the testing framework and &lt;a href="http://www.nmock.org/"&gt;NMock&lt;/a&gt; for mocking interfaces. You may also see some traces of &lt;a href="http://www.jetbrains.com/resharper/"&gt;Resharper&lt;/a&gt;. While by no means required to do proper TDD development you'll find your productivity greatly increased by using this tool (and no, they aren't paying me to say that. But if they wanted to I'd gladly accept their money).&lt;br /&gt;&lt;br /&gt;We'll be writing a plugin that is probably over simplistic. Upon account creation or updation, it will call a webservice. Integration with other systems is probably the thing I am asked most to write so this should cover a pretty common scenario.&lt;br /&gt;&lt;br /&gt;Let's get started.&lt;br /&gt;&lt;br /&gt;We'll begin with the basic NUnit test fixture and create a simple test case. Our dependency for CRM is handled by the PluginContext object that is passed in to us during runtime. But, we've also got a dependency on the webservice we'll be calling to. We need to make sure that our dependency is created correctly. This makes for a tightly coupled plugin but from my experience, that's OK. I doubt I'd take this approach in a fullblown application as a loosely coupled system usually makes for an easier system to maintain. However, for the sake of this example, I'm comfortable with it. The test case and test fixture end up like this:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img id="BLOGGER_PHOTO_ID_5222959906171922306" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 635px; CURSOR: hand; HEIGHT: 239px; TEXT-ALIGN: center" height="200" alt="" src="http://2.bp.blogspot.com/_Yt1QdBPh40E/SHuuXLt8J4I/AAAAAAAAABM/m5PzkFwjfO8/s400/basic+constructor.png" width="539" border="0" /&gt;&lt;br /&gt;Notice how I've created just enough code for this to compile and for the test to run. Resharper helps out immensly here as it recognizes what's missing and can create basic classes and interfaces with types inferred from method parameters and such. I've got the interface for my webservice, a simple instance of it, and my plugin class itself.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I run the unit test now and...&lt;/p&gt;&lt;img id="BLOGGER_PHOTO_ID_5222967896804222194" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 632px; CURSOR: hand; HEIGHT: 111px; TEXT-ALIGN: center" height="74" alt="" src="http://3.bp.blogspot.com/_Yt1QdBPh40E/SHu1oTJI5PI/AAAAAAAAABU/I6Ri4DDQkus/s400/basic+constructor+failed.png" width="535" border="0" /&gt;And good, I get my red/failed test. By reviewing the failure I can see that my unit test is correctly failing because what I expected (an instance of my MyWebService class) was null. Perfect. Now we'll write the implementation code to make the unit test pass: &lt;img id="BLOGGER_PHOTO_ID_5222968162672280962" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 626px; CURSOR: hand; HEIGHT: 171px; TEXT-ALIGN: center" height="158" alt="" src="http://2.bp.blogspot.com/_Yt1QdBPh40E/SHu13xlBhYI/AAAAAAAAAB8/x6rA638Vmes/s400/good+constructor.png" width="531" border="0" /&gt; And then re-run the unit test:&lt;img id="BLOGGER_PHOTO_ID_5222967900282424578" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 639px; CURSOR: hand; HEIGHT: 39px; TEXT-ALIGN: center" height="38" alt="" src="http://2.bp.blogspot.com/_Yt1QdBPh40E/SHu1ogGaDQI/AAAAAAAAABc/x1TGlLATW8E/s400/basic+constructor+pass.png" width="535" border="0" /&gt; And voila! We have our test passing so we know our dependencies will be setup correctly.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;However, when I unit test I want these to be "unit" tests and not integration tests. Therefore, I need to be able to inject my dependency. This allows me to ensure that calls to my webservice are happening correctly without actually standing up a webservice to receive those calls. &lt;/p&gt;&lt;p&gt;So, I'll write my unit test. Again, I've written just enough code to let it all compile but nothing to actually make the test pass. I run it and can see it correctly failing:&lt;/p&gt;&lt;img id="BLOGGER_PHOTO_ID_5222967907542830642" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 637px; CURSOR: hand; HEIGHT: 288px; TEXT-ALIGN: center" height="247" alt="" src="http://4.bp.blogspot.com/_Yt1QdBPh40E/SHu1o7JbDjI/AAAAAAAAABs/hGVrUDhEhaw/s400/DI+constructor+fail.png" width="537" border="0" /&gt; I'll go ahead and write the code to make it pass:&lt;img id="BLOGGER_PHOTO_ID_5222968474693759266" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 602px; CURSOR: hand; HEIGHT: 103px; TEXT-ALIGN: center" height="92" alt="" src="http://4.bp.blogspot.com/_Yt1QdBPh40E/SHu2J78vcSI/AAAAAAAAACE/kV91xC-JkjI/s400/DI+constructor.png" width="533" border="0" /&gt; And the passing test case:&lt;img id="BLOGGER_PHOTO_ID_5222967913000939282" style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: hand; TEXT-ALIGN: center" height="62" alt="" src="http://1.bp.blogspot.com/_Yt1QdBPh40E/SHu1pPevLxI/AAAAAAAAAB0/2FgmLuDUxbU/s400/DI+constructor+pass.png" width="475" border="0" /&gt; Now, a very important part of TDD is refactoring. Refactoring is important as it helps create reusable and maintainable code.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Right now this code has a fatal flaw... if I later write code in one constructor, I have to write it in both constrcutors. Writing code twice is the first indication that you need to refactor. So, let's rewrite the constructors a bit to see if we can remove this duplication. By making my default constructor call my dependency injected constructor I can ensure that any code I need to add to my constructor only needs to be added to the dependency injected constructor:&lt;/p&gt;&lt;img id="BLOGGER_PHOTO_ID_5222968865645646738" style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: hand; TEXT-ALIGN: center" height="101" alt="" src="http://3.bp.blogspot.com/_Yt1QdBPh40E/SHu2gsW705I/AAAAAAAAACM/fpmexK3oRoA/s400/Refactored+constructor.png" width="533" border="0" /&gt;&lt;br /&gt;&lt;p&gt;Now I can rerun my two tests and see they still pass so I know my code still has the same end effect:&lt;/p&gt;&lt;p&gt;&lt;img id="BLOGGER_PHOTO_ID_5222967906181099538" style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: hand; TEXT-ALIGN: center" height="83" alt="" src="http://1.bp.blogspot.com/_Yt1QdBPh40E/SHu1o2EwsBI/AAAAAAAAABk/2srACgXAKLY/s400/Both+tests+passing+after+refactor.png" width="525" border="0" /&gt;&lt;/p&gt;&lt;p&gt;At this point I should also refactor my unit tests, but there is little if anything I could do here so I'll skip it. &lt;strong&gt;Do not refactor your code and tests at the same time.&lt;/strong&gt; I also need to refactor my classes and interfaces into correct file structures but I won't cover that here (again, Resharper or another refactoring tool can help immensly with this).&lt;/p&gt;&lt;p&gt;There is a lot of discipline involved with TDD. It may seem like a lot of work upfront but from my experience with it that work upfront will save you more than enough work on the backend and makes it worth it. Making sure your tests fail first, being thorough in your test cases and effective refactoring will make for successful TDD development. TDD is a developing ideology and not a testing one. The name is misleading. By requiring the coder to write the unit tests first it makes him/her throughly decide on the input an output of a procedure without getting involved in the implementation details. By no means should unit testing be done in lieu of integration, system and user testing. If anyone thinks that unit testing is enough, remind them of the &lt;a href="http://en.wikipedia.org/wiki/Mars_Polar_Lander#Loss_of_lander"&gt;Mars Polar Lander&lt;/a&gt; (which likely failed because while the individual systems were unit tested the whole was not properly system tested).&lt;/p&gt;&lt;p&gt;Now, this was only a basic introduction into the world of TDD. I suggest you read some more on your own. Stay tuned for more posts that dive into specifics regarding TDD with the CRM API.&lt;/p&gt;&lt;p&gt;=-}&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-1841023876247422318?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/1841023876247422318/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=1841023876247422318' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1841023876247422318'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1841023876247422318'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/tdding-crm-40-plugin.html' title='TDDing a CRM 4.0 Plugin - Part 1'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_Yt1QdBPh40E/SHuuXLt8J4I/AAAAAAAAABM/m5PzkFwjfO8/s72-c/basic+constructor.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-3813782279015099515</id><published>2008-07-14T13:23:00.002-06:00</published><updated>2008-07-14T13:25:37.582-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='case studies'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='articles'/><title type='text'>Great MS-CRM site</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;In case you don't know about this site, it is a great way to stay plugged in to the Microsoft Dynamics CRM community with helpful news links, articles about ISV solutions and case studies.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;a href="http://msdynamicsworld.com/page/dynamics-crm-resource-center"&gt;&lt;span style="font-family:trebuchet ms;"&gt;http://msdynamicsworld.com/page/dynamics-crm-resource-center&lt;/span&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-3813782279015099515?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/3813782279015099515/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=3813782279015099515' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3813782279015099515'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3813782279015099515'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/great-ms-crm-site.html' title='Great MS-CRM site'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-8126927645736748484</id><published>2008-07-11T16:29:00.002-06:00</published><updated>2008-07-11T16:31:29.136-06:00</updated><title type='text'>Someone forgot to update the default About box.</title><content type='html'>This isn't CRM related, but I couldn't help but post it cause I laughed when I saw it.&lt;br /&gt;&lt;br /&gt;I was experimenting with the Visual Studio Add-in wizard. Step 5 was creating info for the About Box. Check out the default:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Yt1QdBPh40E/SHffKQQUjaI/AAAAAAAAAA8/ejH0khzMH6A/s1600-h/too+funny.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_Yt1QdBPh40E/SHffKQQUjaI/AAAAAAAAAA8/ejH0khzMH6A/s400/too+funny.jpg" alt="" id="BLOGGER_PHOTO_ID_5221887660214488482" border="0" /&gt;&lt;/a&gt;=-}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-8126927645736748484?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/8126927645736748484/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=8126927645736748484' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8126927645736748484'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8126927645736748484'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/someone-forgot-to-update-default-about.html' title='Someone forgot to update the default About box.'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_Yt1QdBPh40E/SHffKQQUjaI/AAAAAAAAAA8/ejH0khzMH6A/s72-c/too+funny.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-3935210960995758262</id><published>2008-07-11T14:52:00.004-06:00</published><updated>2008-07-15T16:27:17.636-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Pre-Filter Lookup</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;I ran across some code today that will pre-filter a lookup field based on a selection in another lookup field.&lt;br /&gt;&lt;br /&gt;Put this in the onChange of the first lookup:&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;document.filterLookup(crmForm.all.customerid, crmForm.all.contactid);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;Put this in the onLoad of the form:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;// Filtered lookup&lt;br /&gt;document.filterLookup = function (source, target) {&lt;br /&gt;if (IsNull(source) IsNull(target)) {&lt;br /&gt;return;&lt;br /&gt;}&lt;br /&gt;var name = IsNull(source.DataValue) ? '' : source.DataValue[0].name;&lt;br /&gt;target.additionalparams = 'search=' + name;&lt;br /&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Courier New;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;For a more in-depth method of achieving this, visit this blog: &lt;a href="http://jianwang.blogspot.com/2008/05/mysterious-crm-lookup-ii.html"&gt;http://jianwang.blogspot.com/2008/05/mysterious-crm-lookup-ii.html&lt;/a&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-3935210960995758262?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/3935210960995758262/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=3935210960995758262' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3935210960995758262'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3935210960995758262'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/pre-filter-lookup.html' title='Pre-Filter Lookup'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-2025334929574250438</id><published>2008-07-10T16:24:00.002-06:00</published><updated>2008-07-10T16:28:41.284-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sdk library ABetterCRM'/><title type='text'>A Better CRM Library</title><content type='html'>As you've seen from my last few blog posts here I've been working around a few of the frustrating design directions of the CRM SDK team.&lt;br /&gt;&lt;br /&gt;I'm excited to announce that I've created a CodePlex project so that all of this information can be shared. You can find it here:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.codeplex.com/ABetterCRM"&gt;http://www.codeplex.com/ABetterCRM&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;A few disclaimers though:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;This is still in development and testing. Use what you like but make sure nothing is put into a production environment without thorough testing on your side.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Ok, just one disclaimer...&lt;/p&gt;&lt;p&gt;This is licensed by the Apache 2.0 license. So, read it thoroughly if you are unfamiliar with it. &lt;/p&gt;&lt;p&gt;Any suggestions, bugs, comments, or fan-mail should be directed through the CodePlex project and everything (including criticism) is greatly appreciated.&lt;/p&gt;&lt;p&gt;Thanks!&lt;/p&gt;&lt;p&gt;=-}&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-2025334929574250438?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/2025334929574250438/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=2025334929574250438' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2025334929574250438'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2025334929574250438'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/better-crm-library.html' title='A Better CRM Library'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-5395768095768006103</id><published>2008-07-10T14:45:00.001-06:00</published><updated>2008-07-10T14:45:50.845-06:00</updated><title type='text'>CRM Webservices and .Net 3.5</title><content type='html'>Now, I haven't done a lot of experimenting on this but I ran into a bit of a pain today.&lt;br /&gt;&lt;br /&gt;I typically stick to doing development against the provided .dlls and not by adding web services. It poses it's own problems but also has it's advantages (for example, you don't actually have to have a CRM instance going to do CRM development. Nice!).&lt;br /&gt;&lt;br /&gt;I find that the MetadataService in the SDK dll's is sorely lacking (and in many ways completely non-functional) so I had to add it in as a webservice. However, with VS 2008 and .Net 3.5, the services weren't adding in correctly. For example, I didn't get a MetadataService proxy class. Kinda important, you'd think...&lt;br /&gt;&lt;br /&gt;Thank God for the "Advanced" button and being able to add a .Net 2.0 style web service in. Problem solved!&lt;br /&gt;&lt;br /&gt;=-}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-5395768095768006103?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/5395768095768006103/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=5395768095768006103' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5395768095768006103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5395768095768006103'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/crm-webservices-and-net-35.html' title='CRM Webservices and .Net 3.5'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-8336931150123468109</id><published>2008-07-10T14:34:00.002-06:00</published><updated>2008-07-10T14:42:40.658-06:00</updated><title type='text'>CRM Properties and Values: More About "Equals"</title><content type='html'>As you can see from a previous post, I'm not real happy with the DynamicEntity class and what I consider an improper implementations of the Equals method on it. I attempted to fix this. &lt;br /&gt;If you take a close look at the code I provided there was a second little trick in there I didn't touch on. I'm touching it now.&lt;br /&gt;&lt;br /&gt;Assume this:&lt;br /&gt;&lt;blockquote&gt;CrmBoolean leftSide = new CrmBoolean(true);&lt;br /&gt;CrmBoolean rightSide = new CrmBoolean(true);&lt;br /&gt;Assert.IsTrue(leftSide == rightSide);&lt;br /&gt;Assert.IsTrue(leftSide.Equals(rightSide));&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Can you guess what will happen with those assertions?! Yup, they fail.&lt;br /&gt;&lt;br /&gt;GRRR! Why?!&lt;br /&gt;&lt;br /&gt;Now, admittedly this is a little grayer of a subject. Conceptually speaking there is no way to tell that, from a business logic standpoint, that those should equal. Why do I say that?&lt;br /&gt;&lt;br /&gt;Well, what if leftSide is actually a CrmBoolean from the account object and rightSide is a CrmBoolean from the contact record. Now, even though they are both true they don't carry the same meaning.&lt;br /&gt;&lt;br /&gt;Ok, ok, I'll admit, that's pretty flimsy. I'm just trying to give MS the benefit of the doubt here on why they didn't do a data comparison here.&lt;br /&gt;&lt;br /&gt;In the previous post the code has to compare these types of objects to see if they're equal. It does it by using reflection on all public properties. This code was originally part of a general CRM Utility class I'm working on but I refactored it into the object itself. But that's neither here nor there.&lt;br /&gt;&lt;br /&gt;Let's assume for a moment (I love assumptions) that we actually want these properties to reflect a more realistic "Equals" operation.&lt;br /&gt;&lt;br /&gt;We'll start by creating a new object that inherits from CrmBoolean:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;public class CRMBoolean : Microsoft.Crm.Sdk.CrmBoolean { }&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Just like before, we'll override the Equals method (and some related methods) so that we do a data comparison and not just a pointer comparison:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt; &lt;/p&gt;&lt;blockquote&gt;public class CRMBoolean&lt;br /&gt;{&lt;br /&gt;public CRMBoolean(bool value)&lt;br /&gt;:&lt;br /&gt;base(value)&lt;br /&gt;{&lt;br /&gt;}&lt;br /&gt;public CRMBoolean()&lt;br /&gt;{&lt;br /&gt;}&lt;br /&gt;public override&lt;br /&gt;bool&lt;br /&gt;Equals(object obj)&lt;br /&gt;{&lt;br /&gt;if (obj == null)&lt;br /&gt;return false;&lt;br /&gt;if&lt;br /&gt;(obj.GetType() != typeof (CrmBoolean) &amp;amp;&amp;amp; obj.GetType() != typeof&lt;br /&gt;(CRMBoolean))&lt;br /&gt;return false;&lt;br /&gt;if (GetType() == typeof&lt;br /&gt;(string))&lt;br /&gt;return&lt;br /&gt;this == obj;&lt;br /&gt;PropertyInfo[] properties =&lt;br /&gt;GetType().GetProperties(BindingFlags.Instance&lt;br /&gt;BindingFlags.Public);&lt;br /&gt;foreach&lt;br /&gt;(PropertyInfo property in&lt;br /&gt;properties)&lt;br /&gt;{&lt;br /&gt;object leftValue =&lt;br /&gt;property.GetValue(this,&lt;br /&gt;null);&lt;br /&gt;object rightValue = property.GetValue(obj,&lt;br /&gt;null);&lt;br /&gt;if (leftValue&lt;br /&gt;== null &amp;amp;&amp;amp; rightValue != null)&lt;br /&gt;return&lt;br /&gt;false;&lt;br /&gt;if (leftValue !=&lt;br /&gt;null &amp;amp;&amp;amp;&lt;br /&gt;!leftValue.Equals(rightValue))&lt;br /&gt;return&lt;br /&gt;false;&lt;br /&gt;}&lt;br /&gt;return&lt;br /&gt;true;&lt;br /&gt;}&lt;br /&gt;public bool Equals(CRMBoolean&lt;br /&gt;obj)&lt;br /&gt;{&lt;br /&gt;return&lt;br /&gt;base.Equals(obj);&lt;br /&gt;}&lt;br /&gt;public override int&lt;br /&gt;GetHashCode()&lt;br /&gt;{&lt;br /&gt;return&lt;br /&gt;base.GetHashCode();&lt;br /&gt;}&lt;br /&gt;public static bool&lt;br /&gt;operator ==(CRMBoolean&lt;br /&gt;left,&lt;br /&gt;CRMBoolean right)&lt;br /&gt;{&lt;br /&gt;return&lt;br /&gt;Equals(left, right);&lt;br /&gt;}&lt;br /&gt;public&lt;br /&gt;static bool operator ==(CRMBoolean&lt;br /&gt;left,&lt;br /&gt;CrmBoolean right)&lt;br /&gt;{&lt;br /&gt;return&lt;br /&gt;Equals(left,&lt;br /&gt;right);&lt;br /&gt;}&lt;br /&gt;public static bool operator !=(CRMBoolean&lt;br /&gt;left,&lt;br /&gt;CrmBoolean right)&lt;br /&gt;{&lt;br /&gt;return !(left == right);&lt;br /&gt;}&lt;br /&gt;public&lt;br /&gt;static bool operator !=(CRMBoolean left,&lt;br /&gt;CRMBoolean right)&lt;br /&gt;{&lt;br /&gt;return&lt;br /&gt;!Equals(left, right);&lt;br /&gt;}&lt;br /&gt;}&lt;/blockquote&gt;However, here we have a slightly unfun task ahead of us. Namely, we have to override EVERY "CrmSomething" class in the library.&lt;br /&gt;&lt;br /&gt;I'm not going to cover how to do this, but I'm intending to include these types of objects in the future library I'm working on (wow, two posts now mentioning this, I sure better produce something!). I'll likely generate them.&lt;br /&gt;&lt;br /&gt;Once all the various "value" type objects in the CRM libraries are overridden with this "better" Equals method, then I'll no longer need to do that heavy-lifting comparison in the DynamicEntity's Equals method, which is always a bonus.&lt;br /&gt;&lt;br /&gt;=-}&lt;br /&gt;&lt;br /&gt;P.S. Still not caring too much about the formatting of the code. Sorry. Complain and I might fix it.&lt;br /&gt;&lt;p&gt; &lt;/p&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-8336931150123468109?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/8336931150123468109/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=8336931150123468109' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8336931150123468109'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8336931150123468109'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/crm-properties-and-values-more-about.html' title='CRM Properties and Values: More About &quot;Equals&quot;'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-8067667597226305802</id><published>2008-07-10T12:20:00.002-06:00</published><updated>2008-07-10T14:33:33.211-06:00</updated><title type='text'>CRM DynamicEntity Limitations - Part 1</title><content type='html'>&lt;p&gt;DynamicEntities in CRM are either a love or hate relationship. Either you love the flexibility of using them within code (no need to do WebService proxy updates as configuration changes are made in CRM) or you hate them for their lack of usability.&lt;/p&gt;&lt;p&gt;What do I mean by their lack of usability? Namely this:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Microsoft.Crm.Sdk.DynamicEntity leftSide = new&lt;br /&gt;Microsoft.Crm.Sdk.DynamicEntity("account");&lt;/p&gt;&lt;p&gt;Microsoft.Crm.Sdk.DynamicEntity rightSide = new&lt;br /&gt;Microsoft.Crm.Sdk.DynamicEntity("account");&lt;/p&gt;&lt;p&gt;leftSide.Properties.Add(new StringProperty("name", "Fabrikam"));&lt;/p&gt;&lt;p&gt;rightSide.Properties.Add(new StringProperty("name", "Fabrikam"));&lt;/p&gt;&lt;p&gt;Assert.IsTrue(leftSide == rightSide); // this will fail every time&lt;/p&gt;&lt;p&gt;Assert.IsTrue(leftSide.Equals(rightSide)); // this will fail every time&lt;br /&gt;too&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I find that incredibly annoying. I love the TDD style to coding and not having these two objects do a data style comparison kills me. I have rarely run into a need to do a reference comparison and if so, I'll explicitly call a ReferenceEquals. To me. "Equals" is always a data-comparison. I mean, if you do "test" == "test" you'll get True, right?&lt;/p&gt;&lt;p&gt;Now, I could always create routines that will compare DynamicEntity classes (data wise) during my unit testing, but then every time I have some comparison of objects I have to create a new matcher. I tend to use NMock and NUnit which means I've got two different libraries to work with and about 3-4 different types of matching interfaces I have to implement. Why would I want to do that!?&lt;/p&gt;&lt;p&gt;Now, I could go off on MS for this sorely lacking detail but I'll refrain. That is better left for a well thought-out and tested post going over ALL the pain points! =-}&lt;/p&gt;&lt;p&gt;Here's my solution (which will hopefully make it up on CodePlex at some point as part of a general "Better CRM Library" project.&lt;/p&gt;&lt;p&gt;I first take and create my own DynamicEntity class that inherits from MS's DynamicEntity:&lt;/p&gt;&lt;blockquote&gt;public class DynamicEntity : Microsoft.Crm.Sdk.DynamicEntity {}&lt;br /&gt;&lt;/blockquote&gt;&lt;p&gt;Now let's get to some overriding! Now, I wouldn't say this code has been thoroughly unit tested or integration tested, so use at your own risk. It is only meant to point you in the right direction for your own implementations:&lt;/p&gt;&lt;p&gt;&lt;br /&gt; &lt;/p&gt;&lt;blockquote&gt;public class DynamicEntity : Microsoft.Crm.Sdk.DynamicEntity&lt;br /&gt;{&lt;br /&gt;public&lt;br /&gt;DynamicEntity(string name)&lt;br /&gt;: base(name)&lt;br /&gt;{&lt;br /&gt;}&lt;br /&gt;public&lt;br /&gt;DynamicEntity()&lt;br /&gt;{&lt;br /&gt;}&lt;br /&gt;public override bool Equals(object obj)&lt;br /&gt;{&lt;br /&gt;if&lt;br /&gt;(!(obj is Microsoft.Crm.Sdk.DynamicEntity obj is&lt;br /&gt;DynamicEntity))&lt;br /&gt;{&lt;br /&gt;return false;&lt;br /&gt;}&lt;br /&gt;var rightSide = (DynamicEntity)&lt;br /&gt;obj;&lt;br /&gt;if (Name != rightSide.Name)&lt;br /&gt;return false;&lt;br /&gt;foreach (Property&lt;br /&gt;property in Properties)&lt;br /&gt;{&lt;br /&gt;if&lt;br /&gt;(!rightSide.Properties.Contains(property.Name))&lt;br /&gt;return false;&lt;br /&gt;if&lt;br /&gt;(!PropertiesEqual(Properties[property.Name],&lt;br /&gt;rightSide[property.Name]))&lt;br /&gt;{&lt;br /&gt;return false;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;return&lt;br /&gt;true;&lt;br /&gt;}&lt;br /&gt;public static bool operator ==(DynamicEntity&lt;br /&gt;left,&lt;br /&gt;DynamicEntity right)&lt;br /&gt;{&lt;br /&gt;if (Equals(left, null) &amp;amp;&amp;amp;&lt;br /&gt;Equals(right, null))&lt;br /&gt;{&lt;br /&gt;return true;&lt;br /&gt;}&lt;br /&gt;if (Equals(left,&lt;br /&gt;null))&lt;br /&gt;{&lt;br /&gt;return false;&lt;br /&gt;}&lt;br /&gt;return left.Equals(right);&lt;br /&gt;}&lt;br /&gt;public&lt;br /&gt;static bool operator !=(DynamicEntity left,&lt;br /&gt;DynamicEntity&lt;br /&gt;right)&lt;br /&gt;{&lt;br /&gt;return !(left == right);&lt;br /&gt;}&lt;br /&gt;public bool Equals(DynamicEntity&lt;br /&gt;obj)&lt;br /&gt;{&lt;br /&gt;return Equals(this, obj);&lt;br /&gt;}&lt;br /&gt;public override int&lt;br /&gt;GetHashCode()&lt;br /&gt;{&lt;br /&gt;int runningTotal = 0;&lt;br /&gt;runningTotal +=&lt;br /&gt;Name.GetHashCode();&lt;br /&gt;foreach (Property p in Properties)&lt;br /&gt;runningTotal +=&lt;br /&gt;p.GetHashCode();&lt;br /&gt;return runningTotal;&lt;br /&gt;}&lt;br /&gt;public static bool&lt;br /&gt;PropertiesEqual(object leftSide,&lt;br /&gt;object rightSide)&lt;br /&gt;{&lt;br /&gt;if (leftSide ==&lt;br /&gt;null &amp;amp;&amp;amp; rightSide == null)&lt;br /&gt;return true;&lt;br /&gt;if (leftSide ==&lt;br /&gt;null)&lt;br /&gt;return false;&lt;br /&gt;if (leftSide.GetType() !=&lt;br /&gt;rightSide.GetType())&lt;br /&gt;return false;&lt;br /&gt;if (leftSide.GetType() == typeof&lt;br /&gt;(string))&lt;br /&gt;return leftSide == rightSide;&lt;br /&gt;PropertyInfo[] properties =&lt;br /&gt;leftSide.GetType().GetProperties(BindingFlags.Instance&lt;br /&gt;BindingFlags.Public);&lt;br /&gt;foreach (PropertyInfo property in&lt;br /&gt;properties)&lt;br /&gt;{&lt;br /&gt;object leftValue = property.GetValue(leftSide,&lt;br /&gt;null);&lt;br /&gt;object rightValue = property.GetValue(rightSide, null);&lt;br /&gt;if&lt;br /&gt;(leftValue == null &amp;amp;&amp;amp; rightValue != null)&lt;br /&gt;return false;&lt;br /&gt;if&lt;br /&gt;(leftValue != null &amp;amp;&amp;amp; !leftValue.Equals(rightValue))&lt;br /&gt;return&lt;br /&gt;false;&lt;br /&gt;}&lt;br /&gt;return true;&lt;br /&gt;}&lt;br /&gt;}&lt;/blockquote&gt;&lt;p&gt;I haven't included the unit tests around this and I'm sure they don't have full code coverage anyway. But I want to get this out there.&lt;/p&gt;&lt;p&gt;This will hopefully be only one part of a whole series of these types of posts where I continue to expand and show how to handle some of the frustrating limitations of the CRM API. Let's just hope I've got the bench-time to get all them out.&lt;/p&gt;&lt;p&gt;=-}&lt;/p&gt;&lt;p&gt;P.S. Forgive me for the poor formatting of the code. &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-8067667597226305802?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/8067667597226305802/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=8067667597226305802' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8067667597226305802'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8067667597226305802'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/crm-dynamicentity-limitations-part-1.html' title='CRM DynamicEntity Limitations - Part 1'/><author><name>Matthew Bonig</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-3290341288975125564</id><published>2008-07-08T16:48:00.005-06:00</published><updated>2008-07-08T16:55:49.833-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MySpace'/><category scheme='http://www.blogger.com/atom/ns#' term='Social Networking'/><category scheme='http://www.blogger.com/atom/ns#' term='Oracle CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='Siebel'/><category scheme='http://www.blogger.com/atom/ns#' term='SalesLogix'/><category scheme='http://www.blogger.com/atom/ns#' term='Salesforce.com'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='Facebook'/><category scheme='http://www.blogger.com/atom/ns#' term='LinkedIn'/><category scheme='http://www.blogger.com/atom/ns#' term='API'/><category scheme='http://www.blogger.com/atom/ns#' term='SageCRM'/><category scheme='http://www.blogger.com/atom/ns#' term='OpenSocial'/><category scheme='http://www.blogger.com/atom/ns#' term='Google'/><category scheme='http://www.blogger.com/atom/ns#' term='CRM'/><category scheme='http://www.blogger.com/atom/ns#' term='Plaxo'/><title type='text'>Enabling Social Networking within CRM Applications</title><content type='html'>&lt;span style="font-size:130%;color:#339999;"&gt;A primer on integrating your CRM application to Social Networking Sites&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Over the past 6 months, there has been an increase in the demand for CRM application integration with Social Networking sites. This integration allows organizations to harvest relationships that have been developed by their employees using tools like Linked in and Plaxo.  In addition, this integration can be used for marketing purposes by creating corporate social networking sites on MySpace, FaceBook, LinkedIn, Plaxo, etc…&lt;br /&gt;&lt;br /&gt;These integrations can have a significant impact on CRM user adoptions by eliminating data entry and making CRM information far more comprehensive. While &lt;a href="http://www.oracle.com/corporate/press/2008_mar/crmondemand15.html"&gt;Oracle&lt;/a&gt; and Saleforce.com are leading the way with built in social networking features, most other CRM applications can be configured to incorporate social networking functionality. &lt;br /&gt;&lt;br /&gt;There are basically 4 levels of integration:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Static Linking.&lt;/strong&gt;  By far the easiest to implement but also the most limiting. With static linking you provide a link to the social network sites that are most commonly used by your CRM users.  You rely on the smarts of the social network site to do the rest. For instance, you may want to setup a research page in your CRM application and provide links to the search pages on Plaxo, Linked In and Hoovers.  This can be accomplished in almost any CRM application with some basic configurations. &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Dynamic Linking.&lt;/strong&gt;  Most CRM applications allow you to add iFrames with mashups or dynamic links or to other web pages.  The dynamic links will contain fields from the current record to allow you to navigate to the appropriate page on the destination site. &lt;br /&gt;a) Example of this for &lt;a href="http://stateracrm.blogspot.com/2008/07/integrating-linkedin-and-ms-crm.html"&gt;MS-CRM &amp;amp; LinkedIn&lt;/a&gt;; &lt;a href="http://blogs.msdn.com/crm/archive/2007/11/16/integrating-crm-4-0-contacts-with-facebook.aspx"&gt;MS-CRM &amp;amp; Facebook&lt;/a&gt;&lt;br /&gt;b) Example of this for &lt;a href="http://userscripts.org/scripts/show/16103"&gt;salesforce.com &amp;amp; LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Web Services Integration.&lt;/strong&gt; Develop web services calls to social Networking sites and integrate data directly into the CRM application.  This approach is ideal for creating new leads/prospects or establishing relationship maps. If the social networking site provides an API, the code can be written using the API. Otherwise a screen scraping approach can be used and data can be returned based on the current screen design.  A screen scraping approach is not ideal as it can break if the social networking site changes screen designs, but for sites without an open API it is the only option.&lt;br /&gt;a) Example of this for &lt;span style="color:#ff9900;"&gt;MS-CRM &amp;amp; LinkedIn&lt;/span&gt; (Contact us)&lt;br /&gt;b) Example of this for &lt;a href="http://findsellserve.com/page.aspx?action=viewarticle&amp;amp;articleid=101"&gt;SalesLogix &amp;amp; LinkedIn&lt;/a&gt;&lt;br /&gt;c) Example of this for &lt;a href="http://www.salesforce.com/appexchange/detail_overview.jsp?id=a033000000334SPAAY"&gt;salesforce.com &amp;amp; LinkedIn&lt;/a&gt;; &lt;a href="https://www.salesforce.com/appexchange/detail_overview.jsp?id=a0330000003z9bdAAA"&gt;salesforce.com &amp;amp; FaceBook&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;MultiSite Integration.&lt;/strong&gt;  You may wish to aggregate data from multiple Social Networking sites.  This can be accomplished by creating custom code that integrates with each site or by leveraging a common social networking framework.  There are two emerging frameworks &lt;a href="http://developers.facebook.com/fbopen/"&gt;fbOpen&lt;/a&gt; from Facebook and  &lt;a href="http://en.wikipedia.org/wiki/OpenSocial"&gt;Open Social&lt;/a&gt; from Google.&lt;br /&gt;a) &lt;a href="http://www.internetnews.com/article.php/3708831/Salesforcecom+Explains+Its+OpenSocial+Strategy.htm"&gt;salesforce.com OpenSocial Strategy&lt;/a&gt;&lt;br /&gt;b) &lt;a href="http://www.theregister.co.uk/2007/11/14/oracle_crm_open_social/"&gt;Oracle’s OpenSocial Strategy&lt;/a&gt;&lt;br /&gt; &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;span style="font-size:130%;color:#339999;"&gt;Developer/API Resources:&lt;/span&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/apis/opensocial/"&gt;OpenSocial API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blog.linkedin.com/blog/2007/06/the-linkedin--1.html"&gt;LinkedIn API Information&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.plaxo.com/api/"&gt;Plaxo API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://developer.myspace.com/community/"&gt;MySpace Development&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://developers.facebook.com/"&gt;FaceBook Developerment&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-3290341288975125564?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/3290341288975125564/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=3290341288975125564' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3290341288975125564'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3290341288975125564'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/enabling-social-networking-within-crm.html' title='Enabling Social Networking within CRM Applications'/><author><name>Host-Party.com Staff</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-4684203624619919753</id><published>2008-07-08T16:31:00.005-06:00</published><updated>2008-07-09T07:18:18.858-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Social Networking'/><category scheme='http://www.blogger.com/atom/ns#' term='LinkedIn'/><category scheme='http://www.blogger.com/atom/ns#' term='sample'/><category scheme='http://www.blogger.com/atom/ns#' term='Integration'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Integrating LinkedIn and MS-CRM</title><content type='html'>&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;span style="font-size:130%;color:#339999;"&gt;A simple example of using a dynamic URL to pull LinkedIn information into a MS-CRM iframe.&lt;/span&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;For this example we will create a simple integration with LinkedIn from the MS-CRM account record. This integration will allow CRM users to quickly see all contacts from this account that are in their LinkedIn network. This feature is enhanced significantly if you &lt;a href="http://www.linkedin.com/static?key=groups_faq"&gt;setup a group&lt;/a&gt; for your Organization within LinkedIn, because this will ensure you see all of the relationships across your organization. &lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;strong&gt;1) Create the iFrame&lt;br /&gt;&lt;/strong&gt;As a system administrator, go to the Settings tab and click Customizations. Select Customize Entities and then click the Account record. Click Forms and Views under the details section. Click the form record. Add a Tab and name it LinkedIn. Add a section and name it LinkedIn. Add an Iframe and name it IFRAME_LinkedIn. Uncheck restrict cross-frame scripting box and check the “automatically expand to use available space” box under the row layout section of the formatting tab. Click ok to save the iframe changes.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;2) Add Code&lt;br /&gt;&lt;/strong&gt;Click the form properties under the common tasks section of the account form. Select OnLoad from the Event List and click edit. Enter the following code:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;var CRM_FORM_TYPE_CREATE = 1;&lt;br /&gt;var CRM_FORM_TYPE_UPDATE = 2;&lt;br /&gt;switch (crmForm.FormType)&lt;br /&gt;{&lt;br /&gt;case CRM_FORM_TYPE_UPDATE:&lt;br /&gt;crmForm.all.IFRAME_LinkedIn.src="http://www.linkedin.com/search?search=&amp;amp;company=" + crmForm.all.name.DataValue+ "&amp;amp;currentCompany=currentCompany&amp;amp;searchLocationType=I&amp;amp;countryCode=us&amp;amp;postalCode=" + crmForm.all.address1_postalcode.DataValue+ "&amp;amp;distance=50&amp;amp;sortCriteria=3";&lt;br /&gt;break;&lt;br /&gt;}&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Click ok to close Event Detail Properties window. Click ok to close Form Properties window. Click the Save and Close button on the Account form.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;3) Publish&lt;/strong&gt;&lt;br /&gt;Click Publish from the Action menu on the Account Entity form. The change is now available to your application. &lt;/p&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_VCtOSK4xjA0/SHS52D738aI/AAAAAAAAAGk/0P1jg6unXCs/s1600-h/linkedin-mscrm.JPG"&gt;&lt;img id="BLOGGER_PHOTO_ID_5221002206449758626" style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: hand; TEXT-ALIGN: center" alt="" src="http://2.bp.blogspot.com/_VCtOSK4xjA0/SHS52D738aI/AAAAAAAAAGk/0P1jg6unXCs/s400/linkedin-mscrm.JPG" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="color:#339999;"&gt;Notes&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;strong&gt;Required Fields&lt;/strong&gt;&lt;br /&gt;Account Name and Zipcode are required fields for this integration and should be made as required fields in MS-CRM. The Zipcode requirement can be removed by removing the location references in the URL.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Sorting&lt;/strong&gt;&lt;br /&gt;The lists will be sorted by the default sorting (degrees and Name). This can be changed by changing the sortCriteria value.&lt;br /&gt;1: Number of Connections&lt;br /&gt;2: Degrees and Recommendations&lt;br /&gt;3: Degrees and Name&lt;br /&gt;4: Keyword (CompanyName) relevance&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Troubleshooting&lt;/strong&gt;&lt;br /&gt;If you get a security message when you open the account record or the following message in the LinkedIn iframe “Navigation to the webpage was canceled”, you need to change security settings in Internet Explorer to enable “Display Mixed Content”.&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_VCtOSK4xjA0/SHS6cywIriI/AAAAAAAAAGs/1DaDT0M0yJ8/s1600-h/linkedin-mscrm1.JPG"&gt;&lt;img id="BLOGGER_PHOTO_ID_5221002871852019234" style="FLOAT: left; MARGIN: 0px 10px 10px 0px; CURSOR: hand" alt="" src="http://2.bp.blogspot.com/_VCtOSK4xjA0/SHS6cywIriI/AAAAAAAAAGs/1DaDT0M0yJ8/s320/linkedin-mscrm1.JPG" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-4684203624619919753?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/4684203624619919753/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=4684203624619919753' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4684203624619919753'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4684203624619919753'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/integrating-linkedin-and-ms-crm.html' title='Integrating LinkedIn and MS-CRM'/><author><name>Host-Party.com Staff</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_VCtOSK4xjA0/SHS52D738aI/AAAAAAAAAGk/0P1jg6unXCs/s72-c/linkedin-mscrm.JPG' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-2885563251431166741</id><published>2008-07-03T15:05:00.000-06:00</published><updated>2008-07-11T15:05:52.682-06:00</updated><title type='text'>CRM Email Router Event Log</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;One of our IT Administrators found out that when the CRM 4.0 Email Router is running it will log an event in the server Application Event log each time an email is filtered. There is a fix for this that can be found at the following URL. I have also copied the specific section text below:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;&lt;br /&gt;&lt;a href="http://support.microsoft.com/kb/907490"&gt;http://support.microsoft.com/kb/907490&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;&lt;span style="font-family:arial;"&gt;&lt;span style="font-size:85%;"&gt;&lt;strong&gt;Microsoft Dynamics CRM 4.0 E-mail Router&lt;/strong&gt;&lt;br /&gt;You can update the Microsoft Dynamics CRM E-mail Router to enable tracing. To do this, you must modify the registry and XML service configurations.To enable verbose operations log level, you must manually update the configuration by updating the registry. To do this, follow these steps:&lt;br /&gt;1. Log on to the server where the Microsoft CRM E-mail Router is installed with local administrator privilege.&lt;br /&gt;2. Click Start, click Run, type regedit, and then click OK.&lt;br /&gt;3. Locate and then click the following registry subkey: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MSCRMEmail&lt;br /&gt;4. Right-click the LogLevel registry entry, click Modify, and then change the value to 3. By default, this value is set to 1.&lt;br /&gt;5. Update the Microsoft.Crm.Tools.EmailAgent.xml configuration file for the E-mail Router Service. To do this, follow these steps:&lt;br /&gt;a. In Windows Explorer, locate the Microsoft.Crm.Tools.EmailAgent.xml file. By default, this file is located in the following folder:&lt;br /&gt;SystemDrive:\Program Files\Microsoft CRM Email\Service&lt;br /&gt;b. Open the file by using Notepad or another text-editing program.&lt;br /&gt;c. In the file, examine the &amp;lt;systemconfiguration&amp;gt; node, and then scroll down the text toward the end of the file to find the following statement:&lt;br /&gt;&amp;lt;loglevel&amp;gt;1&amp;lt;/loglevel&amp;gt;&lt;br /&gt;By default, this value is set to 1 (one). Modify this statement so that it reads as follows:&lt;br /&gt;&amp;lt;loglevel&amp;gt;3&amp;lt;/loglevel&amp;gt;&lt;br /&gt;d. Save the file.&lt;br /&gt;e. Restart the E-mail Router Service. You will find a new event viewer log view that is named MSCRMEmailLog. It is in this event viewer log view that the events appear.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-2885563251431166741?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/2885563251431166741/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=2885563251431166741' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2885563251431166741'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2885563251431166741'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/07/crm-email-router-event-log.html' title='CRM Email Router Event Log'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-8570099596610885144</id><published>2008-06-17T15:04:00.000-06:00</published><updated>2008-07-11T15:05:15.110-06:00</updated><title type='text'>CRM 4.0 Gotchas</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;After implementing a bunch of CRM 4.0 projects I have compiled a short list of "gotchas" that I always share with people as I am gathering requirements for the project. Some of these were around in v3.0 but were not rectified in v.4.0. If anyone has additional ones that they would like to add in the comments I will update this posting.&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;Service Activities have a max length of 3 days (cannot be changed)&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;Bug in WF/Customization: &lt;/span&gt;&lt;span style="font-family:trebuchet ms;"&gt;If you customize an entity, all WFs associated are "soft" unpublished. If you change a WF and re-publish, JS on that entity is disabled.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;If you restrict Write access to an Account, Opportunities, Contacts, Cases associated to that Account cannot be created. One possible solution is to use Teams.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;The KB and Queue views cannot be modified (use c360 My Workplace instead)&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;You cannot access related data from the Recipient of a Phone Call activity in workflow.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;Contacts and Contract Lines cannot be removed from the Case form. They must be hidden with JavaScript.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;It takes 15mins for a published KB article to be indexed and searchable in the Knowledge Base.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;You cannot delete a KB template unless all articles using it are deleted.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;In order for an Opportunity to show in the Pipeline report it must have a revenue value entered.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-8570099596610885144?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/8570099596610885144/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=8570099596610885144' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8570099596610885144'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8570099596610885144'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/06/crm-40-gotchas.html' title='CRM 4.0 Gotchas'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-5935626818483251370</id><published>2008-06-11T16:58:00.005-06:00</published><updated>2008-07-18T09:16:07.971-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sales stage'/><category scheme='http://www.blogger.com/atom/ns#' term='workflow'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>CRM 4.0 Sales Process Workflow</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;I found a great article today on creating Sales Processes with Workflow in CRM 4.0. Unfortunately in my experience the workflow engine is pretty finicky, especially when it comes to using Stages. A lot of times it hangs on the wait timers.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;a href="http://www.bizitpro.com/weblog/entry/microsoft_crm_40_workflow_stage_example_upgraded_from_crm_30/"&gt;http://www.bizitpro.com/weblog/entry/microsoft_crm_40_workflow_stage_example_upgraded_from_crm_30/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;UPDATE:  Microsoft has released a hotfix to fix the wait timer issue:&lt;/span&gt;&lt;br /&gt;&lt;a href="http://support.microsoft.com/?kbid=951919"&gt;&lt;span style="font-family:trebuchet ms;"&gt;http://support.microsoft.com/?kbid=951919&lt;/span&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-5935626818483251370?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/5935626818483251370/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=5935626818483251370' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5935626818483251370'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/5935626818483251370'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/06/crm-40-sales-process-workflow.html' title='CRM 4.0 Sales Process Workflow'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-3870135475863692770</id><published>2008-06-02T15:37:00.005-06:00</published><updated>2008-07-08T16:59:30.305-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Hiding Action Menu items</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;Well, this is completely unsupported but we had to hide the Activate and Deactivate choices from the Actions menu on a form the other day. I highly suggest downloading the IE Developer Toolbar to accomplish this. It will assist with getting the ids for these two menu choices. Here is the javascript we implemented:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;//HIDE DEACTIVATE AND ACTIVATE MENU ITEM if (document.all._MIchangeStatedeactivate100145 != null) { document.all._MIchangeStatedeactivate100145.style.display = "none";} if (document.all._MIchangeStateactivate100146 != null) { document.all._MIchangeStateactivate100146.style.display = "none";}&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-3870135475863692770?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/3870135475863692770/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=3870135475863692770' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3870135475863692770'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3870135475863692770'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/06/hiding-action-menu-items.html' title='Hiding Action Menu items'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-2173586396326761870</id><published>2008-04-30T13:43:00.002-06:00</published><updated>2008-07-14T13:45:32.880-06:00</updated><title type='text'>Statera and SmartCatalog partnership</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;I recently had the pleasure of meeting the guys from SmartCatalog during a co-presentation we did on CRM 4.0.  Their product is great and interfaces very nicely with the MS-CRM platform.  Read about our partnership from an independent source here:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;a href="http://blog.tmcnet.com/telecom-crm/2008/04/18/crm-platform-from-mozes-endeavor-and-statera-amdocs-results-cegedim-de.asp"&gt;&lt;span style="font-family:trebuchet ms;"&gt;http://blog.tmcnet.com/telecom-crm/2008/04/18/crm-platform-from-mozes-endeavor-and-statera-amdocs-results-cegedim-de.asp&lt;/span&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-2173586396326761870?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/2173586396326761870/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=2173586396326761870' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2173586396326761870'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/2173586396326761870'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/04/statera-and-smartcatalog-partnership.html' title='Statera and SmartCatalog partnership'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-8560475324212665667</id><published>2008-03-20T16:30:00.002-06:00</published><updated>2008-07-08T17:00:18.664-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='form customization'/><category scheme='http://www.blogger.com/atom/ns#' term='attribute'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Disable all attributes on a form</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;I found this snippet of code today on a message board to disable all attributes on a form:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;for (var i = 0; i &lt;&gt;)&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;crmForm.all[i].Disabled = true;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;&lt;p&gt;&lt;span style="font-family:trebuchet ms;"&gt;Original post can be found here:&lt;br /&gt;&lt;/span&gt;&lt;a href="http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.crm.developer&amp;amp;tid=0ddb6aa6-3d3c-4299-835e-94a8bc9509c1"&gt;&lt;span style="font-family:trebuchet ms;"&gt;http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.crm.developer&amp;amp;tid=0ddb6aa6-3d3c-4299-835e-94a8bc9509c1&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-8560475324212665667?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/8560475324212665667/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=8560475324212665667' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8560475324212665667'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/8560475324212665667'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2008/05/disable-all-attributes-on-form.html' title='Disable all attributes on a form'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-4480803564117108369</id><published>2007-09-06T16:41:00.003-06:00</published><updated>2008-07-08T17:01:03.786-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='form customization'/><category scheme='http://www.blogger.com/atom/ns#' term='attribute'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Getting values from a lookup control</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;We recently needed to capture both the GUID and the text value from a CRM lookup control. I found some quick code to do this on the following newsgroup:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;&lt;a href="http://groups.google.com/group/microsoft.public.crm/browse_thread/thread/9bc6721768f0a1f1/4b5f5d0ff5972dc0?lnk=st&amp;amp;q=ms+crm+get+lookup+datavalue&amp;amp;rnum=1&amp;amp;hl=en#4b5f5d0ff5972dc0"&gt;http://groups.google.com/group/microsoft.public.crm/browse_thread/thread/9bc6721768f0a1f1/4b5f5d0ff5972dc0?lnk=st&amp;amp;q=ms+crm+get+lookup+datavalue&amp;amp;rnum=1&amp;amp;hl=en#4b5f5d0ff5972dc0&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;var lookupItem = new Array; &lt;/span&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&lt;br /&gt;lookupItem = crmForm.all.primarycontactid.DataValue;&lt;br /&gt;&lt;br /&gt;if (lookupItem[0] != null)&lt;br /&gt;{&lt;br /&gt;// The text value of the lookup.&lt;br /&gt;alert(lookupItem[0].name); &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;// The GUID of the lookup.&lt;br /&gt;alert(lookupItem[0].id);&lt;br /&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-4480803564117108369?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/4480803564117108369/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=4480803564117108369' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4480803564117108369'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/4480803564117108369'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/09/getting-values-from-lookup-control.html' title='Getting values from a lookup control'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-9011280332608039485</id><published>2007-09-06T16:41:00.000-06:00</published><updated>2008-05-27T16:41:39.955-06:00</updated><title type='text'>Quickly getting the GUID of the current form</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;Here is a quick snippet of javascript code to get the GUID of the current CRM form:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;crmForm.ObjectId&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-9011280332608039485?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/9011280332608039485/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=9011280332608039485' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/9011280332608039485'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/9011280332608039485'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/09/quickly-getting-guid-of-current-form.html' title='Quickly getting the GUID of the current form'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-3712135076307898387</id><published>2007-07-06T16:42:00.000-06:00</published><updated>2008-05-27T16:42:29.663-06:00</updated><title type='text'>Set Datetime field using Javascript</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;Here is the code to set the date and time of a datetime field using Javascript.  You can put this into your onLoad event handler.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;var today = new Date( );&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;today.setHours(14);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;today.setMinutes(00);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;crmForm.all.new_datefield.DataValue = today;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-3712135076307898387?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/3712135076307898387/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=3712135076307898387' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3712135076307898387'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/3712135076307898387'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/07/set-datetime-field-using-javascript.html' title='Set Datetime field using Javascript'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-1776720416927431770</id><published>2007-06-26T16:42:00.000-06:00</published><updated>2008-05-27T16:42:55.751-06:00</updated><title type='text'>Dynamically Changing an IFrame URL</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;Here is the code to dynamically change an IFrame URL. Put this code in your onLoad event handler on a form:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;var ProcessID = crmForm.all.new_fieldname.DataValue;&lt;br /&gt;IFRAME_report.location='http://www.google.com';&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Courier New;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;You can also hide an IFrame onLoad and display it when needed by including the following code:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;crmForm.all.IFRAME_report.style.display = 'none';&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;font-size:85%;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Trebuchet MS;font-size:85%;"&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-1776720416927431770?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/1776720416927431770/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=1776720416927431770' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1776720416927431770'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/1776720416927431770'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/06/dynamically-changing-iframe-url.html' title='Dynamically Changing an IFrame URL'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-7061371696242241688</id><published>2007-06-20T16:43:00.000-06:00</published><updated>2008-05-27T16:43:21.466-06:00</updated><title type='text'>Creating a button to call any Actions Menu function</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;I found a way to call the "Apply Rule" window and pass all the necessary parameters from a button on the form. This procedure can be used though to call any Actions Menu fuction on a form. Below is example code that creates a button on a custom entity. Place this code in the isv.config.xml file. The key piece of code is the JavaScript parameter value: onActionMenuClick('applyrule', 10023)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;The only thing you have to change is the ObjectId (10023 in the sample code below).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;code&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;Entity name="new_snapshot"&amp;gt;&lt;br /&gt;&amp;lt;ToolBar ValidForCreate="0" ValidForUpdate="1"&amp;gt;&lt;br /&gt;&amp;lt;Button Title="Run Process" ToolTip="Run Data Process" Icon="/_imgs/ico_18_4405.gif" Url="" JavaScript="onActionMenuClick('applyrule', 10023)" PassParams="0" WinParams="" AvailableOffline="false" WinMode="1"/&amp;gt;&lt;br /&gt;&amp;lt;/ToolBar&amp;gt;&lt;br /&gt;&amp;lt;/Entity&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;&lt;code&gt;&lt;p&gt;&lt;span style="font-family:trebuchet ms;"&gt;To call any other Actions Menu function, open up the form you want to the button on, view the html source of the form and search for the the Actions Menu fuction (e.g. Deactivate). Copy the JavaScript function into the JavaScript tag on a button.&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-7061371696242241688?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/7061371696242241688/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=7061371696242241688' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7061371696242241688'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7061371696242241688'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/06/creating-button-to-call-any-actions.html' title='Creating a button to call any Actions Menu function'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-9212945496867545956</id><published>2007-05-31T16:43:00.001-06:00</published><updated>2008-05-27T16:52:29.941-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='custom code'/><title type='text'>CRM 3.0: Converting DynamicEntity to a Core Entity</title><content type='html'>Microsoft was very kind. They gave the us the power of the DynamicEntity. Very handy. They also give us the hard-typed "core" entity objects.&lt;br /&gt;&lt;br /&gt;In the case of callouts core entities are not used and only dynamic entities are passed to the callout functions. Working with DynamicEntities though can be a pain, especially when it comes to changing one field and updating the record back in CRM. Since there is no direct access to a specific field, if you want to update a field you need to step through each of the Properties on the DynamicEntity until you find the one you want. Not real efficient if you ask me.&lt;br /&gt;&lt;br /&gt;So, this is where having a hard-typed "core" entity comes in handy. But how do you convert? EASY! Well, sorta easy. You see, MS provided a great little code snippet in the CRM SDK for doing such a conversion. It's right &lt;a href="http://msdn2.microsoft.com/en-us/library/aa681287.aspx"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;However, there is one little problem with this.&lt;br /&gt;&lt;br /&gt;So I started to play with this by creating a real simple test app and then retrieving an account DynamicEntity:&lt;br /&gt;&lt;br /&gt;tempService.Url = @"http://danubecrm:5555/MSCRMServices/2006/crmservice.asmx";&lt;br /&gt;tempService.Credentials = new System.Net.NetworkCredential("administrator","pass@word1","crmdomain");&lt;br /&gt;RetrieveRequest getEntity = new RetrieveRequest();&lt;br /&gt;TargetRetrieveDynamic target = new TargetRetrieveDynamic();&lt;br /&gt;target.EntityId = new Guid("f0705cff-1e0f-dc11-99e2-0003ff2689b7");&lt;br /&gt;target.EntityName = "account";&lt;br /&gt;getEntity.Target = target;&lt;br /&gt;getEntity.ColumnSet = new AllColumns();&lt;br /&gt;getEntity.ReturnDynamicEntities = true;&lt;br /&gt;RetrieveResponse response = (RetrieveResponse) tempService.Execute(getEntity);&lt;br /&gt;DynamicEntity tempDynamicEntity = (DynamicEntity)response.BusinessEntity;&lt;br /&gt;&lt;br /&gt;And then I fed it into the Convert function. CRASH!&lt;br /&gt;&lt;br /&gt;It turns out that the DynamicEntity comes back with the StateCode field being a "StateProperty" type. However, on the account object the StateCode field is an AccountStateInfo type. When the reflection methods tried to write the String value that came from the StateProperty.Value, it crashed cause there is no implicit converstion between a String and a AccountState object.&lt;br /&gt;&lt;br /&gt;So I modified the code conversion routines to ignore this field. I'm sure there is a better solution but right now there is a project to get done and I don't need to know the StateCode of the account to get it done.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-9212945496867545956?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/9212945496867545956/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=9212945496867545956' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/9212945496867545956'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/9212945496867545956'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/05/crm-30-converting-dynamicentity-to-core.html' title='CRM 3.0: Converting DynamicEntity to a Core Entity'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-7465104060669532074</id><published>2007-05-14T16:43:00.001-06:00</published><updated>2008-05-27T16:52:46.561-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='callout'/><title type='text'>CRM 3.0: Using QueryExpressionHelper to get a Contact's GUID</title><content type='html'>Here is an example of how to use QueryExpressionHelp to get a Contact's GUID.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;QueryExpressionHelper queryHelper = new QueryExpressionHelper(EntityName.contact);&lt;br /&gt;queryHelper.Columns.AddColumn("contactid");&lt;br /&gt;queryHelper.Criteria.Conditions.AddCondition("FieldNameFromEntity", ConditionOperator.Equal, thisIsTheParameter);&lt;br /&gt;queryHelper.Criteria.FilterOperator = LogicalOperator.And;&lt;br /&gt;BusinessEntityCollection coll = service.RetrieveMultiple(queryHelper.Query);&lt;br /&gt;contact user = (contact)coll.BusinessEntities[0];&lt;br /&gt;Guid syscoGuid = user.contactid.Value.ToString();&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-7465104060669532074?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/7465104060669532074/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=7465104060669532074' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7465104060669532074'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7465104060669532074'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/05/crm-30-using-queryexpressionhelper-to.html' title='CRM 3.0: Using QueryExpressionHelper to get a Contact&apos;s GUID'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-49625170879698185</id><published>2007-05-09T16:44:00.000-06:00</published><updated>2008-05-27T16:44:50.968-06:00</updated><title type='text'>The Importance of Proper User Removal</title><content type='html'>Recently I wrote an integration using SSIS and the CRM web services that created and updated accounts and contacts in CRM. However, there were some accounts which CRM refused to update returning the following error:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;p&gt;0x80040204  Invalid user auth&lt;/p&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Updating the records through IE posed no problems.&lt;br /&gt;&lt;br /&gt;Searching the web showed that this error only came up when using impersonation, which I wasn't doing.&lt;br /&gt;&lt;br /&gt;After reviewing the data it was determined that the owner of the records had been deleted from Active Directory. &lt;br /&gt;&lt;br /&gt;To fix this, all we needed to do was reassign the accounts to a different user and all worked well from then on.&lt;br /&gt;&lt;br /&gt;So, it's very important that when users leave the company that their accounts in AD don't get deleted. If this is a procedural requirement for the client, then make sure you re-assign any accounts. This should be done naturally in the process of a user leaving the company but can sometimes get missed.&lt;br /&gt;&lt;br /&gt;-M&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-49625170879698185?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/49625170879698185/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=49625170879698185' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/49625170879698185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/49625170879698185'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/05/importance-of-proper-user-removal.html' title='The Importance of Proper User Removal'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-7305284699577028028</id><published>2007-05-03T16:45:00.003-06:00</published><updated>2008-08-29T10:30:28.466-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='MS-CRM'/><title type='text'>Hiding and Disabling CRM fields with Javascript</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;I always forget the syntax for hiding and disabling fields in CRM. Below are some common examples:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:trebuchet ms;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;//hide contract and contract line fields&lt;br /&gt;crmForm.all.contractid_d.style.visibility = 'hidden';&lt;br /&gt;crmForm.all.contractdetailid_d.style.visibility = 'hidden'; &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Courier New;font-size:85%;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Courier New;font-size:85%;"&gt;//hide a tab&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;crmForm.all.tab3Tab.visibility = "hidden";&lt;/span&gt;&lt;br /&gt;&lt;p&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;//disable lookup fields&lt;br /&gt;var oField = crmForm.all.SOME_FIELD_ID;&lt;br /&gt;oField.Disabled = !oField.Disabled;&lt;/span&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;&lt;br /&gt;&lt;br /&gt;//disable radio button field&lt;br /&gt;crmForm.all.new_inboundoutbound.disabled = true;&lt;br /&gt;&lt;br /&gt;//disable datetime field&lt;br /&gt;if(crmForm.FormType!=4)&lt;br /&gt;{&lt;br /&gt;//disable data field&lt;br /&gt;var oField = crmForm.all.new_resolutiondate;&lt;br /&gt;oField.Disabled = !oField.Disabled;&lt;br /&gt;}&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-7305284699577028028?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/7305284699577028028/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=7305284699577028028' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7305284699577028028'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/7305284699577028028'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/05/hiding-and-disabling-crm-fields-with.html' title='Hiding and Disabling CRM fields with Javascript'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-6774995265715677047</id><published>2007-05-03T16:45:00.000-06:00</published><updated>2008-05-27T16:45:21.165-06:00</updated><title type='text'>Export CRM form field names</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;I recently had to export all the CRM form field names for a particular entity for a data mapping exercise. I needed the actual display names instead of the attribute schema names. I had to do all this through the front-end application since I didn't have access to the database. This is what I did:&lt;br /&gt;&lt;/span&gt;&lt;ol&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;Open Advanced Find and create a query (against the Contact entity in this case). &lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;Use the Edit Columns feature and add all available fields by going to "Add Columns" and choose the "Select All" checkbox which selects every field.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;Run the query and export the data to Excel.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;Open the Excel spreadsheet and copy the first row which contains all the column names (the CRM fields).&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:trebuchet ms;"&gt;On a new spreadsheet go to the Edit menu and choose "Paste Special". On the pop-up menu that appears check off the "Transpose" field. This will convert the columns to rows. Voila!&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-6774995265715677047?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/6774995265715677047/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=6774995265715677047' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/6774995265715677047'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/6774995265715677047'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/05/export-crm-form-field-names.html' title='Export CRM form field names'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1686741264350458430.post-484870910113469295</id><published>2007-05-02T16:46:00.001-06:00</published><updated>2008-05-27T16:53:02.703-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='workflow'/><title type='text'>Calling a Workflow Rule from a button</title><content type='html'>&lt;span style="font-family:trebuchet ms;"&gt;Here's a handy bit of code for calling a Workflow rule from a custom button in CRM. Put the code below into the Page_Load handler of an aspx page. In this example I call the aspx page and pass an Account record GUID (accountId) which is fed into the workflow rule.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;// CRM Service Setup&lt;br /&gt;CrmService service = new CrmService();&lt;br /&gt;service.Credentials = System.Net.CredentialCache.DefaultCredentials;&lt;br /&gt;service.Url = "http://server/mscrmservices/2006/crmservice.asmx";&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;// Setup the request&lt;br /&gt;string accountId;&lt;br /&gt;accountId = Request.QueryString["accountId"];&lt;br /&gt;ExecuteWFProcessRequest request = new ExecuteWFProcessRequest();&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;// Triggers Workflow process&lt;br /&gt;request.ProcessId = new Guid("{E4206735-0379-4DE5-A453-AC7A7DA1462B}");&lt;br /&gt;request.EntityMoniker = new Moniker();&lt;br /&gt;request.EntityMoniker.Id = new Guid(accountId);&lt;br /&gt;request.EntityMoniker.Name = EntityName.account.ToString();&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;// Execute the request&lt;br /&gt;ExecuteWFProcessResponse response = (ExecuteWFProcessResponse) service.Execute(request);&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1686741264350458430-484870910113469295?l=stateracrm.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateracrm.blogspot.com/feeds/484870910113469295/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1686741264350458430&amp;postID=484870910113469295' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/484870910113469295'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1686741264350458430/posts/default/484870910113469295'/><link rel='alternate' type='text/html' href='http://stateracrm.blogspot.com/2007/05/calling-workflow-rule-from-button.html' title='Calling a Workflow Rule from a button'/><author><name>Jeremy Johnson</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
