Photo Album using Silverlight, Caliburn Micro and Mongo DB
In this post I am going to explain a small Photo Album I created using Silverlight. With this app I also joined the band wagon of NoSQL movement. I used MongoDB as my backend for this application. This application is still in the development mode. I will add more functionality to this application like Public comments, admin module, etc. Currently this app shows the functionality of admin module, like upload photo, delete photo, etc. The application uses MVVM pattern and as usual I used Caliburn Micro here. I used several framework in the app as shown below
- Silverlight
- Caliburn Micro
- Reactive Extension
- MongoDB
- WCF
In this application I used Reactive Extension (Rx) to perform Async service calls. I felt the Rx is a very powerful tool to perform Async calls. The one good advantage of Rx is you can place Async calls in Thread Pool and push it to dispatcher once the call completed. All these can be done in one or two lines of code. This way the UI will always responsive.
This is my first experiment with Mongo db, I can say it’s very easy to use and fast. There are several Mongo drivers for .NET available in the market and is free. I used the one from here. The advantage of Mongodb is, I don’t want to write code to map my POCO to tables. You can directly persist your entity to Mongodb. You can find a lot of article about Mongo db in internet
I don’t think I need to explain Caliburn Micro in detail here. I wrote a post that gives a brief introduction to Caliburn Micro.
Running MongoDB
Below are the steps to run MongoDB
- Download the latest version of Mongo db from Mongodb.org
- Extract the zip file to C:\Mongodb
- Create a Folder called data in C drive
- Create a subfolder called db inside data
- Goto command prompt and change directory to C:\Mongodb
- Type Mongod in the command prompt and hit enter
Now the Mongodb will listen to the default port and our app can make request. There are different ways of hosting Mongodb, you can get more details from Mongodb site.
The App
Below is the screen shot of my application.
This page will display the thumbnails of uploaded images. We can see the bigger image by clicking on the thumbnail as show below.
The preview will display in a Windowless popup. We can delete the photo by clicking the Trash icon bottom right side of the image. We can upload images by clicking on the upload icon at the top right of the window. The upload page will get displayed as shown below.
When the user upload the image, the application will create a thumbnail version of the image and store along with the original image in the server. This helps to transfer and load the thumbnail page.
Let’s see the entity used in this application.
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.IO; using System.Drawing; using System.Drawing.Imaging; namespace PicturePortfolio.Domain { [DataContract] public class Photo { private const int THUMBWIDTH = 100; private const int THUMBHEIGHT = 100; public Photo() { } public Photo(PhotoPersistanceObject persistanceObject) { this.Name = persistanceObject.Name; this.Description = persistanceObject.Description; this.ID = persistanceObject.ID; this.PublicComments = persistanceObject.PublicComments; this.GUID = persistanceObject.GUID; this.LoadThumbNail(); } [DataMember] public int ID { get; set; } [DataMember] public string GUID { get; set; } [DataMember] public byte[] ImageFile { get; set; } [DataMember] public byte[] ImageThumbNail { get; set; } [DataMember] public string Description { get; set; } [DataMember] public string Name { get; set; } [DataMember] public List<PublicComment> PublicComments { get; set; } public PhotoPersistanceObject GetDataForPersistance() { PhotoPersistanceObject photoPersistance = new PhotoPersistanceObject(); photoPersistance.ID = this.ID; photoPersistance.Name = this.Name; photoPersistance.ImagePath = "/test/" + this.Name; photoPersistance.Description = this.Description; photoPersistance.PublicComments = this.PublicComments; if (string.IsNullOrEmpty(this.GUID) == true) photoPersistance.GUID = Guid.NewGuid().ToString(); else photoPersistance.GUID = this.GUID; return photoPersistance; } public void SaveImage() { if (this.ImageFile == null) throw new ArgumentNullException(); try { byte[] buffer = this.ImageFile.ToArray(); MemoryStream memStream = new MemoryStream(); memStream.Write(buffer, 0, buffer.Length); this.SaveOrginalImage(memStream); this.SaveImageThumbNail(memStream); } catch (Exception ex) { throw; } } public void LoadImage() { string fullFileName = this.GetFileFullPath(); this.ImageFile = this.GetImageAsByteArray(fullFileName); } public void LoadThumbNail() { string fullFileName = this.GetFilePath() + this.ThumbNailImageName; this.ImageThumbNail = this.GetImageAsByteArray(fullFileName); } #region Private Methods private void SaveOrginalImage(MemoryStream memStream) { System.Drawing.Image imgToSave = System.Drawing.Image.FromStream(memStream); imgToSave.Save(this.GetFileFullPath(), ImageFormat.Jpeg); } private void SaveImageThumbNail(MemoryStream memStream) { Image img = Image.FromStream(memStream); Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); Image imageToSave = img.GetThumbnailImage (THUMBWIDTH, THUMBHEIGHT, myCallback, IntPtr.Zero); imageToSave.Save(this.GetFilePath() + ThumbNailImageName, System.Drawing.Imaging.ImageFormat.Jpeg); } private static bool ThumbnailCallback() { return false; } private byte[] GetImageAsByteArray(string fullFileName) { FileInfo fil = new FileInfo(fullFileName); if (fil.Exists == true) { FileStream fileStream = new FileStream(fullFileName, FileMode.Open, FileAccess.Read); byte[] buffer = new byte[fileStream.Length]; fileStream.Read(buffer, 0, (int)fileStream.Length); fileStream.Close(); return buffer; } else return null; } private string GetFilePath() { string filePath = AppDomain.CurrentDomain.BaseDirectory + "Images\\"; return filePath; } private string GetFileFullPath() { string fullPath = this.GetFilePath() + this.Name; return fullPath; } private string ThumbNailImageName { get { string fileName = this.Name.Substring(0, Name.LastIndexOf(".")); return fileName + "_thumb.jpg"; } } #endregion } }
The entity class is very simple. I may need to give a brief explanation about PhotoPersistanceObject used in the entity class. The PhotoPersistanceObject is used for persisting meta data of the image like Name, description etc. You might think that why cant we save Photo object directly, yes I also thought the same way but their is some issue with Mongodb driver I used here. It’s not supporting byte array neither binary, It allows to store binary in Mongodb but throws error while retrieving. So I used a trimmed down version of Photo entity to persist the data.
Async Calls using Thread Pool
As I told before one of the cool feature of Reactive Extension is it’s advantage of making async calls in thread pool. Even if we place async calls in separate thread in Silverlight still it will block the UI to load if we do it in page load. Because the our thread will work in UI. We can achieve the placing calls in thread pool using Rx. See the below code.
private void GetAllThumbnails() { var func=Observable.FromEvent<GetAllPhotosCompletedEventArgs>(_service,"GetAllPhotosCompleted") .ObserveOn(Scheduler.ThreadPool);
_service.GetAllPhotosAsync();
func.ObserveOn(Scheduler.Dispatcher)
.Select(result => result.EventArgs.Result)
.Subscribe(s => this.ParseThumbnails(s.ToList()));
}
As you can see the service calls are doing in ThreadPool. Once the call is done we will change the ObserveOn to Dispatcher, other wise cross thread expection will throw. I call the GetAllThumbnails in the constructor of a ViewModel. As we are doing the call in Threadpool my UI will loaded without any delay, once the call is completed the thumbnails will start display in the listbox.
Mongodb Repository
I wrote a Generic repository similar to the one I wrote for EF. You can find the generic repository for EF in one of my post. Below is the Generic repository class for Mongodb.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using MongoDB; using MongoDB.Linq; using MongoDB.Connections; namespace PicturePortfolio.Persistance { public class GenericRepository:IRepository { public GenericRepository() { } Mongo _mongoDb=null; public IMongoCollection<TEntity> GetQuery<TEntity>() where TEntity : class { if (_mongoDb == null) { _mongoDb = new Mongo(); _mongoDb.Connect(); } return DataBase.GetCollection<TEntity>(typeof(TEntity).Name + "s"); } IMongoDatabase db = null; private IMongoDatabase DataBase { get { if (db == null) { db = _mongoDb.GetDatabase("Protfolio"); } return db; } } public IList<TEntity> GetAll<TEntity>() where TEntity : class { try { return GetQuery<TEntity>().Linq().ToList<TEntity>(); } catch (Exception ex) { throw; } } public IList<TEntity> Find<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class { return this.GetQuery<TEntity>().Linq<TEntity>().Where(criteria).ToList<TEntity>(); } public TEntity Single<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class { return this.GetQuery<TEntity>().Linq<TEntity>().Single(criteria); } public TEntity First<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class { return this.GetQuery<TEntity>().Linq<TEntity>().First(criteria); } public void Add<TEntity>(TEntity entity) where TEntity : class { this.GetQuery<TEntity>().Save(entity); } public void Delete<TEntity>(TEntity entity) where TEntity : class { this.GetQuery<TEntity>().Remove(entity); } public void Delete<TEntity>(IEnumerable<TEntity> entites) where TEntity : class { throw new NotImplementedException(); } public void Update<TEntity>(TEntity entity, TEntity old) where TEntity : class { this.GetQuery<TEntity>().Update(entity, old, UpdateFlags.None, true); } } }
This app is in very primitive stage. you may need to do lot of refactoring to make use in real scenario. I will work on this app and upload it to codeplex some times later.
Download the current source here.
[…] This post was mentioned on Twitter by Rob Eisenberg, Joe Feser and Scott Criswell, Sony Arouje. Sony Arouje said: Photo Album using Silverlight, Caliburn Micro and Mongo DB: http://wp.me/p12zd9-1P […]
Tweets that mention Photo Album using Silverlight, Caliburn Micro and Mongo DB « Sony Arouje Blog -- Topsy.com
November 16, 2010 at 1:55 am
Good day I was fortunate to discover your subject in wordpress
your topic is superb
I get much in your topic really thank your very much
btw the theme of you site is really exceptional
where can find it
bet365
November 24, 2010 at 5:26 pm
Good to hear that my post helped u nd thanks 4 the feedback.
I am using The Journalist v1.9 as my theme
sonyarouje
November 24, 2010 at 7:35 pm
Find and pick some good things from you and it aids me to solve a problem, thanks.
– Henry
RobertsRachatf
December 14, 2010 at 5:34 pm
I’m a .net newbie.. how do I get the code to run?
SupaTed
January 24, 2011 at 3:16 pm
You can download the latest source code from codeplex.
1. go to http://photostream.codeplex.com/SourceControl/list/changesets
2. Click on the Download link at the right hand side of the page to get the latest code.
sonyarouje
January 24, 2011 at 3:48 pm
Hi Sony,
I got that far, but when I run the app I get some exceptions while trying to retrieve the categories. Do I need to create a database with mongo, or run mongo manually? I already installed the Fx components (I didnt have them yet).
Regards, Ted
SupaTed
January 24, 2011 at 5:32 pm
Ted,
You need to download Mongo db and need to run it manually. The beginning of this blog I have a small section called Running Mongo Db. Pls follow the same to start Mongo db.
sonyarouje
January 25, 2011 at 7:56 am
Is it possible, that your application is’nt compatible with the last stable Reactive Extension v1.0.10425 I have downloaded from msdn?
jack-geronimo
May 25, 2011 at 10:31 pm
Hi Jack,
I haven’t compiled with latest version of Rx. If possible I will check it out and let u know. It will be good if you can let me know the issues you come across.
Sony Arouje
May 25, 2011 at 11:31 pm
I think it is a problem as described in this articl: http://goo.gl/n0uXr
jack-geronimo
May 26, 2011 at 9:36 pm
Thanks Jack for the info, I will check it out.
Sony Arouje
May 27, 2011 at 10:50 am
Hi Sony,
Can you tell me which version of Rx did you have used, because I think, I can learn a lot about Rx, by watching (compiling/debugging) your project.
Thanks in advance
Hans
jack-geronimo
June 4, 2011 at 7:48 pm
I used the first version of Async CTP. Am sorry, not remembering the exact version
Sony Arouje
June 5, 2011 at 11:32 pm