Showing posts with label customization. Show all posts
Showing posts with label customization. Show all posts


Retrieve Marketinglists attached to Campaign Activity

Today I've been doing some coding again and I've found an interesting situation. For my code I do need to find which marketing lists belong to a specific Campaign Activity. Apparently more people on the internet faced the same issue, but I haven't found anybody who supplied the answer on how to do that.

I've managed to find an approach which works for both the fetchXml as well as a QueryExpression. Here is the code for both of these options:

FetchXml:


StringBuilder sbFetchXml = new StringBuilder();
sbFetchXml.Append("<fetch version=\"1.0\" output-format=\"xml-platform\" mapping=\"logical\" distinct=\"true\">");
sbFetchXml.Append("<entity name=\"list\">");
sbFetchXml.Append("<attribute name=\"listname\"/><attribute name=\"listid\"/>");
sbFetchXml.Append("<order attribute=\"listname\" descending=\"true\"/>");
sbFetchXml.Append("<link-entity name=\"campaignactivityitem\" from=\"itemid\" to=\"listid\" visible=\"false\" intersect=\"true\">");
sbFetchXml.Append("<link-entity name=\"campaignactivity\" from=\"activityid\" to=\"campaignactivityid\" alias=\"aa\">");
sbFetchXml.Append("<filter type=\"and\">");
sbFetchXml.AppendFormat("<condition attribute=\"activityid\" operator=\"eq\" uitype=\"campaignactivity\" value=\"{0}\"/>", campaignActivityId);
sbFetchXml.Append("</filter>");
sbFetchXml.Append("</link-entity>");
sbFetchXml.Append("</link-entity>");
sbFetchXml.Append("</entity>");
sbFetchXml.Append("</fetch>");

string strXmlResult = service.Fetch(sbFetchXml.ToString());


QueryExpression:

ConditionExpression condActivityId = new ConditionExpression();
condActivityId.AttributeName = "activityid";
condActivityId.Operator = ConditionOperator.Equal;
condActivityId.Values = new object[] { campaignActivityId };

FilterExpression filter = new FilterExpression();
filter.FilterOperator = LogicalOperator.And;
filter.Conditions = new ConditionExpression[] { condActivityId };

LinkEntity leCampaignActivity= new LinkEntity();
leCampaignActivity.LinkFromEntityName = EntityName.campaignactivityitem.ToString();
leCampaignActivity.LinkFromAttributeName = "campaignactivityid";
leCampaignActivity.LinkToEntityName = EntityName.campaignactivity.ToString();
leCampaignActivity.LinkToAttributeName = "activityid";
leCampaignActivity.LinkCriteria = filter;

LinkEntity leCampaignActivityItem = new LinkEntity();
leCampaignActivityItem.LinkFromEntityName = EntityName.list.ToString();
leCampaignActivityItem.LinkFromAttributeName = "listid";
leCampaignActivityItem.LinkToEntityName = EntityName.campaignactivityitem.ToString();
leCampaignActivityItem.LinkToAttributeName = "itemid";
leCampaignActivityItem.LinkEntities = new LinkEntity[] {leCampaignActivity};

QueryExpression query = new QueryExpression();
query.EntityName = EntityName.list.ToString();
query.ColumnSet = new AllColumns();
query.LinkEntities = new LinkEntity[] { leCampaignActivityItem };

BusinessEntityCollection bec = service.RetrieveMultiple(query);

For me this worked, I hope this helps you as well!



Document your CRM installation

Everybody knows the importance of documentation of a project. Thanks to Merijn van Mourik and some other guys there is a tool which can help you documenting your solution. This is a free tool which you can download from Codeplex: CRM 4 documentation.

If you want to use this tool on a standalone machine or a machine without internet connection, then you should download and install the following files first:

vstor30.exe
vstor30sp1-KB949258-x86.exe

I do know that this is not a new tool, but not everybody heard about this before. Happy documentation!



Remove attributes from form which cannot be removed

There are some attributes on CRM default forms which you cannot remove. This post will show you how you can do this anyway. While doing so, please keep in mind that Microsoft must have good reasons to fix attributes on the form. Removing this might have implications, as always have a good backup ready.

The steps you need to follow to remove are:
- Export customizations for the specific entity
- Modify the form in the customizations xml
- Import the modified customizations

Since most of you are well known with CRM I will skip the Export and Import of customizations. If you need assistance with this, then don't try this change anyway :) To modify the customizations, open the file in notepad or another XML editor. In this file look for this path:

ImportExportXml - Entities - Entity - FormXml - forms - entity - form - tabs

Within tabs look for the tab with the correct name and similar for the sections. Get a bit familiar with the section xml once you have found the correct section. You'll find that there are a couple of lines that you need to remove. This is the example for the subjectid on the case entity:


<cell id="{a9859c32-0cdc-41b5-8e7e-3eb173cab4a8}">
<labels>
<label description="Onderwerp" languagecode="1043" />
<label description="Subject" languagecode="1033" />
</labels>
<control id="subjectid" classid="{270BD3DB-D9AF-4782-9025-509E298DEC0A}" datafieldname="subjectid" />
</cell>


Attributes which are locked on the form includes the following:
case - subjectid
case - contractid
case - contractdetailid

Note: Some of the attributes are used in hidden javascript codes. For instance the case contract contractid is used in the script for the customer onchange. You can also look at removing those scripts from the customizations file.



Walk through all elements on a form

In some cases it is required to set all attributes on a form to disabled or do some other logic like checking which attribute has been changed. To do this you do need to walk through each attribute of the CRM form. My fellow MVP Mitch Milam has posted the technique to do this more than a year ago:
http://blogs.infinite-x.net/2007/11/21/disabling-all-fields-on-a-crm-form/

The most important piece of this code is:


var iLen = crmForm.all.length;
for (i = 0; i < iLen; i++)
{
o = crmForm.all[i];
switch (o.tagName)
{
case "INPUT":
case "SELECT":
case "TEXTAREA":
case "IMG":
case "IFRAME":
if (o.id != "leadqualitycode")
{
o.disabled = true;
}
break;
default:
break;
}
}

This example does set all attributes to disabled, but another option would be to replace the o.disabled = true; line with something like:

if (o.IsDirty)
{
alert("The value of " + o.name + " has changed.");
}

This allows you to determine which attributes have been changed on the form.



Hiding elements: the next evolution

One of my colleagues, Richard McCormick, has written a blog post for the Avanade internal employees. He has asked me to make this post publically available since it might be interesting for more people. Read on and get great code samples on how to hide attributes, elements and sections.

Hiding Links & Controls in CRM 4

Before I go on, I should point out that this solution is technically unsupported by Microsoft as it goes outside the CRM SDK, however the customization is small and easy to reverse to get into a state where Microsoft is supported.

With that said, this post aims to provide a quick JavaScript solution for hiding both controls on a CRM page and links in the left-hand navigation page on an Entity Form.

The code below provides three functions that can be used to show/hide specific items on a CRM form:


var HIDE = 'none';
var SHOW = 'block';

// Function to show/hide CRM controls on a CRM form
// such as text boxes, lookups, pick-lists etc.
function SetCrmControlVisible(elementName, visibility)
{
SetElementVisible(elementName + '_c', visibility);
SetElementVisible(elementName + '_d', visibility);
}

// Fuction to show/hide specific elements on a CRM form
// such as the left-hand link items (More Addresses, Workflows
// and even custom ISV links
function SetElementVisible(elementId, visibility)
{
var elem = document.getElementById(elementId);
if (elem != null)
{
elem.style.display = visibility;
}
}

// Function to show/hide Navigation "Sections" in the left-hand
// links (Such as Sales, Marketing and Service)
function SetParentElementVisible(elementId, visibility)
{
var elem = document.getElementById(elementId);
if (elem != null && elem.parentElement != null)
{
elem.parentElement.style.display = visibility;
}
}

Add the above code to the Entity OnLoad (and ensure that you enable the event) and after that you are ready to show/hide elements on the CRM form.

By using the IE Developer toolbar, you can retrieve the id of the element to be hidden (note in IE 8 this come built in and is not a seperate download). Open an entity record (in this case a Contact), press CTRL+N to open it in a new window, and then select IE Developer Toolbar.

Using the controls, select the item you want to hide and get the element id. In this case I have selected the “Opportunities” link and the id is “navOpps



Using this, I can call the SetElementVisible function like this to hide the Opportunities link:

SetElementVisible('navOpps', HIDE);

To hide the entire “Sales” section from the left-hand navigation pane, I can once again find the id, but this time is of the parent item which would result in the following call to hide it:

SetParentElementVisible('_NA_SFA', HIDE);

Lastly, to hide an element on the form such as the “Job Title” field, you once again should use the IE Developer Toolbar to retrieve the element id and then call the SetCrmControlVisible function:

SetCrmControlVisible('jobtitle', HIDE);

By selecting either the textbox or the label, for the Job Title, the returned id will be “jobtitle_c” or “jobtitle_d” – remove everything including and after the “_” and pass that to the SerCrmControlVisible function and you are done!

Happy coding!



Displaying inactive records in associated view

In some situations you do want to show the inactive records in an associated view as well. My fellow MVP George Doubinski wrote an article about how to solve this by using a plugin:
http://crm.georged.id.au/post/2008/03/07/Displaying-inactive-records-in-associated-view.aspx.

In this post I will show that there are more routes to Rome. I've looked into the option of editing the query which is being executed against CRM. This seemed to be more easy then expected. What I did is:
- Export Customizations for the specific entity
- Open the XML file with a text editor
- Look for the section ""
- Look for the saved query with the localized name of the associated view
- In that saved query look for the filter that is specified in the columnset
- Remove the filter
- Save the Customizations file
- Import the Customizations file
- Publish the Customizations

Here's an example:


<ImportExportXml version="4.0.0.0" languagecode="1043" generatedBy="OnPremise">
<Entities>
<Entity>
<Name .... />
....
<SavedQueries>
<savedquery>
<columnsetxml>
<columnset version="3.0">
<column>fullname</column>
<column>createdon</column>
<column>statuscode</column>
<column>subject</column>
<column>leadid</column>
<ascend>createdon</ascend>
<!-- Delete from here -->
<filter type="and">
<condition column="statecode" value="0" operator="eq" />
</filter>
<!-- Delete till here -->
</columnset>
</columnsetxml>
....
</savedquery>
</SavedQueries>
....
</Entity>
</Entities>
....
</ImportExportXml>

Keep in mind that you do add the attribute StateCode or StatusCode to the associated view as well, so that end users can see which records are current and which are inactive.

One of the locations that this is useful, is when you are looking into relationships. The out of the box entity does allow you to select party 1, party 2, role 1 and role 2, but no real extensibility options. You could recreate that entity and add useful attributes like startdate and enddate. As soon as the end date has passed, then the record can be deactivated by using workflow. The fact that a person had a professional relationship with a company is information that you do want to keep visible, especially in the associated view.



Read-only URL Addressable Form

The SDK does describe URL Addressable Forms, but one thing which I do miss on this page, is details around how to link to a form in readonly format. This is done by using the following URL:
http://server/orgname/_forms/readonly/readonly.aspx?objTypeCode=objectTypeCode.
Of course you need to change the server to servername, orgname to organization name and the objectTypeCode to the entity object type code. For instance 1 for account or 1024 for product.



Using Advanced Find for FetchXML builder v4.0

Some time ago I have written an article about how to use the Advanced Find as FetchXML builder. Today I tried using the javascript:prompt() method on a CRM 4.0 deployment and found out that the code doesn't work anymore. This alert script does still work though.


javascript:alert(resultRender.FetchXml.value);

A slightly different code which might be easier to remember is:

javascript:alert(document.all.FetchXml.value);

With the popup selected you can press copy+c and paste the result in a notepad window.



Custom entities and attributes in a plug-in

You cannot create a webservice in Visual Studio when you're writing a plugin. Instead you need to use the method context.CreateCrmService(true);. There is only one single issue when using this approach. You cannot leverage the power of IntelliSense in Visual Studio. Visual Studio will not recognize your custom entities and attributes. How to work with this? The only correct answer is "Dynamic Entities".

To retrieve records based on some attribute values you could use a piece of code like this:


// Create the QueryByAttribute object.
QueryByAttribute query = new QueryByAttribute();
query.ColumnSet = new AllColumns();
query.EntityName = "new_customentity";
query.Attributes = new string[] { "new_customentityid" };
query.Values = new string[] { customEntityId };

// Create the request.
RetrieveMultipleRequest request = new RetrieveMultipleRequest();

// Set the request properties.
request.Query = query;
request.ReturnDynamicEntities = true;

// Execute the request.
RetrieveMultipleResponse response = (RetrieveMultipleResponse)service.Execute(request);

One of the most important details in this piece of code is this line:

request.ReturnDynamicEntities = true;

If you forget this line, you'll end up with this error message:

System.InvalidOperationException was unhandled by user code Message="There is an error in XML document (1, 509)." Source="System.Xml" StackTrace: at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events) at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle) at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall) at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters) at Microsoft.Crm.SdkTypeProxy.CrmService.Execute(Request Request) at Microsoft.Crm.Asynchronous.SdkTypeProxyCrmServiceWrapper.Execute(Object request) at ...your assembly information...

So, don't forget the ReturnDynamicEntities attribute and you should be fine to go!



Unauthorized error after deploying your custom application

I've been working on deploying an application today. I received the error "Unauthorized" all the time and did not know why. The website was deployed as a new virtual directory within the ISV folder, authentication was set to integrated windows authentication and the password which was supplied was correct. Still I was able to get this error message:


Server Error in '/ISV/CustomApplication' Application.
The request failed with HTTP status 401: Unauthorized. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.Net.WebException: The request failed with HTTP status 401: Unauthorized.Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below. Stack Trace:
[WebException: The request failed with HTTP status 401: Unauthorized.]
System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall) +551137
System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters) +204
Microsoft.Crm.Metadata.MetadataWebService.GetDataSet() +31
Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadDataSetFromWebService(Guid orgId) +301
Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadCacheFromWebService(LoadMasks masks, Guid organizationId) +40
Microsoft.Crm.Metadata.DynamicMetadataCacheFactory.LoadMetadataCache(LoadMethod method, CacheType type, IOrganizationContext context) +418
Microsoft.Crm.Metadata.MetadataCache.LoadCache(IOrganizationContext context) +324
Microsoft.Crm.Metadata.MetadataCache.GetInstance(IOrganizationContext context) +386
Microsoft.Crm.BusinessEntities.BusinessEntityMoniker..ctor(Guid id, String entityName, Guid organizationId) +115
Microsoft.Crm.Caching.UserDataCacheLoader.LoadCacheData(Guid key, ExecutionContext context) +323
Microsoft.Crm.Caching.ObjectModelCacheLoader`2.LoadCacheData(TKey key, IOrganizationContext context) +389
Microsoft.Crm.Caching.BasicCrmCache`2.CreateEntry(TKey key, IOrganizationContext context) +82
Microsoft.Crm.Caching.BasicCrmCache`2.LookupEntry(TKey key, IOrganizationContext context) +108
Microsoft.Crm.BusinessEntities.SecurityLibrary.GetUserInfoInternal(WindowsIdentity identity, IOrganizationContext context, UserAuth& userInfo) +344
Microsoft.Crm.BusinessEntities.SecurityLibrary.GetCallerAndBusinessGuidsFromThread(WindowsIdentity identity, Guid organizationId) +194
Microsoft.Crm.Authentication.CrmWindowsIdentity..ctor(WindowsIdentity innerIdentity, Boolean publishCrmUser, Guid organizationId) +279
Microsoft.Crm.Authentication.WindowsAuthenticationProvider.Authenticate(HttpApplication application) +605
Microsoft.Crm.Authentication.AuthenticationStep.Authenticate(HttpApplication application) +125
Microsoft.Crm.Authentication.AuthenticationPipeline.Authenticate(HttpApplication application) +66
Microsoft.Crm.Authentication.AuthenticationEngine.Execute(Object sender, EventArgs e) +513
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +92
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +64
Version Information: Microsoft .NET Framework Version:2.0.50727.1433; ASP.NET Version:2.0.50727.1433

After quite a search I managed to find this blog. There I learned that CRM is using an HttpModule for multitenancy. The http module will be called with an anonymous user account and will fail when it's requesting the metadata webservice. To make your own app working, you'll need to remove the HttpModule from being loaded. A copy paste piece from Cesar de la Torre's blog:

This is what you have to add to your own web.con file:

<system.web>
<httpModules>
<clear/>
</httpModules>

Also, if you are not using any CRM 4.0 Titan assembly within your app, you can also get rid of all the references, adding also the following:

<assemblies>
<clear/>
<add assembly="*"/>


Personally I prefer the removal of only the exact names instead of a generic "clear". What I ended up using is:

<httpModules>
<remove name ="CrmAuthentication"/>
<remove name ="MapOrg"/>
</httpModules>


If any of you finds a better approach I'm glad to hear that. Until then I'll be using this approach.



Convert lead to contact, account and/or opportunity programmatically

Update: The codeproject missed a line of code. After you have executed the InitializeFromRequest, then you'll get a InitializeFromResponse. This response has got a entity in it. This entity you will need to create. The entity does not yet get created by executing the InitializeFromRequest! The line to add is:


Guid entityId = service.Create(rps.Entity);

Today I needed some code which does convert a lead to a contact programmatically. Instead of just building, I first asked my mate Google if he knew how to do that. Kind as Google is, he told me that he didn't know, but he knew that Cem Onvar knows that. Cem has published the code on the codeproject website.

Basically it comes down to two steps. The first step is to disable the lead. This can be done by using the SetStateLeadRequest. The second step is to use an request class which is quite rare. It's the InitializeFromRequest class. On this class you can set the EntityMoniker attribute to the lead which you just disabled. On the same request you'll need to set the target entityname to the name of the entity you wish to convert the lead into (contact, account or opportunity) and you'll need to set the TargetFieldType. This would most likely be TargetFieldType.All. Now execute that request and you're done.

For the direct code look at the codeproject site.

Keep in mind that you can use this approach for all kind of conversions.



Plug-in not working

One of the most read pages on my blog is the list of reasons why a callout won't work. Since the release of CRM 4.0 we do not have callouts anymore, but we're using plug-ins for extending CRM. This article will contain all the reasons that I find on why a plug-in is not working.

Error:


"Unable to load the plug-in assembly"

There are multiple reasons why this error can occur. These reasons include:

- The plug-in is registered on disk, but the file is not located in the correct location (drive:\crm server folder\server\bin\assembly\). Keep in mind that if you use the registration tool that the plug-in will be registered, but the file will not be placed in the right location. This is a manual step.

- The plug-in is registered on disk and the file is located in the correct location, but the security settings are not correctly set on the assembly file. Once you have copied the file to the \server\bin\assembly folder, make sure that you check the permissions of the file. The user which is used to run the application pool in which CRM is running, should have read & execute rights. You can set this by going to the folder and right click on your plugin its dll file. Then press "Properties". Go to the tab "Security" and add the user which is used for the application pool. This user you can figure out in the IISManager. There look at the properties of the CRM website to find out which application pool is used. Then look in the application pool's properties to find out the identity. Most likely this is "Network Service" but it could be changed in your situation.

After setting the security settings, make sure to perform an IISreset.

- Your environment exists out of multiple servers and the plugin is registered on disk. In this situation make sure that the file is placed in the \server\bin\assembly folder on all servers. Also the security should be set correctly (see other possible reason).

Error:

"The request failed with HTTP status 401 unauthorized"

Possible reasons for this error include
- The code does use a reference to the webservices, but uses credentials which do not have enough privileges in CRM

- The plug-in does is created for the non default instance. In this case, look at the following kb article: http://support.microsoft.com/kb/948746. From there you can request a hotfix for this issue.

Error:


"Not have enough privilege to complete Create operation for an Sdk entity"

This error shows up even though the user is a System Administrator. The cause is that the user is not a member of the Deployment Administrators group in CRM Deployment Manager. Once you have added yourself to that group it will work. Keep in mind that you will need to login as an administrator to be able to open the Deployment Manager.

Error:


"The request failed with http status 400: Bad Request"

This error seems to come from a piece of code in the synchronous plug-in framework which forces the CrmService to use the localhost. George has written a post on the logic behind this.
http://crm.georged.id.au/post/2008/02/22/Synchronous-plugins-want-localhost.aspx
The solution is to either run the plug-in asynchronous or otherwise make the webservice available from the localhost.
Update: There is a hotfix available for this issue now: http://support.microsoft.com/kb/950542

As soon as I run into other issues with plug-ins, I'll definitely add them to this list.



Resizing windows

If you have a lot of data on an entity form, then you sometimes do want to force the page to open in full screen mode. There's a small javascript which will allow you to do this. Just add these two lines of code to the form onload of your entity form:


window.moveTo(0,0);
window.resizeTo(screen.availWidth, screen.availHeight);

Also, when you open a custom page you can also set the window size by using the same javascript function. In your code you can use the following function in the Page.OnLoad() to set the window size to match your requirements:

private void SetWindowSize(int iWidth, int iHeight)
{
StringBuilder sbResizeScript = new StringBuilder();
sbResizeScript.Append("\n");
ClientScript.RegisterClientScriptBlock(this.GetType(), "ResizeScript", sbResizeScript.ToString());
}


Happy resizing!



Stopping and continuing a save event

For some customizations you do need to stop the form onsave event, perform some business logic and continue the save event. An example would be that in specific conditions are met when a record is saved, then a popup will need to be shown. After filling in data in the popup and pressing a continue button on the popup, then the save operation would need to continue. The SDK helps in this situation. Look at the following page for more information. MSDN SDK: OnSave Event

On that page you will see that you can stop the onsave event by setting the 'event.returnValue = false'. Don't forget to follow that line with a 'return false'. This will cause the save procedure to stop right at that point. Otherwise statements after that will still be executed.

To call the save event from your javascript code, you can use the javascript functions crmForm.Save(); and crmForm.SaveAndClose(). By looking at the 'event.Mode' you can determine which event was executed before. If the code is 1, then it is a crmForm.Save(); or it is 2 for a crmForm.SaveAndClose(). There's only one small issue. There can be other save events as well. There's the 'save and new', 'save as completed', and also the 'send' for emails. Below is a list of save events with the corresponding javascript functions to call. Once again, this is not documented in the SDK, so this might change with a hotfix or new version.


Save
Code: 1
Function: crmForm.Save();


SaveAndClose
Code: 2
Function: crmForm.SaveAndClose();


Send
Code: 7
Function: send();


SaveAsCompleted
Code: 58
Function: SaveAsCompleted();


SaveAndNew
Code: 59
Function: crmForm.SubmitCrmForm(59, true, true, false);

Good luck!



Javascript and IFrames

I'm struggling with IFrames and JavaScript quite often. When you're working with this as well, the first thing to keep in mind is:

Deselecte the option "restrict cross site scripting" (in dutch "Het uitvoeren van scripts tussen frames beperken")

That usually saves a lot of troubles.

Furthermore, the next step is to create JavaScripts. One which I use quite often, is one which allows me to access a value in the IFrame. This is useful in onSave routines to copy data from the iframe to any of the CRM attributes. This is the code which I use:


crmForm.all.name.DataValue = crmForm.all.IFRAME_CustomIFrame.Document.formname.name.value;

In this example you do need to change the name of the attribute to which you want to save the data to (crmForm.all.name -> crmForm.all.yourSchemaName). The next modification is to change the name of the IFrame. When setting up an IFrame, you do supply a name. Change the IFRAME_CustomIFrame to IFRAME_YourIFrameName. Then on your custom html or aspx page, you do need to access the variable. That you can do by specifying the name of the form followed by the name of the attribute.

For other examples, look at the page of Jonas Deibe Playing around with iframes or Michael Höhne More JavaScript Code.



CrmDateTime conversion to DateTime

When working with CrmDateTime, you'll notice that this type does have a .value attribute, but the type of this attribute is string. There is no attribute to get the .net class DateTime out of your CrmDateTime. If you do want to use a DateTime anyway, you'd easily need to convert it. You could use any of these:


Convert.ToDateTime(crmdatetime.Value)
DateTime.Parse(crmdatetime.Value)

For more information on the CrmDateTime, look in the SDK:
http://msdn2.microsoft.com/en-us/library/aa613542.aspx



AttributeInfo.TypeName(int) != AttributeMetadata.Type.Name(float)

You might bump into an error message while importing an entity from a customizations file. The error show could be:


Error: lead: AttributeInfo.TypeName(int) != AttributeMetadata.Type.Name(float)

The cause of this error is a mismatch in attribute type. Usually a field has been removed in the other environment and has been recreated with another attribute type. The type in your destination CRM system of an attribute is the type displayed in the right side of the != statement. In the example above float. The type in the import file is the type displayed on the left side of the != statement. This is int in the example above. In order to get rid of this error message and to import the entity, you will need to remove the attribute from the destination system.

In the list of attributes, search for a custom attribute which has the same type as displayed on the right side of the equation. Determine if there is any of these which has been changed. Delete this attribute and import the entity again. Keep in mind that the attribute must be removed from the form first (also the published form) before it can be removed from the attribute list. Another thing to keep in mind, is that when you perform this action on a production system, that the data in the attribute will be removed. Since this is not recoverable, you should first perform the removal of the attribute by following the next steps:
- create a new custom temporary attribute
- copy the data from the old attribute to the temporary attribute for each record
- remove the attribute
- import the entity (and with that the new attribute)
- copy the data from the temporary attribute to the new attribute for each record
- remove the temporary attribute

If there are many custom attributes, it might be hard to locate which of these is causing the error to appear. Unfortunately the name is not in the error description and also the event viewer and tracing does not mention the exact attribute name. To find out which of the attributes is causing the error, you should start the database profiler. Start a new trace and run the import. As soon as the error shows up, stop the tracing. In the trace search for the last occurence of:

exec sp_executesql N'SELECT T1.AttributeId AS attributeid

When you have found this record, copy the whe query. When you run this query in the SQL Server Management Studio, then only a single record should be returned. One of the attributes in the result is the name attribute. This is the attribute which is changed and will need to be removed.

Disclaimer: Only perform actions on the database if you are comfortable with the technologies. Incorrect SQL statements can corrupt your database and cause mayor dataloss. Always make a backup before performing database queries against your CRM database.



Improve performance of Microsoft Dynamics CRM

The performance of your CRM implementation is one of the key factors for customer acceptance. There are multiple options available for optimizing your system. Here's a list to get started:

Microsoft - Optimize Microsoft Dynamics CRM 3.0

Microsoft - Optimize Microsoft Dynamics CRM 1.2

Aaron Elder - Optimize the CRM Webservice Calls

Brein Reid - Reduce the amount of useless calls to IIS 6.0 Applications for optimal Performance

Some more tips:
- Do NOT virtualize your SQL Server.
- Run CRM from a computer with a physical network connection instead of wireless.
- In your code use as least webservice calls as posssible.
- Look in the implementation guide on how to cluster your SQL Server and/or load balance your webserver.
- If requirements allow you to do so, run the callout code asynchronous. In the callout just create an MSMQ message and create a windows service which listens to this MSMQ and execute the code.
- In imports and migrations, try to disable as many workflows and callouts as possible.



Fetch the ObjectTypeCode based on EntityName

Not a very rocket science, but just something again that I don't want to type over and over again. So here's a piece of code that helps you to fetch the ObjectTypeCode based on the EntityName for CRM 3.0:

private int GetObjectTypeCode (string entityName)
{
CrmMetaDataService.MetadataService metadataService = new CrmMetaDataService.MetadataService();
metadataService.Credentials = System.Net.CredentialCache.DefaultCredentials;
EntityMetadata entityData = metadataService.RetrieveEntityMetadata(entityName, EntityFlags.EntityOnly);
return entityData.ObjectTypeCode;
}


For CRM 4.0 this would be (thanks for the comment Pratima):
private int GetObjectTypeCode (string entityName)
{
CrmMetaDataService.MetadataService metadataService = new CrmMetaDataService.MetadataService();
metadataService.Credentials = System.Net.CredentialCache.DefaultCredentials;
EntityMetadata entityData = metadataService.RetrieveEntityMetadata(entityName, EntityFlags.EntityOnly);
return entityData.ObjectTypeCode.Value;
}


Have fun copy pasting :)



Creating Environment independent solutions

When developing a solution for CRM, make sure that it runs on all different crm environments. Many implementations have their CRM installation on another port compared to the development environments. A way to handle this, is to look at the location of MS CRM as it is stored in the registery.


// Set default values
private const string CRM_REG_DIRECTORY = @"software\Microsoft\mscrm\";
private const string CRM_SERVICE_PATH = @"/2006/crmservice.asmx";

// Create CrmService instance
service = new CrmService();
service.Url = GetCRMWebServiceRoot() + CRM_SERVICE_PATH;

/// <summary>
/// Returns the recorded web location from the registry
/// </summary>
private static string GetCRMWebServiceRoot()
{
string result = null;
try
{
RegistryKey regKey = Registry.LocalMachine.OpenSubKey(CRM_REG_DIRECTORY, false);
if (regKey != null)
{
result = (string) regKey.GetValue("ServerUrl");
}
else
{
throw new ApplicationException("Microsoft CRM could not be found on this computer.");
}
}
catch (Exception err)
{
throw new ApplicationException("Cannot retrieve registry value for CRM web service. " + err.Message);
}
return result;
}


Thanks to Richard McCormick for providing the codes :)