Playing with jQuery, Uploadify and LightBox

Recently I started working on a project in ASP.NET and one of the “features” I had to implement was to allow the user to upload photos. As user experience is important, I didn’t want to have a simple page which would post back refreshing the page, or just a “one by one” upload process.

Since the FileUpload control is one of the controls that don’t work inside the Update Panel, I had to look at some other options. As I am still learning jQuery and trying to improve in this area, I decided to look for some upload plugin written in jQuery. After some research on the Internet I managed to find Uploadify which seems like one of the best options out there: simple, good looking and easy to play with.

In one of my previous blog entries I was showing how to upload, store an image to a database and then retrieve it and add a watermark to the image. I will use this article as a starting point for this new application and just modify it in order to make the upload process more user friendly.

First step to use the Uploadify plugin is to import it to our project and in order to use it we need to use a simple input element of type “file”. Once we defined the input element in our page we need to call the uploadify method on it and specify the default values we want. After this small step, the upload page should look like:

HTML
  1. <form id=“form1” runat=“server”>
  2. <div>
  3. <script type=“text/javascript”>
  4. // <![CDATA[
  5. var id = “55”;
  6. var theString = “asdf”;
  7. $(document).ready(function() {
  8. $(‘#fileInput’).uploadify({
  9. ‘uploader’: ‘uploadify/uploadify.swf’,
  10. ‘script’: ‘Upload.ashx’,
  11. ‘scriptData’: { ‘id’: id, ‘foo’: theString },
  12. ‘cancelImg’: ‘uploadify/cancel.png’,
  13. ‘auto’: false,
  14. ‘multi’: true,
  15. ‘fileDesc’: ‘Image Files’,
  16. ‘fileExt’: ‘*.jpg;*.png;*.gif;*.bmp;*.jpeg’,
  17. ‘queueSizeLimit’: 5,
  18. ‘sizeLimit’: 512000,
  19. ‘buttonText’: ‘Choose Images’,
  20. ‘folder’: ‘/uploads’,
  21. ‘onComplete’: function(event, queueID, fileObj, response, data) {
  22. },
  23. ‘onAllComplete’: function(event, queueID, fileObj, response, data) {
  24. }
  25. });
  26. });
  27. // ]]></script>
  28. <input id=“fileInput” name=“fileInput” type=“file” />
  29. <a href=“javascript:$(‘#fileInput’).uploadifyUpload();”>Upload Files</a>
  30. </div>
  31. </form>

I will not go into explaining all the parameters used for setting up Uploadify, I will just highlight 3 parameters that were of interest for me:

  • queueSizeLimit: according to the official documentation it allows you to set the maximum number of files that can exist in the queue at one time.  If the user tries to add more the the maximum limit, the onQueueFull function is triggered.  The multioption should be used in conjunction with this option.
  • sizeLimit: allows you to set the maximum file size allowed per file in bytes.  If the file exceeds the size limit, it will return an error on upload.
  • script: is required and points to the back-end script that will process your files.  The back-end script can be written in any server-side language.

As you can see I set my upload control to allow a maximum of 5 files at one time and the maximum size allowed for one file is 512000 bytes. The Upload.ashx script holds the server code which is responsible for getting the image the user tries to upload, store it in the database and reply back to the client. Since we allow the user to upload an image, I want to allow them to delete the posted image in case they changed their mind. And I want to do it by the simple click of the mouse, no page postbacks… and why not show the uploaded images in an elegant way with LightBox?

Well, let’s try to do that…

We’ll need to modify our upload page just a bit to accommodate an area where the images will be displayed after upload and also add a small piece of code which will display the images in case of successful upload – the code which will display the images will be send as response by the Upload.ashx script. I added one div element at the end of my previous code and gave it an id=”uploaded_photos” and also modified the code in the onComplete event of the uploadify method (changes are in italic font):

HTML
  1. <form id=“form1” runat=“server”>
  2. <div>
  3. <script type=“text/javascript”>
  4. // <![CDATA[
  5. var id = “55”;
  6. var theString = “asdf”;
  7. $(document).ready(function() {
  8. $(‘#fileInput’).uploadify({
  9. ‘uploader’: ‘uploadify/uploadify.swf’,
  10. ‘script’: ‘Upload.ashx’,
  11. ‘scriptData’: { ‘id’: id, ‘foo’: theString },
  12. ‘cancelImg’: ‘uploadify/cancel.png’,
  13. ‘auto’: false,
  14. ‘multi’: true,
  15. ‘fileDesc’: ‘Image Files’,
  16. ‘fileExt’: ‘*.jpg;*.png;*.gif;*.bmp;*.jpeg’,
  17. ‘queueSizeLimit’: 5,
  18. ‘sizeLimit’: 512000,
  19. ‘buttonText’: ‘Choose Images’,
  20. ‘folder’: ‘/uploads’,
  21. ‘onComplete’: function(event, queueID, fileObj, response, data) {
  22. $(‘#uploaded_photos’).append(response);
  23. },
  24. ‘onAllComplete’: function(event, queueID, fileObj, response, data) {
  25. }
  26. });
  27. });
  28. // ]]></script>
  29. <input id=“fileInput” name=“fileInput” type=“file” />
  30. <a href=“javascript:$(‘#fileInput’).uploadifyUpload();”>Upload Files</a>
  31. </div>
  32. </form>
  33. <div id=“uploaded_photos”></div>

Since our upload page is ready we need to start writing some code to get the images, store them and reply to the client in case the upload is successful. To add a new generic handler right-click on the project and then select Add –> New Item. In the dialog window that appears select Generic Handler and name it Upload.ashx. The code for the Upload.ashx looks like this:

C#
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.Services;
  6. using System.Text;
  7. namespace UploadImages
  8. {
  9. /// <summary>
  10. /// Summary description for $codebehindclassname$
  11. /// </summary>
  12. [WebService(Namespace = “http://tempuri.org/”)]
  13. [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  14. public class Upload : IHttpHandler
  15. {
  16. public void ProcessRequest(HttpContext context)
  17. {
  18. try
  19. {
  20. HttpPostedFile file= context.Request.Files[“Filedata”];
  21. byte[] image_data = new byte[file.ContentLength];
  22. file.InputStream.Read(image_data, 0, file.ContentLength);
  23. ImagesDbEntities2 ent = new ImagesDbEntities2();
  24. images img = new images();
  25. img.name = file.FileName;
  26. img.data = image_data;
  27. img.type = file.ContentType;
  28. ent.AddToimages(img);
  29. ent.SaveChanges();
  30. #region update the number of images the user can upload
  31. int imgs = 5; // the number of images by default – in case of error set to 0
  32. // try to limit the allowed images to 5
  33. IEnumerable<images> images = from i in ent.images select i;
  34. if (images != null && images.Count() != 0)
  35. {
  36. imgs = 5 images.Count();
  37. if (imgs < 0) imgs = 0;
  38. }
  39. else
  40. imgs = 5;
  41. #endregion
  42. // create the delete link ID which will be used in the jquery selector
  43. string deleteLinkID = “deleteImage” + img.id;
  44. StringBuilder sb = new StringBuilder();
  45. sb.Append(“<script>”);
  46. sb.Append(“$(\”a[class=gallery]\”).lightBox();”);
  47. sb.Append(“$(‘#fileInput’).uploadifySettings(‘queueSizeLimit’,” + imgs.ToString() + “);”);
  48. sb.Append(“$(\”#” + deleteLinkID + \”).click(function(){ “);
  49. sb.Append(“if(confirm(\”Are you sure you want to delete the image?\”)) { “);
  50. // start creating the script which will delete the image by using AJAX call to a web service
  51. sb.Append(“$.ajax({“);
  52. sb.Append(“type: \”POST\”,”);
  53. // define the service method that will be used to delete the image
  54. sb.Append(“url: \”ImagesService.asmx/DeleteImage\”,”);
  55. sb.Append(“data: \”{id: “ + img.id + “}\”,”);
  56. sb.Append(“contentType: \”application/json; charset=utf-8\”,”);
  57. sb.Append(“dataType: \”json\”,”);
  58. sb.Append(“success: function(msg) {“);
  59. // if the call is successful append the result so we’ll hide the image and update the# of images the user can select
  60. sb.Append(“if(msg.d != \”\”) { $(\”#uploaded_photos\”).append(msg.d); }”);
  61. sb.Append(“}});”);
  62. sb.Append(” } “); // the matching } for the if(confirm)
  63. sb.Append(” })”);
  64. sb.Append(“</script>”);
  65. string response = “<div id=\”divImage” + img.id +
  66. \” style=\”float:left; padding: 5px; text-aling: center;\”><a class=\”gallery\”
  67. href=\”GetImage.aspx?id=” + img.id + \”><img src=\”GetImage.aspx?size=t&id=” +
  68. img.id + \”></a><br /><a id=\” + deleteLinkID +
  69. \” href=\”#\” title=\”Delete\”>Delete</a></div>” + sb.ToString();
  70. context.Response.Write(response);
  71. }
  72. catch(Exception ex)
  73. {
  74. context.Response.Write(“”);
  75. }
  76. }
  77. public bool IsReusable
  78. {
  79. get
  80. {
  81. return false;
  82. }
  83. }
  84. }
  85. }

The last step is to create the ImagesService, by right-clicking on the project and then selecting Add –> New Item. In the dialog window that appears select Web Service and name it ImagesService with the .asmx extension. I had to uncomment one line in the file which was generated: I had to remove the comment for the [System.Web.Script.Services.ScriptService] attribute.

After removing the default method created generated by Visual Studio for me, I created my DeleteImage method which is called by the jQuery AJAX method in the Upload.ashx handler:

C#
  1. [WebMethod]
  2. public string DeleteImage(string id)
  3. {
  4. try
  5. {
  6. long imageID = long.Parse(id);
  7. ImagesDbEntities2 ent = new ImagesDbEntities2();
  8. images image = (from img in ent.images where img.id == imageID select img).FirstOrDefault();
  9. if (image != null)
  10. {
  11. ent.DeleteObject(image);
  12. ent.SaveChanges();
  13. }
  14. #region update the number of images the user can upload
  15. int imgs = 5; // the number of images by default – in case of error set to 0
  16. // try to limit the allowed images to 5
  17. IEnumerable<images> images = from i in ent.images select i;
  18. if (images != null && images.Count() != 0)
  19. {
  20. imgs = 5 images.Count();
  21. if (imgs < 0) imgs = 0;
  22. }
  23. else
  24. imgs = 5;
  25. #endregion
  26. StringBuilder sb = new StringBuilder();
  27. sb.Append(“<script>”);
  28. // update the number of images the user can select
  29. sb.Append(“$(‘#fileInput’).uploadifySettings(‘queueSizeLimit’,” + imgs.ToString() + “);”);
  30. sb.Append(“$(\”#divImage” + id + \”).fadeOut();”); // fadeout the image
  31. sb.Append(“</script>”);
  32. return sb.ToString();
  33. }
  34. catch (Exception ex)
  35. {
  36. return “”;
  37. }
  38. return “”; // by default
  39. }

In the screenshots below you can see the above code in action during the various steps:

default

1. the plugin before any action

images_selected

2. after some files were selected

images_uploaded

3. images inserted in the page after successful upload

lightbox_image

4. see images using the LightBox plugin

confirm_delete

5. clicking on the delete link first brings a confirmation dialog

after_delete

6. after deleting an image

The above pieces of code are working and can be used by someone looking to implement such a functionality in a project, but they can be extended and why not improved. In my real application I am associating the uploaded images to the logged in user, I send some additional info during the upload of images using the scriptData option of Uploadify plugin and of course I improved the looks of the page using some extra CSS.

Hope that the above pieces of code will be helpful for some and I am opened for suggestions or comments related to the code posted here.

UPDATE: I uploaded the code for this sample here.

Enjoy!

Adding a “watermark” to an image in ASP.NET

Currently I am working on a personal project in ASP.NET and one of the tasks is to store images in a MySQL database and later retrieve and show them to the users.

Just for demo purpose, the MySQL table structure is very simple:

db_structure

First step of my task was actually storing the images in the MySQL table and for that I used Entity Framework in order to simplify the data handling.

For adding a new image to the database I created one very simple ASP.NET page:

ASP.NET
  1. <asp:FileUpload ID="fileUploadControl" runat="server" />
  2. <asp:Button ID="UploadButton" runat="server" Text="Upload" OnClick="UploadButton_Click" /><br />
  3. <asp:Label ID="StatusLabel" runat="server" Text="" />

and the code behind for uploading the image and displaying the image details in case of success, or the error in case of any issue is:

C#
  1. protected void UploadButton_Click(object sender, EventArgs e)
  2. {
  3. if (fileUploadControl.HasFile)
  4. {
  5. try
  6. {
  7. ImagesDbEntities2 ent = new ImagesDbEntities2();
  8. images img = new images();
  9. img.name = fileUploadControl.FileName;
  10. img.data = fileUploadControl.FileBytes;
  11. img.type = fileUploadControl.PostedFile.ContentType;
  12. ent.AddToimages(img);
  13. ent.SaveChanges();
  14. StatusLabel.ForeColor = Color.Black;
  15. StringBuilder sb = new StringBuilder();
  16. sb.Append("File Name: " + fileUploadControl.FileName + "<br />");
  17. sb.Append("File Size: " + fileUploadControl.PostedFile.ContentLength + " bytes <br />");
  18. sb.Append("File Type: " + fileUploadControl.PostedFile.ContentType + "");
  19. StatusLabel.Text = sb.ToString();
  20. }
  21. catch (Exception ex)
  22. {
  23. StatusLabel.ForeColor = Color.Red;
  24. StatusLabel.Text = "EXCEPTION: " + ex;
  25. }
  26. }
  27. else
  28. {
  29. StatusLabel.ForeColor = Color.Red;
  30. StatusLabel.Text = "Please select a file to upload!";
  31. }
  32. }

The simple code above allows us to store images in the MySQL table, so next task is to retrieve an image and display it on a web page. For this I created a simple ASP.NET webpage which based on the image ID I pass in the query string it returns the image with that ID from the database. There is only code-behind written for this task and I have 2 implementations:

  1. writing some simple text on top of the image and center it
  2. using a custom image with transparent background and center it on top of the image

Most of the implementation for both ways of adding the watermark to the image is the same and consists in retrieving the table entry based on the ID we pass in the URL query string, creating a MemoryStream object based on the bytes stored on the data field and then create an Image object from the MemoryStream created previously. Also in order to write a text on top of the image or display the watermark image over the initial image we need to create a Graphics object and call the DrawString or DrawImage methods.

So, here is the code to display a text over the image retrieved from the database:

C#
  1. public partial class GetImage : System.Web.UI.Page
  2. {
  3. MemoryStream ms = null;
  4.  
  5. protected void Page_Load(object sender, EventArgs e)
  6. {
  7. if (!IsPostBack)
  8. {
  9. string id = Request.QueryString["id"];
  10. if (!string.IsNullOrEmpty(id))
  11. {
  12. ImagesDbEntities2 ent = new ImagesDbEntities2();
  13. long imgID = Convert.ToInt64(id);
  14. var image = (from img in ent.images where img.id == imgID select img).FirstOrDefault();
  15. if (image != null)
  16. {
  17. MemoryStream ms = new MemoryStream(image.data);
  18. System.Drawing.Image memImage = System.Drawing.Image.FromStream(ms);
  19. Graphics g = Graphics.FromImage(memImage);
  20. Font f = new Font("Arial", 12, FontStyle.Bold);
  21. SolidBrush brush = new SolidBrush(Color.Red);
  22. SizeF stringDimens = g.MeasureString("AndreiC - 2010", f, 300);
  23. if (stringDimens.Width < memImage.Width && stringDimens.Height < memImage.Height)
  24. {
  25. g.DrawString("AndreiC - 2010", f, brush,
  26. (memImage.Width - stringDimens.Width) / 2,
  27. (memImage.Height - stringDimens.Height) / 2);
  28. }
  29.  
  30. Response.ContentType = "image/Jpeg";
  31. memImage.Save(Response.OutputStream, ImageFormat.Jpeg);
  32.  
  33. // Clean-up
  34. ms.Dispose();
  35. memImage.Dispose();
  36. g.Dispose();
  37. }
  38. }
  39. }
  40. }
  41. }

If we want to display the image with ID=1 from the database, all we have to do is add the following HTML code in the page:

HTML
  1. <img src="GetImage.aspx?id=1" />

The result is:

text_test

In case you want to add an image as watermark over your database stored image, you can use the following code:

C#
  1. public partial class GetImage : System.Web.UI.Page
  2. {
  3. MemoryStream ms = null;
  4.  
  5. protected void Page_Load(object sender, EventArgs e)
  6. {
  7. if (!IsPostBack)
  8. {
  9. string id = Request.QueryString["id"];
  10. if (!string.IsNullOrEmpty(id))
  11. {
  12. ImagesDbEntities2 ent = new ImagesDbEntities2();
  13. long imgID = Convert.ToInt64(id);
  14. var image = (from img in ent.images where img.id == imgID select img).FirstOrDefault();
  15. if (image != null)
  16. {
  17. System.Drawing.Image watermark = System.Drawing.Image.FromFile(
  18. Server.MapPath("Images/andreic_watermark.png"));
  19. MemoryStream ms = new MemoryStream(image.data);
  20. System.Drawing.Image memImage = System.Drawing.Image.FromStream(ms);
  21. Graphics g = Graphics.FromImage(memImage);
  22.  
  23. // draw watermark only if image is larger than watermark image
  24. if (watermark.Height < memImage.Height && watermark.Width < memImage.Width)
  25. {
  26. g.DrawImage(watermark,
  27. (memImage.Width - watermark.Width) / 2,
  28. (memImage.Height - watermark.Height) / 2);
  29. }
  30. Response.ContentType = "image/Jpeg";
  31. memImage.Save(Response.OutputStream, ImageFormat.Jpeg);
  32. // Clean-up
  33. ms.Dispose();
  34. watermark.Dispose();
  35. memImage.Dispose();
  36. g.Dispose();
  37. }
  38. }
  39. }
  40. }
  41. }

For this implementation I had to create one additional PNG image with transparent background and I placed it in the project inside a folder named Images.

The HTML code to show the image stays the same and the result is:

img_watermark_test

Hope this helps!

MySQL Membership and Roles Providers for ASP.NET

Recently I started working on a personal project and before diving into some real coding, I decided to go ahead and refresh some of my ASP.NET skills and knowledge as I didn’t do any serious ASP.NET development in more than 1.5 years. As most probably I will host the project on GoDaddy and will start with Economy plan until some upgrades will be necessary I am planning to use MySQL as data repository as they offer 10 MySQL databases of 1GB each- enough space for what I need at the beginning.

Since my project will need to support authentication of users and different roles within the application, I started refreshing my knowledge on the Membership and Roles providers. As my choice for database is MySQL I was afraid I will have to write custom providers to work with membership and roles stored in a MySQL database. Fortunately, I found out there is a much easier way of doing this thanks to the latest MySQL Connector for .NET.

Here are the steps I followed in order to set-up Membership and Roles providers using the MySQL .NET Connector.

  • download and install the latest MySQL .NET Connector. I am using version 6.3.4
  • create a new Web Project in Visual Studio and after add references to MySql.Data and MySql.Web
  • the MySQL .NET Connector will modify your machine.config file after installation and will add new entries in the membership, roleManager and profile providers. You will need to locate the machine.config file under the following location: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG and edit it. You will need to add one extra attribute to the MySql Membership Provider defined in there: autogenerateschema=“true”.  The autogenerateschema=“true” attribute will cause MySQL Connector/NET to silently create, or upgrade, the schema on the database server, to contain the required tables for storing membership information. After this change the MySql Membership Provider in the  machine.config file should look like this:
XML
  1. <add name="MySQLMembershipProvider"
  2. autogenerateschema="true"
  3. connectionStringName="LocalMySqlServer"
  4. ... />
  • one other change made in the machine.config file by the MySQL .NET Connector is in the connectionsStrings section where a new connection for MySql is added:
XML
  1. <add name="LocalMySqlServer" connectionString="" />

We will need to override this connection string in our web.config in order to specify the details on the MySQL database used to create the tables structure that supports the membership and roles providers. Because the membership and roles providers defined in the machine.config file use the already defined MySql connection string, we will need to remove it and then add it again. Our connectionStrings section in web.config file should look like:

XML
  1. <connectionStrings>
  2. <remove name="LocalMySqlServer"/>
  3. <add name="LocalMySqlServer"
  4. connectionString="Datasource=localhost;Database=db_name;uid=user;pwd=password;"
  5. providerName="MySql.Data.MySqlClient"/>
  6. </connectionStrings>
  • although this step can be done from the Website Administration Tool, I prefer to do it directly in the web.config file as it’s very simple. In order to support forms authentication, locate the authentication section in your web.config file and change the mode attribute from Windows to Forms:
XML
  1. <authentication mode="Forms"/>
  • open the Website Administration Tool and switch to the Provider tab and then select the Select a different provider for each feature (advanced) link to set the providers. You should see the MySQLMembershipProvider and MySQLRoleProvider listed. I selected both of them as I want both membership and roles to be stored in my MySQL database. After you close the Administration Tool, your web.config file should be get modified and you’ll notice that under the system.web section the following lines where added:
XML
  1. <roleManager defaultProvider="MySQLRoleProvider"/>
  2. <membership defaultProvider="MySQLMembershipProvider"/>
  • save your solution and build it. If you check your database you should see the tables structure generated:

tables

  • once you finish this step and want to go ahead and use the MySQL Membership and Roles providers in your code you should modify your web.config file once again. I replaced the above mentioned entries for role and membership sections with the entries found in the machine.config file:
XML
  1. <roleManager enabled="true" defaultProvider="MySQLRoleProvider">
  2. <providers>
  3. <clear/>
  4. <add name="MySQLRoleProvider" type="MySql.Web.Security.MySQLRoleProvider"
  5. connectionStringName="LocalMySqlServer" applicationName="MysqlTest" />
  6. </providers>
  7. </roleManager>
  8.  
  9. <membership defaultProvider="MySQLMembershipProvider">
  10. <providers>
  11. <clear/>
  12. <add name="MySQLMembershipProvider" type="MySql.Web.Security.MySQLMembershipProvider"
  13. connectionStringName="LocalMySqlServer" enablePasswordRetrieval="false"
  14. enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="MysqlTest"
  15. requiresUniqueEmail="false" passwordFormat="Clear" maxInvalidPasswordAttempts="5"
  16. minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1"
  17. passwordAttemptWindow="10" passwordStrengthRegularExpression="" />
  18. </providers>
  19. </membership>

Of course you can customize the attributes of the role and membership providers as you wish. I changed the applicationName attribute to be able to differentiate later between application entries in the tables.

  • from this moment you should be able to use Login related controls in your application. Just for testing purposes I added one CreateUserWizard control which works fine without writing  any code, and then I used one Login control for which I implemented the OnAuthenticate event as follows:
C#
  1. protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
  2. {
  3. bool authenticated = false;
  4. authenticated = Membership.ValidateUser(Login1.UserName, Login1.Password);
  5. e.Authenticated = authenticated;
  6.  
  7. if(authenticated)
  8. {
  9. FormsAuthentication.SetAuthCookie(Login1.UserName, true);
  10. Response.Redirect("Secret.aspx");
  11. }
  12. else
  13. {
  14. // do some error handling
  15. }
  16. }

In order to implement this I followed as references:

  1. http://dev.mysql.com/doc/refman/5.1/en/connector-net-tutorials-asp-roles.html
  2. http://blog.nolanbailey.com/2008/03/08/mysql-aspnet-membership-and-role-provider/194

Hope this comes useful for others as well!