Thursday, December 20, 2007

How to download and save email activity attachments from CRM 3.0

Ever want to get an attachment from an Email in CRM 3.0?
Well you are in luck, here is how to do it.

1) Use fetchxml to get all attachments from a specific email or for any other emails matching your query, for simplicity sake, the fetch xml will be for a single email.

2) Email attachments will be returned in the result set complete with the contents of the original file in Base64 format, you will have to decode it.

Use the following method to get a nodelist for any query

static XmlNodeList GetFetchResult(string fetchXML)

{

CrmService service = new CrmService();

service.Credentials = new System.Net.NetworkCredential("CRMAdmin", "Pa$$w0rd", "ADVWORKS");

string fetch = fetchXML;

string ret = service.Fetch(fetch);

System.Xml.XmlDocument xdoc = new System.Xml.XmlDocument();

xdoc.LoadXml(ret);

System.Xml.XmlNodeList list = xdoc.SelectNodes("resultset/result");

xdoc = null;

return list;

}

All email attachments are kept in the entity "ActivityMimeAttachment", here is the xml for the query.

Replace the "activityID" value with any email value or add your conditions.

<fetch mapping='logical'>
<entity name='activitymimeattachment'>
<all-attributes/>
<filter type='and'>
<condition attribute='activityid' operator='eq' value='{A9B5DD63-D59E-DC11-920F-0003FF612152}'/>
</filter>
</entity>
</fetch>
Call the GetFetchResult into a new Nodelist and loop through it.
XmlNodeList list = GetFetchResult("");

if (list.Count > 0)

{

foreach (XmlNode n in list)

{
string body = n["body"].InnerText;
string decoded = base64Decode(body);

}

}

Use this method to convert the body of the email into it's original format.

static string base64Decode(string data)

{

try

{

System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();

System.Text.Decoder utf8Decode = encoder.GetDecoder();

byte[] todecode_byte = Convert.FromBase64String(data);

int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);

char[] decoded_char = new char[charCount];

utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);

string result = new String(decoded_char);

return result;

}

catch (Exception e)

{

throw new Exception("Error in base64Decode" + e.Message);

}

}
From here you can save the file, the filename is in the "filename" attribute of the result set.
Have a great day.
Oshri Cohen

Labels: , , , , , , , ,

Monday, December 3, 2007

How to attach a file to any CRM 3.0 entity

Introduction

Microsoft CRM 3.0 has the ability to store files as attachments for any entity that supports notes.
This article is a guide to how an article would be attached to a note (otherwise known as an annotation within CRM 3.0).
To understand how to attach a file in CRM 3.0, you need to understand CRM's file attachment architecture.
Almost all entities in the system can have notes, when manually attaching a file through the UI a reference to the file shows up in the notes section.
That is because CRM holds all file attachments that are not for activities as notes.
You will have to do the following:
1) Create or retrieve the entity that you want the file to be attached to.
2) Read the file steam and encode it to a Base64 string.
3) Create the UploadFromBase64DataAnnotationRequest object and execute it.

Using the code

I will first explain the steps and code required to attach a file to any entity.
Let's assume you want to attach a PDF file to a client's contact record in CRM programmatically.
First you will need the contactID guid of the target contact record.
Secondly, you will need to create a new note.
CRM attaches all files that are NOT activity attachments as notes.
To create a note use the following block of code:

CrmService myCRMService = new CrmService();
myCRMService.Credentials = System.Net.CredentialCache.DefaultCredentials;

//#1
annotation note = new annotation();
note.subject = "my attachment";

//#2
note.ownerid = new Owner();
note.ownerid.type = "systemuser";
note.ownerid.Value = new Guid("000-000-0000"); <-- don't use this value
note.ownerid.name = "Administrator";

//#3
note.objectid = new Lookup();
note.objectid.name = "contact name";
note.objectid.type = EntityName.contact.ToString(); <-- use the correct type
note.objectid.Value = new Guid("000-000-0000-000"); <-- use the contact's guid

//#4
note.objecttypecode = new EntityNameReference();
note.objecttypecode.Value = EntityName.contact.ToString();

Guid newNoteID = new Guid();
newNoteID = myCRMService.Create(t);

#1
A note entity is known in CRM as an Annotation.

#2
Like all other user owned entities in CRM you need to assign an owner, in this example i used the system administrator.

#3
The annotation class has what is known as the objectid it is a property of type Lookup().
You need to tell CRM where you want the note attached, by providing the name, ID and type of the target entity.

In this case I used a contact as my target.
The note.objectid.type is very important it tells CRM the type of the target entity.
In order to help reduce human error the CRM API developers included a reference Enum class called EntityName, this enum is updated if you create new custom entities and publish them.

#4
The objecttypecode property is a required field, without it CRM will throw an exception.
You have to give the target entity's type, i recommend using the EntityName Enum to prevent any errors.
This may seem a bit redundant, I am not sure why we have to set the type again, if anyone knows please let me know.

Finally you will need to create the note to get the a Guid.
The next step is to actually upload a file:

//#1
FileInfo pointer = new FileInfo( "c:/test.pdf");
FileStream fileStream = pointer.OpenRead();
byte[] byteData = new byte[(int)fileStream.Length];
fileStream.Read(byteData, 0, (int)fileStream.Length);
string encodedData = System.Convert.ToBase64String(byteData);

//#2
fileStream.Flush();
fileStream.Close();

//#3
UploadFromBase64DataAnnotationRequest upload = new UploadFromBase64DataAnnotationRequest();
upload.AnnotationId = newNoteID;
upload.FileName = "test.pdf";

//#4
upload.MimeType = "application/pdf";

//#5
upload.Base64Data = encodedData;

//#6
UploadFromBase64DataAnnotationResponse uploaded = (UploadFromBase64DataAnnotationResponse)myCRMService.Execute(upload);

#1
You will need two classes the FileInfo class and the FileStream class.

The FileInfo, because you can easily get the size of the file and the extension which will be needed at point #4 to identify the mimetype.
The FileStream class because you will need to convert the file to a base64 string (not really sure why).

#2
I don't need to state this but make sure to close the filestream.

#3
If you want to upload a file to an annotation you will need to use the UploadFromBase64DataAnnotationRequest class, this class is used ONLY for annotation attachments.

The class will accept the ID of the annotation, you will also have to provide the filename.

#4
The sticky point here is specifying the MimeType, this took me forever because you have to be very specific otherwise CRM will not deliver the file correctly when you want to upload it.
I used the following MimeType list to search for the correct value based on the file extension.

#5
In this step you provide the upload request with the encoded Base64 string.

#6
Finally you upload the file by calling the crmService.Execute method and passing it the upload request.

If you have a large file to upload and many other tasks to perform afterwards, i suggest uploading the file Asynchroniously by calling the crmService.ExecuteAsync method.

I hope you this article has helped you.

Labels: , , , , ,

Monday, October 22, 2007

How to Retrieve system user list efficiently

Do you need a list of all users in CRM 3.0?

if so do the following:

Here is the Fetch XML that I used to pull all the system users

<fetch mapping='logical'><entity name='systemuser'><all-attributes/></entity></fetch>

To retrieve the list simply use this C# code:

string result = service.Fetch(fetchxml);


XmlDocument xdoc = new XmlDocument(); xdoc.LoadXml(result);

To loop through the list and fill a drop down list for example:

XmlNodeList userList = xdoc.SelectNodes("resultset/result");


foreach(XmlNode n in userList)
{
ListItem itm = new ListItem();
itm.Text = n["fullname"].InnerText;
itm.Value = n["systemuserid"].InnerText;
ddlUserList.Items.Add(itm);
}

It is that simple.

Oshri Cohen

Labels: , , , , , ,

Friday, October 19, 2007

How to debug a Callout efficiently.

Debugging a Callout must be the single most tedious task that i have ever had to do while developing for CRM 3.0.

The need to continuously restart IIS and the associated services is very time consuming.

I used a combination of a webservice i wrote and the callout to ease the development and debugging.
Essentially, on every event the callout executes a webservice method that accepts the same parameters.

1) Create a webservice, ideally it shoudl be hosted as a virtual directory within the crm 3.0 website.

2) Override a method, in the callout file, for the purpose of this tutorial I will use the PostCreate method.
3) The PostCreate method accepts the following parameters:
Microsoft.Crm.Callout.CalloutUserContext userContext, Microsoft.Crm.Callout.CalloutEntityContext entityContext, string postImageEntityXml
It is a real pain to transfer complex classes such as the userContext via webservice, so i decided to serialize each class using the xmlSerializer and transfer every parameter to my webserice as a string.

Before we continue we will need the webservice.

The webservice i as simple as it gets, you will need a WebMethod named PostCreate that accepts the same number of paremeters, just on this end every parameter is a string.

4) Deserialize the string objects back into their original type (I will show you the code later.)

5) Compile the projects and add the webservice to the callout.

6) In each Callout method you will need to call the webserivce before the base method is called.

I will post a generic callout class shortly so that all you need to do is add it to your project and start working.

*** If you are worried about performance, don't be after the first callt he webservice get's cached and every subsequent call has little performance penalty.

Here is the link to the Complete project:

Simboliq Callout project with webservice

Oshri Cohen

Labels: , , , , , ,

Tuesday, October 16, 2007

How to hide CRM 3.0 menu items

A requirement for one of my clients required me to hide the" save and new" from an entity.

This is very simple to do, essentially the CRM developers named these buttons with a static name that can be accessed within any javascript event.

here is the reference to the most common buttons:


Save = "_MBcrmFormSave"
Save and Close = "_MBcrmFormSaveAndClose"
Save and New = "_MBcrmFormSubmitCrmForm59truetruefalse"
Print = "_MBcrmFormPrint"
Save as completed = "_MBSaveAsCompleted"


to hide the button simply call the following function:

document.getElementById('[name of the button]').Style.Display='None' or 'Inline';

Setting the Style.Display to 'None' will hide the button.

Setting the Style.Display to 'Inline'will Show the button.

*** Please make sure that the menu item actually exists, use this statement for the test:

var mnuItem = document.getElementById('[name of the button]');
if( mnuItem != null)

{
//hide or show here
}

***

I would appreciate if anyone knows the ID name for other buttons to post it as a comment, if you do so i will append it to the post and of course credit you for it.


Have a great Day

Oshri Cohen

Labels: , , , , ,

Monday, October 15, 2007

How to Include JS files in CRM 3.0

I encountered a problem while customizing a CRM 3.0 implementation for a client.
The problem was that the project required a lot of JavaScript and if you have been working with CRM 3.0 you already knows how frustrating it is to develop using JS within crm.

In order to remedy that problem, I thought of using a custom .JS file and include it in the forms that I was customizing. Microsoft clearly states modifying the CRM source pages is not supported or recommended and may not survive an upgrade or a patch.

Here is my solution that may help us all in future CRM 3.0 client side development.

1.
Create your .JS file.

2.
Place it either in a new Virtual Directory within the CRM website or as a separate website all together.

3.
Select the entity that you want to customize and enable the OnLoad event.

4.
Put the following code within the event:

var script = document.createElement("script");
script.type = "text/javascript";
script.src= "[the location of your script either relative or absolute]";
socument.getElementsByTagName("head")[0].appendChild(script);



5.
At this point the .JS file has been appended to the page and has been subsequently loaded by the browser.

***Please note that any modifications to the JS file will require you to clear the browsers Temporary files, otherwise it will not load the latest version. ***

*** this has not been tested for CRM within outlook. ***

Have a great day.

Oshri Cohen

Labels: , , , , ,