March 2012

Volume 27 Number 03

ASP.NET MVC 3 - Develop Hybrid Native and Mobile Web Apps

By Shane Church | March 2012

You want to build a mobile application but you’re bewildered by the array of available devices and APIs to learn. Which mobile platform should you choose? The Apple iOS (iPhone and iPad) uses Objective C, Google Android uses Java and Windows Phone uses Silverlight, yet each one of these options has a distinct API and a distinct market. Choosing to focus on one particular technology stack could leave 50 percent of the market—or more—unable to use your application. If you choose to try to support all of these platforms, you have at least three distinct codebases to maintain, significantly increasing your development and maintenance costs.

There is another option: You could build a mobile Web application, because it can be viewed on any of these devices. But this approach also has some hurdles. The biggest obstacle for developing an entire business application using HTML and JavaScript is the lack of access to many native device hardware features such as the camera, GPS or accelerometer.

Clearly the mobile market is only going to grow, so how do you support all of these device options while providing the best user experience possible? In this article, I’ll show you how to build a mobile application that takes advantage of the best of both worlds by wrapping a mobile Web application with a native application shell.

The Hybrid Application Concept

The basic concept of a hybrid application is to wrap a mobile-optimized Web application in a device-specific native application shell. The native application shell hosts a Web browser control that’s configured to launch the specific mobile application URL when the shell application launches. Other UI elements can be provided in the native application shell as needed, but only the Web browser control is required. The native Web browser control then listens to the URLs being requested as the user navigates the site. When the user requests a specific URL that requires native functionality, the Web browser control interrupts the navigation event and instead invokes the native functionality. As the user completes the native process, the application navigates the Web browser control back into the Web site flow in the appropriate location.

To illustrate how this is done, I’ll walk through my experience building an application with my EffectiveUI colleagues for a client. Built for a mobile field worker who processes a number of maintenance work orders for municipal assets such as signs, benches and fire hydrants, the application takes advantage of browser-supported features to get the user’s current location, and native hardware access to take pictures of assets and upload them to the server. Figure 1 shows the main menu of the completed application.

The Completed Application Main Menu
Figure 1 The Completed Application Main Menu

Building the Web Application

When building this mobile application, I followed a number of suggestions from Steve Sanderson’s article, “Build a Better Mobile Browsing Experience” (msdn.microsoft.com/magazine/hh288079) in the July 2011 issue of MSDN Magazine. In addition to the recommendations in this article, I learned a few things along the way:

  • Optimize UI Elements for Touch Most mobile users are using touch-based interaction. Touch interaction is inherently less precise than mouse-based interaction on the desktop. All interactive elements such as buttons and menu items need to be proportionally larger in the mobile interface than they are in the desktop experience.
  • Optimize Your Mobile Views for Bandwidth Most mobile devices are resource constrained, especially when considering bandwidth. Don’t force your user to download a number of large images in order to use your site. Users on mobile devices expect responsive interfaces and will quickly abandon your site or application if it doesn’t perform to their expectations.
  • Use HTML5 and CSS3 Because mobile Web browsers don’t have the long legacy of desktop browsers, they’re much quicker to adopt the emerging HTML5 and CSS3 standards than their desktop counterparts. In many cases, mobile browsers are far ahead of desktop browsers in implementing these features. Take advantage of this in your mobile views to lighten the payload that the mobile browser needs to download and let the browser do more of the stylistic rendering.

One of the technical requirements from my client when creating this application was to demonstrate sharing controller logic between desktop and mobile views of the site. This requirement is common to many customers and should be favored by developers as well, as it greatly streamlines the process of building an application that supports both desktop and mobile users. ASP.NET MVC 3 provides the ability to switch views based on request elements, such as the requesting browser, while still sharing controllers and models between multiple views. It also allows the developer to finely control the experience on the site for each of the different platforms, meaning the developer only needs to build the business logic once and then tailor the presentation for each platform. Figure 2 shows a utility function to decide which view to present.

Figure 2 Utility for Deciding Which View to Present

private ActionResult SelectView(string viewName, object model,
  string outputType = "html")
{
  if (outputType.ToLower() == "json")
  {
    return Json(model, JsonRequestBehavior.AllowGet);
  }
  else
  {
    #if MOBILE
      return View(viewName + "Mobile", model);
    #else
      if (Request.Browser.IsMobileDevice)
      {
        return View(viewName + "Mobile", model);
      }
      else
      {
        return View(viewName, model);
      }
    #endif
  }
}

The utility function allowed me to meet the requirement to share the same code for making the decision about which view to present to the user based on the incoming request. If the incoming request is a script that’s requesting JSON instead of HTML, the controller can also respond appropriately using the same business logic and model classes by simply setting the outputType parameter appropriately. I also use a precompiler statement looking for the MOBILE conditional compilation symbol to enable debugging the mobile views using my desktop browsers. This was enabled using an additional build target, “Mobile,” in the ASP.NET MVC 3 project, and it allowed me to skip the check for Request.Browser.IsMobileDevice in the desktop debugging configuration, greatly improving my efficiency in building and debugging the mobile version of the application.

While building the application I also used separate master pages for the mobile and desktop versions of the site. The desktop and mobile versions of the master page are significantly different to address the disparities in presentation between the platforms. The mobile master page includes my mobile-specific CSS files and a simplified layout structure to ease the development of individual views using the jQuery Mobile Framework markup and syntax.

All of the modern mobile platforms allow access to a device’s GPS radio to determine the user’s current location through the HTML5 World Wide Web Consortium (W3C) geolocation APIs. The use of the geolocation APIs was discussed in detail in Brandon Satrom’s article, “Integrating Geolocation into Web Applications” (msdn.microsoft.com/magazine/hh580735) in the December 2011 issue. Although that article discusses using an HTML5 JavaScript polyfill to support location on browsers that don’t natively support the HTML5 geolocation APIs, most current mobile browsers support the HTML5 geolocation APIs natively, so the polyfill technique most likely isn’t necessary. You should consider the devices and browsers that you’re targeting while you’re evaluating the necessity of using the polyfill technique. One thing to note specifically for Android is that you’ll need to make sure the enableHighAccuracy parameter in the geolocation call is set to “true” in order to successfully access the GPS functionality in the Android emulator, as shown in Figure 3.

Figure 3 Geolocation Using HTML5

if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
$("#map_canvas").GoogleMap("addMarker", {
id: "device_location",
latitude: position.coords.latitude,
longitude: position.coords.longitude,
description: "Current Location",
iconImageUrl: '@Url.Content("~/Content/images/my-location-dot.png")',
callback: function () {
$("#map_canvas").GoogleMap("panToLocation", {
latitude: position.coords.latitude,
longitude: position.coords.longitude
});
}
});
}, function (error) {
}, {
enableHighAccuracy: true
});
}

Using jQuery Mobile

The jQuery Mobile Framework is “a unified HTML5-based user interface system for all popular mobile device platforms,” according to the project’s Web site (jquerymobile.com). It contains a number of touch-optimized widgets and greatly eases the task of building mobile Web applications that look and feel like native mobile applications. jQuery Mobile can be added to your ASP.NET MVC 3 project through NuGet using the NuGet Package Manager interface or from the Package Manager Console by running the command “Install-Package jquery.mobile.” This adds the jQuery Mobile JavaScript and CSS files to your project. You’ll still need to add references to the jQuery Mobile JavaScript and CSS files to your mobile master page, as shown in Figure 4.

Figure 4 The Mobile Master Page

<!DOCTYPE html>
<html>
<head>
  <title>@ViewBag.Title</title>
  <meta name="viewport" content="width=device-width,
    initial-scale=1.0, user-scalable=no, height=device-height" />
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
  <link href="@Url.Content("~/Content/eui_assets/css/reset.css")"
    rel="stylesheet" type="text/css" />
  <link href="@Url.Content("~/Content/jquery.mobile-1.0.min.css")"
    rel="stylesheet" type="text/css" />
  <link href="@Url.Content("~/Content/mobile.css")"
    rel="stylesheet" type="text/css" />
  <script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")"
    type="text/javascript"></script>
  @RenderSection("PreJQueryMobileInit", false)
  <script src="@Url.Content("~/Scripts/jquery.mobile-1.0.min.js")" 
    type="text/javascript"></script>
  <script type="text/javascript">
    $('a[data-ajax="false"]').live('click', function (event) {
      if (!$(this).hasClass("camera-link")) {
        $.mobile.showPageLoadingMsg();
      }
    });
  </script>
  @RenderSection("Head", false)
</head>
<body class="eui_body" id="@ViewBag.BodyID">
  @RenderBody()
</body>
</html>

jQuery Mobile does make some significant modifications to the patterns with which any jQuery developer is familiar. To quote the jQuery Mobile documentation:

The first thing you learn in jQuery is to call code inside the $(document).ready() function so everything will execute as soon as the DOM is loaded. However, in jQuery Mobile, [AJAX] is used to load the contents of each page into the DOM as you navigate, and the DOM ready handler only executes for the first page. To execute code whenever a new page is loaded and created, you can bind to the pageinit event.

I used the pageinit event inside of all the pages in the application that contained the Google Maps control in order to initialize the map when the page is transitioned into view via AJAX.

An additional feature of the mobile master page is the @RenderSection(“PreJQueryMobileInit”, false) line, shown in Figure 4, which allows you to execute scripts before jQuery Mobile is initialized on the page. In the example application I used this feature to bind to the mobileinit event so I could set up a custom callback when the jQuery Mobile listview filter behavior is complete. I also added two lines of code to the jQuery Mobile library to add a filterCompleteCallback method to the listview prototype in order to get a notification when the built-in list filtering was complete. This allowed me to refresh the matched items on the map to match the filtered list. The callback function needed to be added to the jQuery Mobile listview before jQuery Mobile was applied to any of the markup; that code is executed in the mobileinit event handler shown in Figure 5.

Figure 5 Binding to the mobileinit Event

if(navigator.geolocation) {   
  navigator.geolocation.getCurrentPosition(function (position) {
    $("#map_canvas").GoogleMap("addMarker", {
      id: "device_location",
      latitude: position.coords.latitude,
      longitude: position.coords.longitude,
      description: "Current Location",
      iconImageUrl: '@Url.Content("~/Content/images/my-location-dot.png")',
      callback: function () {
        $("#map_canvas").GoogleMap("panToLocation", {
          latitude: position.coords.latitude,
          longitude: position.coords.longitude
        });
      }
    });
  }, function (error) {
  }, {
    enableHighAccuracy: true
  });
}
@section PreJQueryMobileInit {
  <script type="text/javascript">
    $(document).bind("mobileinit", function () {
      $.mobile.listview.prototype.options.filterCompleteCallback = function () {
        // Note that filtercompletecallback is a custom
        // addition to jQuery Mobile and would need to be updated
        // in future revisions.
        // See comments in jquery.mobile-1.0.js with SSC 09/12/2011
        var ids = [];
        var $visibleItems = $("#js-work-orders-list").find(
          "li:not(.ui-screen-hidden");
        for (var i = 0; i < $visibleItems.length; i++) {
          var item = $($visibleItems[i]).find("p");
          ids.push(item.text().substr(item.text().indexOf('#') + 1));
        }
        ids.push("device_location");
        $("#map_canvas").GoogleMap("hideAllMarkersExceptList", ids);
      }
    });
  </script>
}

jQuery Mobile takes significant advantage of new features in HTML5 such as the header and footer tags and the data-* attributes. The data-role attributes determine the behavior that should be attached to a given element. For example, in the MapMobile.cshtml view in Figure 6, I have two divs defined with the data-role=“page” attribute.

&gt;Figure 6 MapMobile.cshtml Markup

<div data-role="page" id="map_page" data-fullscreen="true"
  data-url="map_page" data-theme="a">
  <header data-role="header" data-position="fixed">
    <a href="@Url.Action("Index", "Home")" data-icon="home"
      data-direction="reverse">Home</a>
    <h1>Map Demo</h1>
    <a href="#" data-icon="back" id="js-exit-street-view"
      class="ui-btn-hidden">Exit Street View</a>
  </header>
  <div data-role="content" class="main-content">
    <div id="map_canvas" style="width:100%;height:100%"></div>
  </div>
  <footer data-role="footer" data-position="fixed"
    data-id="fixed-nav" data-theme="a">
    <nav data-role="navbar">
      <ul>
        <li><a href="#map_page" class="ui-btn-active
          ui-state-persist">Map</a></li>
        <li><a href="#items_page">Work Orders</a></li>
      </ul>
    </nav>
  </footer>
</div>
<div data-role="page" id="items_page" data-url="items_page" data-theme="a">
  <header data-role="header" data-position="fixed">
    <a href="@Url.Action("Index", "Home")" data-icon="home"
      data-direction="reverse">Home</a>
    <h1>Map Demo</h1>
  </header>
  <div data-role="content" class="main-content">
    <div class="list-container">
      <ul data-role="listview" id="js-work-orders-list" data-filter="true">
      @foreach (MapItem item in Model.Items)
  {
      <li class="work-order-id-@item.ID">
        <a href="@Url.Action("Details", "Home", new { id = item.ID })"
          data-ajax="false">
          <h3>@item.Issue</h3>
          <p>Work Order #@item.ID</p>
        </a>
      </li>
    }
      </ul>
    </div>
  </div>
  <footer data-role="footer" data-position="fixed"
    data-id="fixed-nav" data-theme="a">
    <nav data-role="navbar">
      <ul>
        <li><a href="#map_page" data-direction="reverse">Map</a></li>
        <li><a href="#items_page" class="ui-btn-active
          ui-state-persist">Work Orders</a></li>
      </ul>
    </nav>
  </footer>
</div>

This attribute tells jQuery Mobile that each of these divs should be treated as a separate page on the mobile device and to transition between them using AJAX without a page navigation occurring in the browser. This produces the effect shown in the screenshots in Figure 7. The jQuery Mobile Web site provides recommendations and more details on how to use each of the data-* attributes in the jQuery Mobile context.

Transitioning Between Pages Via AJAX
Figure 7 Transitioning Between Pages Via AJAX

Building the Native Mobile Application Shells

The basic pattern in developing each of the native application shells is designing an application that simply contains a full-screen Web browser control. Within this control I capture the event that’s fired when the user requests a new page and compare the requested URL against a list of known URLs that should invoke native functionality. This is where the “magic” of a Web-based application in a native application shell happens. For the purposes of this application, the URL that I matched within the site is “Home/Image” to invoke the native camera functionality. When the user is on the Work Order details page, he’ll see a camera icon in the upper-right corner of the screen as illustrated in Figure 8. Clicking on this icon invokes the native camera.

Invoking Native Camera Functionality
Figure 8 Invoking Native Camera Functionality

Windows Phone

Windows Phone uses Silverlight for all of the native functionality. In some ways, this makes Windows Phone the easiest platform to support for the mobile Web developer who is familiar with ASP.NET. The basic XAML layout for the native application shell is simple, as shown here:

<Canvas x:Name="LayoutRoot" Background="Black" Margin="0">
  <phone:WebBrowser HorizontalAlignment="Left" Name="webBrowser1" 
    Navigating="webBrowser1_Navigating" IsScriptEnabled="True"
    IsGeolocationEnabled="True"
    Background="Black" Height="720" Width="480" />
</Canvas>

The key items to note here are that IsScriptEnabled is set to true—because, by default, the Web browser control in Windows Phone doesn’t enable script—and that I’m handling the Navigating event.

In the MainPage.xaml.cs, shown in Figure 9, I handle the webBrowser1_Navigating event. If the navigation URL matches the URL that I’m looking for, I pick out the ID of the work order that I’m working with and invoke the native CameraCaptureTask while cancelling the Web browser navigation. After the user takes the picture with the camera, the photoCaptureOr­SelectionCompleted method is invoked. Here, I upload the picture to the Web server using the same HTTP form POST action that the Web site would be using if I submitted a form that contained a file upload input button. When the photo upload completes, upload_FormUploadCompleted is invoked, returning the user to the Web application flow.

Figure 9 Windows Phone MainPage.xaml.cs

public partial class MainPage : PhoneApplicationPage
{
  CameraCaptureTask cameraCaptureTask;
  BitmapImage bmp;
  string id = "";
  string baseURL = "http://...";
  // Constructor
  public MainPage()
  {
    InitializeComponent();
    cameraCaptureTask = new CameraCaptureTask();
    cameraCaptureTask.Completed +=
      new EventHandler<PhotoResult>(photoCaptureOrSelectionCompleted);
  }
  private void webBrowser1_Navigating(object sender, NavigatingEventArgs e)
  {
    // Catch Navigation and launch local camera
    if (e.Uri.AbsoluteUri.ToLower().Contains("home/image"))
    {
      id = e.Uri.AbsoluteUri.Substring(e.Uri.AbsoluteUri.LastIndexOf("/") + 1);
      cameraCaptureTask.Show();
      e.Cancel = true;
    }
  }
  void photoCaptureOrSelectionCompleted(object sender, PhotoResult e)
  {
    if (e.TaskResult == TaskResult.OK)
    {
      byte[] data = new byte[e.ChosenPhoto.Length];
      e.ChosenPhoto.Read(data, 0, data.Length);
      e.ChosenPhoto.Close();
      Guid fileId = Guid.NewGuid();
      Dictionary<string, object> postParameters = new Dictionary<string, object>();
      postParameters.Add("photo", new FormUpload.FileParameter(
        data, fileId.ToString() +
        ".jpg", "image/jpeg"));
      FormUpload upload =
        new FormUpload(baseURL + "Home/UploadPicture/" + id, postParameters);
      upload.FormUploadCompleted +=
        new FormUpload.FormUploadCompletedHandler(upload_FormUploadCompleted);
      upload.BeginMultipartFormDataPost();
    }
  }
  void upload_FormUploadCompleted(object source)
  {
    webBrowser1.Navigate(webBrowser1.Source);
  }
  private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
  {
    webBrowser1.Navigate(new Uri(baseURL));
  }
}

Windows Phone has some different behaviors when interacting with the Web-based version of the Google Maps or Bing Maps controls when compared with Android or iOS. Because of the way the Internet Explorer 9 mobile browser captures touch, swipe and pinch gestures without passing them through to the JavaScript engine, the Web-based maps can’t zoom or pan with gestures and must use the zoom or pan controls provided by the map. Given this limitation, a future enhancement to this project would be to invoke the native Bing Maps control on Windows Phone where interactive map functionality is required and then return to the Web application on screens that didn’t require interactive map functionality.

Android

The Java code for Android was written by my colleague, Sean Christmann, and is similar to the code for Windows Phone. The following layout XML defines a full-screen layout for the Android WebView control:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <WebView android:id="@+id/webView"
      android:layout_width="match_parent" 
      android:layout_height="match_parent"></WebView>
</LinearLayout>

Within EffectiveUIActivity.java, shown in Figure 10, the override for onCreate sets up the WebViewClient to override the onLoadResource and shouldOverrideUrlLoading methods of the WebView control to search for the same matching string as used in Windows Phone and, if found, creates the camera activity and cancels the navigation. The code also overrides onGeolocationPermissionsShowPrompt to suppress the prompt to the user that would occur each time the application is run to allow permission for the WebView control to access the GPS location. After the camera activity is executed, the onActivityResult function posts the picture to the Web server using the same method as the earlier Windows Phone example and then returns the user to the Web application flow.

Figure 10 Android EffectiveUIActivity.java

public class EffectiveUIActivity extends Activity {
  /** Called when the activity is first created. */
  WebView webView;
  String cameraId;
  static String baseURL = "http://...";
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    webView = (WebView)findViewById(R.id.webView);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.getSettings().setGeolocationEnabled(true);
    webView.setVerticalScrollbarOverlay(true);
    webView.loadUrl(baseURL);
    final EffectiveUIActivity activity = this;
    webView.setWebViewClient(new WebViewClient(){
      @Override
      public void onLoadResource(WebView view, String url) {
        super.onLoadResource(view, url);
        if(url.contains("Home/Image")){
          activity.createCamera();
        }
      }
      @Override
      public boolean shouldOverrideUrlLoading(WebView view, String url){
        String match = "Home/Image/";
        int i = url.indexOf(match);
        if(i>0){
          cameraId = url.substring(i+match.length());
          activity.createCamera();
          return true;
        }
        return false;
      }
    });
    webView.setWebChromeClient(new WebChromeClient(){
      @Override
      public void onGeolocationPermissionsShowPrompt(
        String origin, GeolocationPermissions.Callback callback) {
        super.onGeolocationPermissionsShowPrompt(origin, callback);
        callback.invoke(origin, true, false);
      }
    });       
  }
  public void createCamera(){
    Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
    startActivityForResult(intent, 2000);
  }
  @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (resultCode == Activity.RESULT_OK && requestCode == 2000) {
        Bitmap thumbnail = (Bitmap) data.getExtras().get("data");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        thumbnail.compress(CompressFormat.JPEG, 75, bos);
        byte[] imagebytes = bos.toByteArray();
        ByteArrayBody bab = new ByteArrayBody(imagebytes, "image/jpeg",
          UUID.nameUUIDFromBytes(imagebytes).toString()+".jpg");
        HttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost(baseURL+"Home/UploadPicture");
        MultipartEntity reqEntity =
          new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
        reqEntity.addPart("photo", bab);
        try {
          reqEntity.addPart("ID", new StringBody(cameraId, "text/plain",
            Charset.forName( "UTF-8" )));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
          }
          post.setEntity(reqEntity);
          try {
            HttpResponse response = client.execute(post);
            BufferedReader reader = new BufferedReader(
              new InputStreamReader(
              response.getEntity().getContent(), "UTF-8"));
            String sResponse;
            StringBuilder s = new StringBuilder();
            while ((sResponse = reader.readLine()) != null) {
              s = s.append(sResponse);
            }
          } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            }
            webView.loadUrl(webView.getUrl());
          }
        }
}

iOS

The Objective-C code for iOS was also written by my colleague, Sean Christmann, and is also similar to that used for Windows Phone and Android. Within WebCameraViewController.m shown in Figure 11, the UIWebView control executes the shouldStartLoadWithRequest method to do the pattern matching on the requested URL. If the URL string matches, the code returns “NO” to cancel the navigation and invokes the native UIImagePickerController. This allows the user to pick an image from the photo library or take a new picture with the onboard camera. After selecting the picture, the code then posts the picture back to the Web server using the ASIFormDataRequest library before returning the UIWebView back to the normal application flow.

Figure 11 iOS Code

- (void) choosefromCamera {
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil];
    if ([UIImagePickerController
      isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
      picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    }else{
      picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    }
    [self presentModalViewController:picker animated:YES];
}
- (void)imagePickerController:(UIImagePickerController *)picker   
    didFinishPickingMediaWithInfo:(NSDictionary *)info {
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    NSData *jpg = UIImageJPEGRepresentation(image, 0.3);
    [picker dismissModalViewControllerAnimated:YES];
    [picker release];
    NSString *url =
      [NSString stringWithFormat:@"%@:7511/WorkOrders/UploadPicture", baseURL];
    ASIFormDataRequest *request =
      [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
    [request addData:jpg withFileName:[
      NSString stringWithFormat:@"%@.jpg", [self GetUUID]]
      andContentType:@"image/jpeg" forKey:@"photo"];
    [request addPostValue:cameraId forKey:@"ID"];
    [request setDelegate:self];
    [request setDidFinishSelector:@selector(imageUploaded:)];
    [request setDidFailSelector:@selector(imageUploaded:)];
    [request startSynchronous];
    [webView reload];
}
-(void) imageUploaded:(ASIFormDataRequest *)request {
    NSString *response = [request responseString];
    NSLog(@"%@",response);
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(
  NSURLRequest *)request
    navigationType:(UIWebViewNavigationType)navigationType {
    NSURL *url = [request URL];
    NSString *str = [url absoluteString];
    NSRange range = [str rangeOfString:@"WorkOrders/Image/"];
    if (range.location != NSNotFound) {
      cameraId = [str substringFromIndex:range.location+17];
      [cameraId retain];
      NSLog(@"%@", cameraId);
      [self choosefromCamera];       return NO;
    }else{
      return YES;
    }
}

Graceful Degradation of the Mobile Experience

So what happens if the user of the mobile Web site isn’t using the native application shell to access the camera? In this scenario, it’s important to have a graceful degradation of the user experience. Graceful degradation is the concept of building your application so that it continues to function correctly even if it’s viewed with less-than-optimal software. This doesn’t mean that every feature works in exactly the same way or that it even looks similar to the intended experience, but it aims to ensure that all of the user’s fundamental goals can still be accomplished even if the user isn’t getting the best experience.

To enable graceful degradation in this application, I built an ASP.NET MVC 3 controller and view for the image capture URL, “Home/Image,” that’s being captured by the native application shells to provide a simple file upload form as shown in Figure 12. This form allows users who aren’t using the enhanced mobile shells to accomplish the same task of adding a picture to a work order, although they aren’t getting the integrated experience. The form posts to the same controller action used by the native application shells, encouraging code reuse among all of the different platforms and views.

A Simple File Upload Form for Graceful Degradation
Figure 12 A Simple File Upload Form for Graceful Degradation

Significant Cost Advantages

The hybrid application approach can provide significant cost advantages over unique native applications, both in the short and long term. Tools such as jQuery Mobile narrow the usability differences, which may lead to significant business advantages where native device access isn’t required.

With mobile devices proliferating like wildfire, you have a few choices when looking to build a mobile application:

  • Build a Native Application for Each Platform You Want to Support This has the distinct advantage of providing the best user experience and performance for each platform while allowing access to all the native features of the device and the marketing power of the app stores. The disadvantage, however, is that it might be significantly more expensive to build and maintain because it will require a separate codebase for each platform you wish to support. In addition, each new version of the application requires that the application be resubmitted to the app stores.
  • Build a Mobile Web Application This has the advantage of being the simplest and cheapest option to develop, launch and update for all of the platforms, but the user experience can be compromised by the lack of access to native hardware features. Lack of access to the app stores can also compromise adoption of your application, pushing all of the marketing of your application to you.
  • Build a Hybrid Native and Mobile Web Application This is the approach I discussed, which provides a solid compromise between the high costs of developing a unique native application for each platform and the lack of native hardware access for a mobile Web application. This option also provides access to the app stores, increasing the reach of your application.

It’s important to note that none of these approaches is inherently better than the others—all of them have their own strengths and weaknesses. A comprehensive cost/benefit analysis of each of the options will help determine which path is the right one for your users and your business. When making this decision, it’s important to consider the user experience, up-front development costs and ongoing maintenance costs as well as more subtle factors such as marketing and user adoption.

For many business application scenarios, I advocate for inclusion of a mobile Web or hybrid application, as the additional effort of building unique native applications for each mobile platform could outweigh the business benefit. The business scenarios need to be carefully reviewed within the confines of a mobile Web or hybrid application deployment.

Mobile applications are here to stay and are increasingly important as computing shifts away from the traditional desktop experience to an array of mobile experiences. As you look to build applications in the mobile space, remember that compromise isn’t always a dirty word and that it can result in the best of both worlds.


Shane Church is a technical lead for EffectiveUI in Denver, Colo. He has been developing in the Microsoft .NET Framework with a focus on ASP.NET and Microsoft mobile technologies since 2002. His blog is located at s-church.net. You can learn more about EffectiveUI at effectiveui.com.

Thanks to the following technical expert for reviewing this article: Dr. James McCaffrey