Thursday, April 25, 2013

Device Detection in ASP.NET MVC 4

In this example I am going to demonstrate the MVC 4 Device detection libraries. ASP.NET MVC 4 introduces Display Modes to which allows you to write device specific application. This is a newly introduced feature of ASP.NET MVC 4. This selects a view depending on the browser making the request, which means you can target specific devices and present the user device specific customized pages.

By default Display Mode Provider implements the mobile view;

clip_image002

You just need to provide the mobile specific pages to your application by just creating a pagename.mobile.cshtml and you are done.

clip_image004

I have done few customizations in my Web and Mobile pages just to display the requesting browser UserAgent and to identify if this is a page for web or mobile. Now let’s run the application and see output.

I have used Mozilla User Agent Switcher Add-on here to switch between the browsers. clip_image002[4]

Page displayed by default User Agent (Desktop)

clip_image004[4]

Page displayed when I select the iPhone as User Agent.

You can notice here that this all achieved without even writing a piece of code till now, with just the addition of .mobile.cshtml page I am able to achieve this.

But in real world things are not so straight forward, we have plenty of devices emerging every year and we have to provide device specific pages for each one of those devices with very little or negligible development effort and of course with minimal code changes.

With display mode provider we cannot just add a device specific pages instead we can also specify Browser specific page, OS Specific pages and even Vendor specific pages yes that is true. The only thing what we need to know is the identifiable user agent which provides us enough information about the Browser, Device, OS, etc information.

Now let me add some customization to my code for iPhone. In this case I am going to display a page which is specific to only iPhone. First of all let’s create a page for iPhone as Index.iphone.cshtml

clip_image006

You can and add some code which will tell us on runtime that this page is only meant for iPhone. Now add few lines of code in Global.ascx.cs file. Here in these codes I have added entry into the DisplayModeProvider for iPhone, this will tell the application that whenever you see the iPhone text in the browser UserAgent, just take the user to Index.iphone.cshtml page.

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iphone")
{
     ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf("iphone", StringComparison.OrdinalIgnoreCase) >= 0)
});

Now let’s see the output at runtime,

clip_image002[8]

Here in the screen above you can see that I have got an additional entry for iphone in DisplayModeProvider collection, and the output screen I got from the code is as below:

clip_image004[9]

Similarly you can add as many entries as you wish, and for e.g if you want to have same page for multiple devices like for iPad and Tablet I want to display Index.tablet.cshtml page, then you can write the codes as:

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("tablet")
{
    ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf("tablet", StringComparison.OrdinalIgnoreCase) >= 0
    || context.GetOverriddenUserAgent().IndexOf("ipad", StringComparison.OrdinalIgnoreCase) >= 0)
});

So you can see DisplayModeProvider is fully customizable and extensible based on your requirement. But the catch is you need to have a complete list of Browser User Agents to handle virtually all the possible devices programmatically from your code. For I came across one very interesting link from Tech Brij Blog, including the code provided in this blog will complete this example and you can have a complete working example of the device detection using MVC 4

private static string GetDeviceType(string ua)
{
    string ret = "";
    // Check if user agent is a smart TV - http://goo.gl/FocDk
    if (Regex.IsMatch(ua, @"GoogleTV|SmartTV|Internet.TV|NetCast|NETTV|AppleTV|boxee|Kylo|Roku|DLNADOC|CE\-HTML", RegexOptions.IgnoreCase))
    {
        ret = "tv";
    }
    // Check if user agent is a TV Based Gaming Console
    else if (Regex.IsMatch(ua, "Xbox|PLAYSTATION.3|Wii", RegexOptions.IgnoreCase))
    {
        ret = "tv";
    }
    // Check if user agent is a Tablet
    else if ((Regex.IsMatch(ua, "iP(a|ro)d", RegexOptions.IgnoreCase) || (Regex.IsMatch(ua, "tablet", RegexOptions.IgnoreCase)) && (!Regex.IsMatch(ua, "RX-34", RegexOptions.IgnoreCase)) || (Regex.IsMatch(ua, "FOLIO", RegexOptions.IgnoreCase))))
    {
        ret = "tablet";
    }
    // Check if user agent is an Android Tablet
    else if ((Regex.IsMatch(ua, "Linux", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "Android", RegexOptions.IgnoreCase)) && (!Regex.IsMatch(ua, "Fennec|mobi|HTC.Magic|HTCX06HT|Nexus.One|SC-02B|fone.945", RegexOptions.IgnoreCase)))
    {
        ret = "tablet";
    }
    // Check if user agent is a Kindle or Kindle Fire
    else if ((Regex.IsMatch(ua, "Kindle", RegexOptions.IgnoreCase)) || (Regex.IsMatch(ua, "Mac.OS", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "Silk", RegexOptions.IgnoreCase)))
    {
        ret = "tablet";
    }
    // Check if user agent is a pre Android 3.0 Tablet
    else if ((Regex.IsMatch(ua, @"GT-P10|SC-01C|SHW-M180S|SGH-T849|SCH-I800|SHW-M180L|SPH-P100|SGH-I987|zt180|HTC(.Flyer|\\_Flyer)|Sprint.ATP51|ViewPad7|pandigital(sprnova|nova)|Ideos.S7|Dell.Streak.7|Advent.Vega|A101IT|A70BHT|MID7015|Next2|nook", RegexOptions.IgnoreCase)) || (Regex.IsMatch(ua, "MB511", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "RUTEM", RegexOptions.IgnoreCase)))
    {
        ret = "tablet";
    }
    // Check if user agent is unique Mobile User Agent
    else if ((Regex.IsMatch(ua, "BOLT|Fennec|Iris|Maemo|Minimo|Mobi|mowser|NetFront|Novarra|Prism|RX-34|Skyfire|Tear|XV6875|XV6975|Google.Wireless.Transcoder", RegexOptions.IgnoreCase)))
    {
        ret = "mobile";
    }
    // Check if user agent is an odd Opera User Agent - http://goo.gl/nK90K
    else if ((Regex.IsMatch(ua, "Opera", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "Windows.NT.5", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, @"HTC|Xda|Mini|Vario|SAMSUNG\-GT\-i8000|SAMSUNG\-SGH\-i9", RegexOptions.IgnoreCase)))
    {
        ret = "mobile";
    }
    // Check if user agent is Windows Desktop
    else if ((Regex.IsMatch(ua, "Windows.(NT|XP|ME|9)")) && (!Regex.IsMatch(ua, "Phone", RegexOptions.IgnoreCase)) || (Regex.IsMatch(ua, "Win(9|.9|NT)", RegexOptions.IgnoreCase)))
    {
        ret = "desktop";
    }
    // Check if agent is Mac Desktop
    else if ((Regex.IsMatch(ua, "Macintosh|PowerPC", RegexOptions.IgnoreCase)) && (!Regex.IsMatch(ua, "Silk", RegexOptions.IgnoreCase)))
    {
        ret = "desktop";
    }
    // Check if user agent is a Linux Desktop
    else if ((Regex.IsMatch(ua, "Linux", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "X11", RegexOptions.IgnoreCase)))
    {
        ret = "desktop";
    }
    // Check if user agent is a Solaris, SunOS, BSD Desktop
    else if ((Regex.IsMatch(ua, "Solaris|SunOS|BSD", RegexOptions.IgnoreCase)))
    {
        ret = "desktop";
    }
    // Check if user agent is a Desktop BOT/Crawler/Spider
    else if ((Regex.IsMatch(ua, "Bot|Crawler|Spider|Yahoo|ia_archiver|Covario-IDS|findlinks|DataparkSearch|larbin|Mediapartners-Google|NG-Search|Snappy|Teoma|Jeeves|TinEye", RegexOptions.IgnoreCase)) && (!Regex.IsMatch(ua, "Mobile", RegexOptions.IgnoreCase)))
    {
        ret = "desktop";
    }
    // Otherwise assume it is a Mobile Device
    else
    {
        ret = "mobile";
    }
        return ret;
}

The code above covers a very exhaustive list of devices/browsers/OS which are available. This is a reengineered version of Categorizr(A device detection script) provided with premium version of 51degrees.mobi

The function provided above uses RegEx library to find the matching content in the Browser User Agent string and based on the match it returns the device type as string. The Code below calls the GetDevice function to get the device type string to the ContextCondition as either tablet, mobile or tv.

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("tablet")
{
   ContextCondition = (context => GetDeviceType(context.GetOverriddenUserAgent()) == "tablet")
});
DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("tv")
{
   ContextCondition = (context => GetDeviceType(context.GetOverriddenUserAgent()) == "tv")
});
DisplayModeProvider.Instance.Modes.Insert(2, new DefaultDisplayMode("mobile")
{
   ContextCondition = (context => GetDeviceType(context.GetOverriddenUserAgent()) == "mobile")
}); 

This is just a small sample of the Display Mode Provided packaged with ASP.NET MVC 4, other links which might help you in understanding the overall concepts are:

References:

http://www.campusmvp.net/displaymodes-in-mvc-4/

http://msdn.microsoft.com/en-us/library/system.web.webpages.displaymodeprovider(v=vs.111).aspx

Sample project with the implementation can be downloaded from here:

https://docs.google.com/file/d/0BzIjFd_Ps-MSQThrTklrbXZOQlU/edit?usp=sharing

Sunday, February 3, 2013

Image Upload and Preview Control in ASP.NET Ajax

Image upload and previewing is a very basic requirement usually when we come across user registration page or add and edit an institution, etc. In this post I am providing here the sample which does the same using ASP.NET Ajax.

I had given here just a simple example just to upload and preview the image, I am not going to save the image in the Database or Load from the Database, but of course you can extend this control based on your requirements.

This is how the final screen will look once you complete the code:

image

You will need Ajax Control Toolkit for AsyncFileUpload control, this controls helps to perform the asynchronous operation without page refresh. Best way to get this through NuGet package manager in you project.

Now lets see the ASP.NET Page and the Code Behind for that.

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="UploadImage.aspx.cs" Inherits="UploadImage" %>
   2:  
   3: <%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>
   4:  
   5:  
   6: <!DOCTYPE html>
   7:  
   8: <html xmlns="http://www.w3.org/1999/xhtml">
   9: <head runat="server">
  10:     <title></title>
  11:  
  12:     <script language="javascript" type="text/javascript">
   1:  
   2:         function getRandomNumber() {
   3:             var randomnumber = Math.random(10000);
   4:             return randomnumber;
   5:         }
   6:  
   7:         function OnClientAsyncFileUploadComplete(sender, args) {
   8:             var handlerPage = '<%= Page.ResolveClientUrl("~/ImageRequestHandler.ashx")%>';
   9:             var queryString = '?randomno=' + getRandomNumber() + '&action=preview';
  10:             var src = handlerPage + queryString;
  11:             var clientId = '<%=previewImage.ClientID %>';
  12:         document.getElementById(clientId).setAttribute("src", src);
  13:     }
  14:     
</script>
  13:  
  14: </head>
  15: <body>
  16:     <form id="form1" runat="server">
  17:         <ajaxToolkit:ToolkitScriptManager ID="toolKitScriptManager" runat="server">
  18:         </ajaxToolkit:ToolkitScriptManager>
  19:         <div>
  20:             <asp:Panel ID="pFileUpload" runat="server">
  21:                 <label>
  22:                     Image Source:</label>
  23:                 <ajaxToolkit:AsyncFileUpload ID="asyncFileUpload" runat="server" 
  24:                     OnClientUploadComplete="OnClientAsyncFileUploadComplete"
  25:                     OnUploadedComplete="OnAsyncFileUploadComplete" Width="374px" />
  26:                 <br />
  27:                 <asp:Image runat="server" ID="previewImage" Width="150px" BorderStyle="Double" BorderColor="Green" />
  28:             </asp:Panel>
  29:         </div>
  30:     </form>
  31: </body>
  32: </html>

The code above is very simple and self explanatory, still let me quickly give you a walkthrough. In the Script section of this page I am writing an function which gets called by the AsyncFileUpload control once the file upload to server is completed. Basically in Server side we are just saving the image temporarily in Session which is referenced in Handler section which I am going to cover very soon. In the same function we are calling the ImageHandler which gets the image from the session as mentioned above and write the image to response stream. Once the operation is over this function maps the source to the image control in the client side.

While calling the Image Handler sometimes in certain cases the browser caches the response stream due to which we may face the problem in refreshing the images. To overcome this you can see In the same script I have used a function to generate a random number which basically used to get the unique URL to call the ImageHandler and overcome the response caching issue.

I have almost explained the entire functionality still lets look into the ImagePreviewHandler and Code Behind part of the application.

   1: protected void OnAsyncFileUploadComplete(object sender, AsyncFileUploadEventArgs e)
   2: {
   3:     if (asyncFileUpload.FileBytes != null)
   4:     {
   5:         Context.Session.Add("SessionImage", asyncFileUpload.FileBytes);
   6:     }
   7: }
   1: <%@ WebHandler Language="C#" Class="ImageRequestHandler" %>
   2: using System;
   3: using System.Web;
   4:  
   5: public class ImageRequestHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
   6: {
   7:     public void ProcessRequest(HttpContext context)
   8:     {
   9:         context.Response.Clear();
  10:  
  11:         if (context.Request.QueryString.Count != 0)
  12:         {
  13:             byte[] imageData = context.Session["SessionImage"] as byte[];
  14:  
  15:             if (imageData != null)
  16:             {
  17:                 context.Response.OutputStream.Write(imageData, 0, imageData.Length);
  18:                 context.Response.ContentType = "image/JPEG";
  19:             }
  20:         }
  21:     }
  22:  
  23:     public bool IsReusable {
  24:         get {
  25:             return false;
  26:         }
  27:     }
  28:  
  29: }

In the first snippet I have given the code behind of the ASP.NET page, which simple saves the image byte array into the Session in the  OnUploadedComplete  event of AsyncFileUpload control. This image byte array is used later in Image Handler to process further.

And finally in the second snippet I am showing the ASP.NET Generic Handler, in ProcessRequest I am fetching the image byte array from the Session and writing the image to the response of the page. A part from implementing IHttpHandler, I am also deriving the System.Web.SessionState.IRequiresSessionState, which provides me the ability to read and write to the session. This is very important in our case since we are using session variable to read the images in the Image Request Handler.

And that all we need. You can download the code from here.

Link: https://docs.google.com/file/d/0BzIjFd_Ps-MSaUhwdzl6NXRQMVE/edit?usp=sharing