ASP.NET MVC 3

开发 Hybrid Native 和移动 Web 应用程序

Shane Church

下载代码示例

您想要生成移动应用程序,但你摸不着的可用的设备和学习的 Api 的数组。 您应该选择哪个移动平台? 苹果 iOS (iPhone 和 iPad) 使用目标 C、 谷歌 Android 使用 Java,Windows Phone 使用 Silverlight,但这些选项中的每个具有不同的 API 和独特的市场。 选择专注于一个特定技术堆栈可以离开 50%的市场份额 — — 或更多 — — 无法使用您的应用程序。 如果您选择尝试支持所有这些平台,您有至少三个不同的代码库以维持,大大增加您的开发和维护成本。

还有另外一个选择:您可以生成移动 Web 应用程序,因为它可以在任何这些设备上查看。 但这种方法也有一些障碍。 为开发使用 HTML 和 JavaScript 的整个业务应用程序的最大障碍是访问的缺乏如摄像头、 GPS 或加速度计的许多本机设备硬件功能。

清楚地增长,那么,如何做支持所有这些设备选项同时提供最佳的用户体验可能只会移动市场? 在本文中,我将展示如何构建一个进行包装,与本机应用程序中壳的移动 Web 应用程序中充分利用了这两个领域的最佳的移动应用程序。

混合应用概念

混合式应用程序的基本概念是特定于设备的本机应用程序中壳中换行优化的移动 Web 应用程序。 本机应用程序中壳主机配置为当外壳应用程序启动时启动特定的移动应用程序的 URL 的 Web 浏览器控件。 其他 UI 元素可以提供本机应用程序 shell 中,根据需要,但需要在 Web 浏览器控件。 当用户导航网站所请求的 Url,然后侦听本机 Web 浏览器控件。 当用户请求需要的本机功能的特定的 URL 时,Web 浏览器控件中断导航事件,并转而调用本机功能。 如用户完成了本机的过程,在应用程序导航回 Web 站点流量中的适当位置的 Web 浏览器控件。

为了说明如何做到这一点,我就会走过我的经验,为客户建立与同事 EffectiveUI 应用程序。 构建流程等症状、 长凳、 消防栓市政资产维护工作订单的数量,移动领域工人,在应用程序利用的浏览器支持的功能,以实现用户的当前的位置和本机硬件资产的图片,并将它们上传到服务器的访问。 图 1 显示已完成的应用程序的主菜单。

The Completed Application Main Menu
图 1 已完成的应用程序主菜单

生成 Web 应用程序

当生成此移动应用程序,我跟在后面的多项建议从史蒂夫 · 桑德森条,"建立更好移动浏览体验"(msdn.microsoft.com/magazine/hh288079) 2011 年 7 月发行的 MSDN 杂志。 在这篇文章中的建议,我学会了一路走来的几件事情:

  • 优化最触摸移动用户在使用基于触摸互动的 UI 元素。 触摸互动是本来就不太精确比基于鼠标的相互作用在桌面上。 所有的交互式元素如按钮和菜单项需要按比例大移动界面比在桌面体验。
  • 为带宽最移动设备的资源约束下,特别是当考虑带宽优化您的移动视图。 不要强迫你的用户才能使用你的网站下载的大图像的数量。 在移动设备上的用户期望响应的接口,将很快就放弃您的网站或应用程序如果它不执行对他们的期望。
  • 使用 HTML5 和 CSS3 因为移动 Web 浏览器没有长遗留下来的桌面浏览器,它们采用新兴的 HTML5 和 CSS3 标准比桌面同行得更快。 在许多情况下,移动浏览器已经遥遥领先桌面浏览器中执行这些功能。 利用这个在您移动视图,以减轻移动浏览器需要下载,让浏览器做更多的文体渲染的负载。

从我的客户创建此应用程序时的技术要求之一是展示网站的桌面和移动视图之间共享控制器逻辑。 这一要求是通用的许多客户,应由开发人员也会受到青睐,因为它极大地简化了构建应用程序的过程,支持桌面和移动用户。 ASP。NET MVC 3 提供了基于请求的元素例如,请求的浏览器,同时仍共享控制器和模型之间的多个视图的视图切换的能力。 它还允许开发人员很好地控制在网站上为每个不同的平台,意味着,开发人员只需要一次建立的业务逻辑,然后调整为每个平台的演示文稿的经验。 图 2 显示决定哪个视图提供的实用程序函数。

图 2 实用程序,用于决定哪个视图的礼物

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
  }
}

实用程序函数允许我去见共享相同的代码决策有关哪个视图向基于传入请求的用户的要求。 如果传入的请求是请求而不是 HTML 的 JSON 的脚本,控制器可以还响应适当地使用相同的业务逻辑和模型类仅仅适当地设置输出类型参数。 我还使用寻找移动的条件编译符号的预编译器语句启用调试使用我的桌面浏览器的移动视图。 启用此使用其他生成的目标,"移动,"在 ASP 中的。NET MVC 3 项目,并且它允许我跳过 Request.Browser.IsMobileDevice 的检查,在桌面调试配置中,大大提高了我在生成和调试应用程序的移动版本的办事效率。

在生成应用程序的同时我还用不同的母版页移动和桌面版本的站点。 母版页的台式机和移动的版本是解决平台之间的演示文稿中的差距明显不同。 移动母版页包含我的移动特定的 CSS 文件和简化的布局结构,以减轻对单个视图使用 jQuery 移动框架标记和语法的发展。

所有现代移动平台允许设备 GPS 无线电来确定用户的当前的位置,通过 HTML5 万维网联合会 (W3C) 地理定位 Api 访问。 在布兰顿 Satrom 的文章,"整合地理定位到 Web 应用程序"中详细讨论了地理 Api 的使用 (msdn.microsoft.com/magazine/hh580735) 在 2011 年 12 月出版。 虽然这篇文章讨论如何使用 HTML5 JavaScript polyfill 支持本身不支持 HTML5 地理定位 Api 的浏览器上的位置,最新的移动浏览器支持 HTML5 地理定位 Api 以本机方式,所以 polyfill 技术很可能没有必要。 您应该考虑的设备和浏览器您要吸引而您是要评估使用 polyfill 技术的必要性。 一件事来注意专门为 Android 是您需要确保在地理定位调用 enableHighAccuracy 参数设置为"true",以成功地访问 GPS 功能在 Android 仿真器中,如中所示图 3

图 3 地理定位使用 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
});
}

使用 jQuery 移动服务

JQuery 移动框架根据项目的 Web 站点是"统一的基于 HTML5 的用户接口系统所有流行的移动设备平台上,"(jquerymobile.com)。 它包含多个触摸优化窗口小部件,并极大地简化了建设移动 Web 应用程序的外观和感觉像移动的本机应用程序的任务。 jQuery 移动可以添加到您的 ASP。NET MVC 3 项目通过使用 NuGet 软件包管理器界面的 NuGet 或从软件包管理器控制台,通过运行命令"安装软件包 jquery.mobile"。这将 jQuery 移动 JavaScript 和 CSS 文件添加到项目中。 您仍然需要对 jQuery 移动 JavaScript 和 CSS 文件的引用添加到您的移动母版页,如图所示,在图 4

图 4 移动母版页

<!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 移动不会进行一些重大修改其中任何一个 jQuery 的开发人员很熟悉的模式。 引用 jQuery 移动文档:

您学习 jquery 的第一件事是调用 $(document).ready() 函数内部的代码,所以一切都将执行,一旦加载 DOM。 然而,jquery 移动、 [AJAX] 用于加载到 DOM 的每个页面的内容,如您导航和 DOM 准备好处理程序仅执行第一页。 每当加载并创建一个新页面,请执行代码,您可以将绑定到 pageinit 事件。

我为了初始化地图,当页面过渡成通过 AJAX 的视图包含谷歌地图控件的应用程序中使用 pageinit 事件,内部的所有页。

移动母版页的附加功能是 @ RenderSection("PreJQueryMobileInit", false) 线,所示图 4,它允许您在页面上执行之前初始化移动的 jQuery 脚本。 在示例应用程序绑定到 mobileinit 事件,所以我可以设置自定义的回调,jQuery 移动 listview 过滤器行为完成后使用此功能。 我也添加到要添加到列表视图原型的 filterCompleteCallback 方法,以内置列表筛选已完成时获得通知的 jQuery 流动图书馆的两行代码。 这让我刷新以匹配筛选列表中的地图上的匹配的项。 要添加到 jQuery 移动 listview jQuery 移动应用的标记 ; 任何前所需的回调函数 中所示的 mobileinit 事件处理程序中执行代码图 5

图 5 绑定到 mobileinit 事件

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 移动利用重大新功能 HTML5 中的页眉和页脚的标记和数据等 * 属性。 数据角色属性确定了应附加到给定元素的行为。 例如,在 MapMobile.cshtml 中的视图图 6,我有两个 div 定义与数据角色 ="页"属性。

图 6 MapMobile.cshtml 标记

<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>

此属性告诉 jQuery 移动这些 div 的每个应视为单独的页上移动设备和它们之间的过渡没有出现在浏览器中的页面导航使用 AJAX。 这将产生中的屏幕快照所示的效果图 7。 JQuery 的移动 Web 站点提供建议和更多的细节如何使用每一数据-* jQuery 移动上下文中的属性。

Transitioning Between Pages Via AJAX
图 7 通过 AJAX 页面之间转换

建设本机移动应用壳

本机应用程序中的贝壳的每个发展中的基本模式设计的应用程序只包含一个全屏幕的 Web 浏览器控件。 此控件内我捕获用户请求一个新的页面时,将触发的事件,比较反对应调用本机功能的已知 Url 列表请求的 URL。 这是应用的发生在本机应用程序中壳中的基于 Web 的"魔术"的地方。 此应用程序而言,我匹配在站点中的 URL 是"首页/图像"来调用本机相机功能。 在用户工作订单详细信息页面上时,他将看到一个照相机图标在屏幕的右上角,如中所示图 8。 单击此图标将调用本机的相机。

Invoking Native Camera Functionality
图 8 调用本机的相机功能

Windows Phone

Windows Phone 使用 Silverlight 所有的本机功能。 在某些方面,这使得 Windows Phone 支持的移动的 Web 开发人员熟悉 ASP 的最简单的平台。NET。 基本的 XAML 布局,本机应用程序命令行程序很简单,如下所示:

<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>

要注意这里是 IsScriptEnabled 设置的关键项目为 true — — 因为,默认情况下,在 Windows Phone 中的 Web 浏览器控件不会启用脚本 — — 和我正处理导航事件。

MainPage.xaml.cs,在所示图 9,我处理 webBrowser1_Navigating 事件。 如果导航 URL 匹配的我正在寻找的 URL,挑的工作令我一起工作,同时取消 Web 浏览器导航调用本机 CameraCaptureTask ID。 之后,用户将图片带相机,photoCaptureOr­SelectionCompleted 方法调用。 在这里,我将图片上载到 Web 服务器使用相同的 HTTP 形式会使用该 Web 站点,如果我提交的载于文件上载输入的按钮的窗体的邮政行动。 在图片上传完成后,upload_FormUploadCompleted 调用时,返回到 Web 应用程序流的用户。

图 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));
  }
}

与基于 Web 的版本的 Android 或 iOS 相比的谷歌地图或必应地图控件进行交互时,Windows Phone 有一些不同的行为。 因为的互联网资源管理器 9 方式移动浏览器没有传递给的 JavaScript 引擎通过捕获触摸、 刷卡和捏的手势,基于 Web 的地图不能缩放或泛用手势和必须使用缩放或平移控件提供的地图。 由于此限制,今后加强对这一项目将调用本机必应地图控件 Windows Phone 交互式地图功能在要求的位置上,然后回到 Web 应用程序,不需要交互式地图功能的屏幕上。

Android 系统

Android 的 Java 代码由我的同事肖恩 · 克里斯,写的类似于 Windows Phone 的代码。 以下 XML 布局定义为 Android 的 web 视图控件的全屏幕布局:

<?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>

在 EffectiveUIActivity.java 内, 所示图 10、 倚仗的重写设置重写 onLoadResource 的 WebViewClient 和 shouldOverrideUrlLoading 方法要在 Windows Phone 中使用,如果搜索相同的匹配字符串的 web 视图控件的发现、 创建与相机活动和取消导航。 该代码还会覆盖 onGeolocationPermissionsShowPrompt 压制的用户将会产生每次允许访问 GPS 位置的 web 视图控件的权限运行应用程序时的提示。 执行相机活动后,onActivityResult 函数张贴到 Web 服务器使用相同的方法,如前面的示例中 Windows Phone 的图片,然后将用户返回到 Web 应用程序流。

图 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

IOS 的目标 C 代码也由我的同事肖恩 · 克里斯,写的也类似于用于 Windows Phone 和 Android。 内所示的 WebCameraViewController.m 图 11,UIWebView 控件执行的 shouldStartLoadWithRequest 方法做所请求的 URL 上相匹配的模式。 如果 URL 字符串匹配,返回"否"将取消导航代码,并调用本机 UIImagePickerController。 这允许用户从照片库中选取一个图像或板载的相机拍摄新图片。 选择图片后, 代码然后张贴图片回 Web 服务器使用 ASIFormDataRequest 库 (ASIHTTPRequest/allseeing-i.com) 才回正常应用程序流返回 UIWebView。

图 11 iOS 代码

- (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;
    }
}

移动体验的优雅降级

那如果移动 Web 站点的用户不使用本机应用程序 shell 访问相机? 在此方案中,它是重要的用户体验的优雅降级。 优雅降级是生成应用程序,这样,它继续正常工作,即使在查看与比优化软件的概念。 这并不意味着每个功能的工作是在完全相同的方式,或甚至看起来类似于预定的经验,但它的目的是确保所有用户的基本目标仍然可以有即使用户不获得最佳的体验。

要启用此应用程序中的优雅降级,我建立了 ASP。NET MVC 3 控制器和视图图像捕获 URL,"首页/图像,"通过提供一个简单的文件的本机应用程序中壳捕获上载表单中所示图 12。 此窗体允许用户不使用增强型手机壳来完成相同的任务,将图片添加到一个工作秩序,虽然他们得不到集成的经验。 在窗体发送到相同的控制器操作使用的本机应用程序中的贝壳,鼓励所有不同的平台和视图之间的代码重用。

A Simple File Upload Form for Graceful Degradation
图 12 简单文件上载表单的优雅降级

显著的成本优势

混合应用方法可以提供独特的本机应用程序,两者在长期和短期的显著的成本优势。 JQuery 移动等工具缩小可用性的差异,可能导致巨大的业务优势本机设备访问不是必需的。

与增殖像野火一样的移动设备,您有几个选择,当希望构建移动应用程序:

  • 生成本机应用程序为每个平台您想要支持这有明显的优势,提供最佳的用户体验和每个平台的性能,同时允许访问所有设备的本机功能和应用程序商店的营销力量。 缺点,但是,是它可能是建立和维护,因为它将需要一个单独的代码库为您希望支持每个平台的昂贵得多。 此外,每个新版本的应用程序要求该应用程序重新提交给应用程序商店。
  • 生成移动 Web 应用程序本有的优势在于开发、 启动和更新所有平台、 最简单、 最便宜的选项,但用户体验可以受到缺乏对本机硬件功能的访问。 缺乏获得应用程序商店还可以危害您的应用程序,推进市场化的应用程序,您的所有通过。
  • 建立混合本机和移动 Web 应用程序本是,讨论的方法,其中提供了独特的本机应用程序,为每个平台和移动 Web 应用程序的本机硬件访问缺乏发展的成本高的固体折衷。 此选项还提供应用程序商店,增加您的应用程序的访问。

请务必注意这些方法都没有是生来就比其他人 — — 他们都有自己的长处和弱点。 全面的成本效益分析的每个选项将有助于确定哪一条路是适合您的用户与您的业务。 作出这一决定时,时,重要的是考虑用户体验、 先期开发成本和日常维护成本,以及更多微妙的因素,如营销和用户通过。

对于许多业务应用方案,我主张列入的移动 Web 或混合应用程序,如额外的努力,建立独特的本机应用程序,每个移动平台应该大于商业效益。 商业方案需要仔细检查移动 Web 或混合应用程序部署的范围内。

移动应用程序将留在这里,作为计算数组的移动体验远离传统的桌面体验轮班越来越重要。 当您想要构建应用程序移动的空间中,记得妥协不总是一个肮脏的字眼,并可能会导致这两个领域的最佳产品。

Shane Church 是在科罗拉多州丹佛的 EffectiveUI 技术负责人 他一直在 microsoft 开发。重点是 ASP.NET 框架。自 2002 年以来的网和微软移动计算技术。 他的博客就位于 s church.net。您可以了解有关在 EffectiveUI effectiveui.com

多亏了以下的技术专家审查这篇文章:**博士。**James McCaffrey