Hello MSDN - Uploading Contact Images in CRM 2011

I am not a blogger, however I found that jotting things down at a central location helps you refer back.....I am going to use this blog to note things down which annoyed me at work.......and no I wont be pointing fingers at anyone here...... :) And yes the code I post here are proof of concept snippets....please dont use them without the due deligence

One of my clients requirements included adding a photograp of a contact in a Contact Entity. Surprisingly this is not supported out of the box in CRM 2011. So I decided to write a small Silverlight component to proof out my design approach.

Files can be attached to entities in CRM and the contact entity definitely allows you to attach files to it. I thought this should be a good way to get started.

1. Retrieve/Store the file in the contacts' attahcement

2. Create a silverlight component which will allow the user to upload/retrieve the image.

3. Customize the contact form to add the silverlight web resource

4. And we are done......

 

The silverlight part was a breeze when it came to retrieving and displaying a contact's picture which I manually loaded. The code for that is as follows:

1. Create your silverlight control and add reference to the csdl file.

2. Ad an image control to your silverlight control and use this piece of code to fetch the image

  Annotation annotation = new Annotation(); 
 var data = page.GetProperty("data") as ScriptObject; 
 var entity = data.GetProperty("entity") as ScriptObject; 
 
 if(Guid.TryParse(entity.Invoke("getId",null).ToString(),out recordId)) 
 { 
 DataServiceQuery<Annotation> query = (DataServiceQuery<Annotation>)(context.AnnotationSet.Where<Annotation> 
 (note => note.ObjectId != null && note.ObjectTypeCode=="contact" && note.FileName=="contactpicture.jpg" )); 
 query.BeginExecute(NoteFetchCallback, query); 
 
 }

And the call back looks like:

  private void NoteFetchCallback(IAsyncResult result) 
 { 
 var query = result.AsyncState as DataServiceQuery<Annotation>; 
 
 if (query != null) 
 { 
 var results = query.EndExecute(result).ToList(); 
 if (results.Count > 0) 
 { 
 var contactPicture = results[0]; 
 imageArray = Convert.FromBase64String(contactPicture.DocumentBody); 
 MemoryStream stream = new MemoryStream(imageArray); 
 BitmapImage image = new BitmapImage(); 
 image.SetSource(stream); 
 imageContact.Source = image; 
 } 
 

3. I moved on with the upload functionality as well:

  OpenFileDialog dialog = new OpenFileDialog() { Multiselect = false, Filter = ".jpg|" }; 
 dialog.ShowDialog(); 
 var fileInfo = dialog.File; 
 imageArray = new byte[fileInfo.Length]; 
 var fileStream = fileInfo.OpenRead(); 
 fileStream.Read(imageArray, 0, (int)fileInfo.Length - 1); 
 
 
 Annotation annotation = new Annotation() { MimeType = @"image/pjpeg", FileName = "contactpicture.jpg", DocumentBody = Convert.ToBase64String(imageArray), ObjectId = new EntityReference() { Id=recordId, LogicalName="contact" } }; 
 context.AddToAnnotationSet(annotation); 
 context.BeginSaveChanges(null, null); 
 

However I noticed that the images I would upload this way would fail in the fetch and display operation in Silverlight. and it would give me a weird error Error HRESULT E_FAIL
has been returned from a call to a COM component. at the line highlighted in red :

 private void NoteFetchCallback(IAsyncResult result) 
 { 
 var query = result.AsyncState as DataServiceQuery<Annotation>; 
 
 if (query != null) 
 { 
 var results = query.EndExecute(result).ToList(); 
 if (results.Count > 0) 
 { 
 var contactPicture = results[0]; 
 imageArray = Convert.FromBase64String(contactPicture.DocumentBody); 
 MemoryStream stream = new MemoryStream(imageArray); 
 BitmapImage image = new BitmapImage(); 
 image.SetSource(stream); 
 imageContact.Source = image; 
 } 
  

After digging around a bit I found that there was a silverlight component which does the same thing uploaded in Codeplex: https://crmattachmentimage.codeplex.com/

I looked around at the source code and Carlton had commented that the array into which you load the image should have two more extra bytes of memmory which will be used by the BitmapImage.SetSource method to add temination characters.

I added the fix and things started working again...... This is the upload snippet which works:

 
  OpenFileDialog dialog = new OpenFileDialog() { Multiselect = false, Filter = ".jpg|" }; 
 dialog.ShowDialog(); 
 var fileInfo = dialog.File; 
 imageArray = new byte[fileInfo.Length+2]; 
 var fileStream = fileInfo.OpenRead(); 
 fileStream.Read(imageArray, 0, (int)fileInfo.Length - 1); 
 
 
 Annotation annotation = new Annotation() { MimeType = @"image/pjpeg", FileName = "contactpicture.jpg", DocumentBody = Convert.ToBase64String(imageArray), ObjectId = new EntityReference() { Id=recordId, LogicalName="contact" } }; 
 context.AddToAnnotationSet(annotation); 
 context.BeginSaveChanges(null, null);
 

It never occured to me to check codeplex before I started writing this component - Lesson Learnt! and yes the image thingy too.....

 I did take a look at the code in codeplex and I felt it could do with some changes. Particularly around

1. Using a Provider model so that we can switch the data store easily. For example if I need to upload the image to a sharepoint site or maybe even a folder in the server I have to pretty much rewrite the whole control.

2. I also noticed that the solution adds another hidden field to maintain the link between a contact and the Annotation entity which contains the file. I dont think this is a good approach. I strongly feel that the image should be stored along with the entities attachment. The approach of using attachments will work only for entities to which attachments and its security permissions are allowed. However this problem can be easily addressed if there is a Provider model in picture.

Man...writing blogs are tiring...... :(