使用 Razor 模板生成 HTML 视图
在移动开发领域,术语“混合应用”通常指的是在托管 Web 查看器控件内将其部分(或全部)屏幕呈现为 HTML 页面的应用程序。
在某些开发环境中,可以完全使用 HTML 和 JavaScript 生成移动应用,但在尝试完成复杂的处理或 UI 效果时,这些应用可能会遇到性能问题,并且它们可以访问的平台功能也受到限制。
Xamarin 集两个领域的长处于一身,特别是在利用 Razor HTML 模板化引擎时。 借助 Xamarin,可以灵活地生成使用 JavaScript 和 CSS 的跨平台模板化 HTML 视图,而且还可以完全访问基础平台 API 并使用 C# 进行快速处理。
本文档介绍如何借助 Xamarin,使用 Razor 模板化引擎来生成可跨移动平台使用的 HTML+JavaScript+CSS 视图。
以编程方式使用 Web 视图
在了解 Razor 之前,请先查看此部分,其中介绍了如何使用 Web 视图直接显示 HTML 内容,特别是在应用内生成的 HTML 内容。
Xamarin 提供对 iOS 和 Android 上的基础平台 API 的完全访问权限,因此可以使用 C# 轻松创建和显示 HTML。 每个平台的基本语法如下所示。
iOS
在 Xamarin.iOS 中的 UIWebView 控件中显示 HTML 也只需几行代码:
var webView = new UIWebView (View.Bounds);
View.AddSubview(webView);
string contentDirectoryPath = Path.Combine (NSBundle.MainBundle.BundlePath, "Content/");
var html = "<html><h1>Hello</h1><p>World</p></html>";
webView.LoadHtmlString(html, NSBundle.MainBundle.BundleUrl);
有关使用 UIWebView 控件的更多详细信息,请参阅 iOS UIWebView 方案。
Android
使用 Xamarin.Android 在 WebView 控件中显示 HTML 只需几行代码即可完成:
// webView is declared in an AXML layout file
var webView = FindViewById<WebView> (Resource.Id.webView);
// enable JavaScript execution in your html view so you can provide "alerts" and other js
webView.SetWebChromeClient(new WebChromeClient());
var html = "<html><h1>Hello</h1><p>World</p></html>";
webView.LoadDataWithBaseURL("file:///android_asset/", html, "text/html", "UTF-8", null);
有关使用 WebView 控件的更多详细信息,请参阅 Android WebView 方案。
指定基目录
在这两个平台上,都有一个参数指定 HTML 页面的基目录。 这是设备文件系统上的位置,用于解析对图像和 CSS 文件等资源的相对引用。 例如,诸如以下标记
<link rel="stylesheet" href="style.css" />
<img src="monkey.jpg" />
<script type="text/javascript" src="jscript.js">
引用了以下文件:style.css、monkey.jpg 和 jscript.js。 基目录设置告知 Web 视图这些文件所在的位置,以便可以将这些文件加载到页面中。
iOS
模板输出在 iOS 中使用以下 C# 代码呈现:
webView.LoadHtmlString (page, NSBundle.MainBundle.BundleUrl);
基目录指定为 NSBundle.MainBundle.BundleUrl
,指的是应用程序的安装目录。 Resources 文件夹中的所有文件都复制到此位置,例如下面显示的 style.css 文件:
所有静态内容文件的生成操作都应为 BundleResource:
Android
当 html 字符串显示在 web 视图中时,Android 还需要将基目录作为参数传递。
webView.LoadDataWithBaseURL("file:///android_asset/", page, "text/html", "UTF-8", null);
特殊字符串 file:///android_asset/ 是指应用中的 Android Assets 文件夹,此处显示包含 style.css 文件。
所有静态内容文件的生成操作都应为 AndroidAsset。
从 HTML 和 JavaScript 调用 C#
当 html 页面加载到 Web 视图中时,它会像从服务器加载页面一样处理链接和表单。 这意味着如果用户单击链接或提交表单,Web 视图会尝试导航到指定的目标。
如果链接指向外部服务器(例如 google.com),则 Web 视图会尝试加载外部网站(假设有 Internet 连接)。
<a href="http://google.com/">Google</a>
如果链接是相对的,则 Web 视图会尝试从基目录加载该内容。 显然,此操作不需要网络连接,因为内容存储在设备上的应用中。
<a href="somepage.html">Local content</a>
表单操作遵循相同的规则。
<form method="get" action="http://google.com/"></form>
<form method="get" action="somepage.html"></form>
不会在客户端上托管 Web 服务器;但是,可以使用目前的响应式设计模式中使用的相同服务器通信技术通过 HTTP GET 来调用服务,并通过发出 JavaScript(或调用已托管在 Web 视图中的 JavaScript)来异步处理响应。 这使你能够轻松地将数据从 HTML 传递回 C# 代码进行处理,然后将结果显示在 HTML 页面上。
iOS 和 Android 都为应用代码提供了拦截这些导航事件的机制,以便应用程序代码可以做出响应(如果需要)。 此功能对于生成混合应用至关重要,因为它允许本机代码与 Web 视图交互。
iOS
可以重写 iOS 中 Web 视图上的 ShouldStartLoad 事件,以允许应用程序代码处理导航请求(例如点击链接)。 方法参数提供所有信息
bool HandleShouldStartLoad (UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType) {
// return true if handled in code
// return false to let the web view follow the link
}
然后分配事件处理程序:
webView.ShouldStartLoad += HandleShouldStartLoad;
Android
在 Android 上,只需子类化 WebViewClient,然后执行代码来响应导航请求。
class HybridWebViewClient : WebViewClient {
public override bool ShouldOverrideUrlLoading (WebView webView, IWebResourceRequest request) {
// return true if handled in code
// return false to let the web view follow the link
}
}
然后在 web 视图上设置客户端:
webView.SetWebViewClient (new HybridWebViewClient ());
从 C# 调用 JavaScript
除了告诉 Web 视图加载新的 HTML 页面之外,C# 代码还可以在当前显示的页面中运行 JavaScript。 可以使用 C# 字符串创建整个 JavaScript 代码块并执行,也可以通过 script
标记创建对页面上已有的 JavaScript 进行方法调用。
Android
创建要执行的 JavaScript 代码,然后在其前面加上“javascript:”前缀,并指示 Web 视图加载该字符串:
var js = "alert('test');";
webView.LoadUrl ("javascript:" + js);
iOS
iOS Web 视图提供了专用于调用 JavaScript 的方法:
var js = "alert('test');";
webView.EvaluateJavascript (js);
总结
本节介绍 Android 和 iOS 上的 Web 视图控件功能,这些功能使我们能够通过 Xamarin 生成混合应用,包括:
- 能够从代码中生成的字符串加载 HTML,
- 能够引用本地文件(CSS、JavaScript、图像或其他 HTML 文件),
- 能够在 C# 代码中拦截导航请求,
- 能够从 C# 代码调用 JavaScript。
下一节介绍 Razor,它可用于轻松创建要在混合应用中使用的 HTML。
什么是 Razor?
Razor 是随 ASP.NET MVC 引入的模板化引擎,它最初在服务器上运行并生成要提供给 Web 浏览器的 HTML。
Razor 模板化引擎用 C# 扩展了标准 HTML 语法,以便你可以轻松表达布局并合并 CSS 样式表和 JavaScript。 模板可以引用 Model 类,该类可以是任何自定义类型,并且可以直接从模板访问其属性。 它的主要优点之一是能够轻松混合 HTML 和 C# 语法。
Razor 模板不仅限于服务器端使用,它们还可以包含在 Xamarin 应用中。 使用 Razor 模板以及以编程方式处理 Web 视图的功能,可以使用 Xamarin 生成复杂的跨平台混合应用程序。
Razor 模板基础知识
Razor 模板文件的文件扩展名为 .cshtml。 可以从“新建文件”对话框中的“文本模板化”部分将它们添加到 Xamarin 项目:
下面显示了一个简单的 Razor 模板 (RazorView.cshtml)。
@model string
<html>
<body>
<h1>@Model</h1>
</body>
</html>
请注意与常规 HTML 文件的以下差异:
@
符号在 Razor 模板中具有特殊含义 – 它表示其后的表达式是要计算的 C# 表达式。@model
指令始终显示为 Razor 模板文件的第一行。@model
指令应后跟一个类型。 在此示例中,向模板传递了一个简单的字符串,但这可以是任何自定义类。- 当在整个模板中引用
@Model
时,它提供对生成模板时传递给模板的对象(在本例中是一个字符串)的引用。 - IDE 将自动生成模板(扩展名为 .cshtml 的文件)的分部类。 可以查看此代码,但不能对其进行编辑。 分部类命名为 RazorView,以便与 .cshtml 模板文件名匹配。 此名称用于在 C# 代码中引用模板。
- 还可以将
@using
语句包含在 Razor 模板的顶部,以包含其他命名空间。
然后,可以使用以下 C# 代码生成最终的 HTML 输出。 请注意,我们将 Model 指定为字符串“Hello World”,它将合并到呈现的模板输出中。
var template = new RazorView () { Model = "Hello World" };
var page = template.GenerateString ();
以下是 iOS 模拟器和 Android Emulator 上的 Web 视图中显示的输出:
更多 Razor 语法
在本节中,我们介绍一些基本 Razor 语法,以帮助你开始使用它。 本节中的示例使用数据填充以下类,并使用 Razor 显示它:
public class Monkey {
public string Name { get; set; }
public DateTime Birthday { get; set; }
public List<string> FavoriteFoods { get; set; }
}
所有示例都使用以下数据初始化代码
var animal = new Monkey {
Name = "Rupert",
Birthday=new DateTime(2011, 04, 01),
FavoriteFoods = new List<string>
{"Bananas", "Banana Split", "Banana Smoothie"}
};
显示 Model 属性
如果模型是具有属性的类,则可在 Razor 模板中轻松引用这些属性,如以下示例模板所示:
@model Monkey
<html>
<body>
<h1>@Model.Name</h1>
<p>Birthday: @(Model.Birthday.ToString("d MMMM yyyy"))</p>
</body>
</html>
可以使用以下代码将其呈现为字符串:
var template = new RazorView () { Model = animal };
var page = template.GenerateString ();
最终输出显示在 iOS 模拟器和 Android Emulator 的 Web 视图中,如下所示:
C# 语句
模板中可以包含更复杂的 C#,例如本例中的 Model 属性更新和 Age 计算:
@model Monkey
<html>
<body>
@{
Model.Name = "Rupert X. Monkey";
Model.Birthday = new DateTime(2011,3,1);
}
<h1>@Model.Name</h1>
<p>Birthday: @Model.Birthday.ToString("d MMMM yyyy")</p>
<p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
</body>
</html>
可以通过用 @()
围绕代码来编写复杂的单行 C# 表达式(例如设置年龄的格式)。
要编写多个 C# 语句,可以使用 @{}
围绕这些语句。
If-else 语句
代码分支可以用 @if
表示,如以下模板示例所示。
@model Monkey
<html>
<body>
<h1>@Model.Name</h1>
<p>Birthday: @(Model.Birthday.ToString("d MMMM yyyy"))</p>
<p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
<p>Favorite Foods:</p>
@if (Model.FavoriteFoods.Count == 0) {
<p>No favorites</p>
} else {
<p>@Model.FavoriteFoods.Count favorites</p>
}
</body>
</html>
循环
还可以添加类似 foreach
这样的循环结构。 可以对循环变量使用 @
前缀(在本例中为 @food
),以将其呈现为 HTML。
@model Monkey
<html>
<body>
<h1>@Model.Name</h1>
<p>Birthday: @Model.Birthday.ToString("d MMMM yyyy")</p>
<p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
<p>Favorite Foods:</p>
@if (Model.FavoriteFoods.Count == 0) {
<p>No favorites</p>
} else {
<ul>
@foreach (var food in @Model.FavoriteFoods) {
<li>@food</li>
}
</ul>
}
</body>
</html>
上述模板的输出显示在 iOS 模拟器和 Android Emulator 上运行:
本节介绍了使用 Razor 模板呈现简单只读视图的基础知识。 下一节介绍如何使用 Razor 生成更完整的应用,这些应用可以接受用户输入并在 HTML 视图中的 JavaScript 和 C# 之间进行互操作。
配合使用 Razor 模板和 Xamarin
本节介绍如何使用 Visual Studio for Mac 中的解决方案模板生成自己的混合应用程序。 “文件”>“新建”>“解决方案...”窗口提供了三个模板:
- “Android”>“应用”>“Android WebView 应用程序”
- “iOS”>“应用”>“WebView 应用程序”
- ASP.NET MVC 项目
对于 iPhone 和 Android 项目,“新建解决方案”窗口如下所示 - 右侧的解决方案说明强调了对 Razor 模板化引擎的支持。
请注意,可以轻松将 .cshtml Razor 模板添加到任何现有 Xamarin 项目,而无需使用这些解决方案模板。 iOS 项目不需要 Storyboard 即可使用 Razor;只需以编程方式将 UIWebView 控件添加到任何视图,就可以用 C# 代码呈现整个 Razor 模板。
iPhone 和 Android 项目的默认模板解决方案内容如下所示:
这些模板提供现成的应用程序基础结构,用于加载带有数据模型对象的 Razor 模板、处理用户输入并通过 JavaScript 与用户进行通信。
该解决方案的重要部分包括:
- 静态内容,如 style.css 文件。
- Razor .cshtml 模板文件,如 RazorView.cshtml。
- 在 Razor 模板中引用的 Model 类(如 ExampleModel.cs)。
- 用于创建 Web 视图并呈现模板的特定于平台的类,例如 Android 上的
MainActivity
和 iOS 上的iPhoneHybridViewController
。
下一节介绍项目的工作方式。
静态内容
静态内容包括 CSS 样式表、图像、JavaScript 文件或可从 Web 视图中显示的 HTML 文件链接或引用的其他内容。
模板项目包含一个最小样式表,用于演示如何在混合应用中包含静态内容。 CSS 样式表在模板中的引用方式如下所示:
<link rel="stylesheet" href="style.css" />
可以添加所需的任何样式表和 JavaScript 文件,包括 JQuery 等框架。
Razor cshtml 模板
该模板包含一个 Razor .cshtml 文件,其中包含预先编写的代码,以帮助在 HTML/JavaScript 和 C# 之间进行数据通信。 这样,便可以生成复杂的混合应用,这些应用不仅显示 Model 中的只读数据,而且还接受 HTML 中的用户输入,并将其传递回 C# 代码进行处理或存储。
呈现模板
通过在模板上调用 GenerateString
,可呈现可在 Web 视图中显示的 HTML。 如果模板使用模型,则应在呈现之前提供该模型。 此图说明了呈现的工作方式,不是在运行时由 Web 视图解析静态资源,而是使用提供的基目录查找指定的文件。
从模板调用 C# 代码
从呈现的 Web 视图回调到 C# 的通信通过以下方式完成:设置 Web 视图的 URL,然后在 C# 中拦截请求以处理本机请求,而无需重新加载 Web 视图。
RazorView 按钮的处理方式就是一个示例。 该按钮具有以下 HTML:
<input type="button" name="UpdateLabel" value="Click" onclick="InvokeCSharpWithFormValues(this)" />
InvokeCSharpWithFormValues
JavaScript 函数读取 HTML 表单中的所有值,并设置 Web 视图的 location.href
:
location.href = "hybrid:" + elm.name + "?" + qs;
这会尝试将 Web 视图导航到使用自定义方案(例如 hybrid:
)的 URL
hybrid:UpdateLabel?textbox=SomeValue&UpdateLabel=Click
当本机 Web 视图处理此导航请求时,我们有机会拦截它。 在 iOS 中,这是通过处理 UIWebView 的 HandleShouldStartLoad 事件来完成的。 在 Android 中,我们只需对表单中使用的 WebViewClient 进行子类化,并替代 ShouldOverrideUrlLoading。
这两个导航拦截器的内部构造本质上是相同的。
首先,检查 Web 视图尝试加载的 URL,如果它不以自定义方案 (hybrid:
) 开头,则允许导航按正常方式进行。
对于自定义 URL 方案,URL 中介于方案和“?” 之间的所有内容都是要处理的方法名称(本例中为“UpdateLabel”)。 查询字符串中的所有内容都被视为方法调用的参数:
var resources = url.Substring(scheme.Length).Split('?');
var method = resources [0];
var parameters = System.Web.HttpUtility.ParseQueryString(resources[1]);
此示例中的 UpdateLabel
对文本框参数执行最少量的字符串操作(在字符串前面添加“C# says”),然后回调到 Web 视图。
处理 URL 后,该方法会中止导航,以便 Web 视图不会尝试完成到自定义 URL 的导航。
从 C# 操作模板
从 C# 到呈现的 HTML Web 视图的通信是通过在 Web 视图中调用 JavaScript 来完成的。 在 iOS 上,这通过在 UIWebView 上调用 EvaluateJavascript
来完成:
webView.EvaluateJavascript (js);
在 Android 上,可以通过使用 "javascript:"
URL 方案将 JavaScript 作为 URL 加载,在 Web 视图中调用 JavaScript:
webView.LoadUrl ("javascript:" + js);
打造真正的混合应用
这些模板不使用每个平台上的本机控件,整个屏幕都是单一的 Web 视图。
HTML 非常适合原型制作和显示 Web 最适合的内容,例如富文本和响应式布局。 但是,并非所有任务都适合 HTML 和 JavaScript,例如,滚动浏览较长的数据列表时,使用本机 UI 控件(例如 iOS 上的 UITableView 或 Android 上的 ListView)效果更佳。
可以使用特定于平台的控件轻松增强模板中的 Web 视图 - 只需在 Mac 上使用 Xcode 或在 Android 上使用 Resources/layout/Main.axml 编辑 MainStoryboard.storyboard 即可。
RazorTodo 示例
RazorTodo 存储库包含两个单独的解决方案,以显示完全由 HTML 驱动的应用和将 HTML 与本机控件相结合的应用之间的差异:
- RazorTodo - 使用 Razor 模板的完全由 HTML 驱动的应用。
- RazorNativeTodo - 使用 iOS 和 Android 的本机列表视图控件,但使用 HTML 和 Razor 显示编辑屏幕。
这些 Xamarin 应用在 iOS 和 Android 上运行,利用可移植类库 (PCL) 来共享公共代码,如数据库和模型类。 Razor .cshtml 模板也可以包含在 PCL 中,以便可以轻松地跨平台共享。
这两个示例应用都包含来自本机平台的 Twitter 共享和文本转语音 API,演示了使用 Xamarin 的混合应用程序仍然可以从 HTML Razor 模板驱动的视图访问所有基础功能。
RazorTodo 应用对列表和编辑视图使用 HTML Razor 模板。 这意味着我们几乎可以完全在共享的可移植类库(包括数据库和 .cshtml Razor 模板)中生成应用。 下面的屏幕截图显示 iOS 和 Android 应用。
RazorNativeTodo 应用对编辑视图使用 HTML Razor 模板,但在每个平台上实现本机滚动列表。 这带来了许多好处,包括:
- 性能 - 本机滚动控件使用虚拟化来确保快速、平滑的滚动,即使数据列表很长。
- 本机体验 - 可轻松启用特定于平台的 UI 元素,如 iOS 和 Android 中的快速滚动索引支持。
使用 Xamarin 生成混合应用的一个主要优点是,可以从完全由 HTML 驱动的用户界面(如第一个示例)开始,然后在需要时添加特定于平台的功能(如第二个示例所示)。 iOS 和 Android 上的本机列表屏幕和 HTML Razor 编辑屏幕如下所示。
总结
本文介绍了 iOS 和 Android 上可用的 Web 视图控件的功能,这些功能有助于简化生成混合应用程序。
然后,讨论了 Razor 模板化引擎以及可用于使用 .cshtml Razor 模板文件在 Xamarin 应用中轻松生成 HTML 的语法。 本文还介绍了 Visual Studio for Mac 解决方案模板,借助该模板可快速开始使用 Xamarin 生成混合应用程序。
最后,介绍了 RazorTodo 示例,这些示例演示如何将 Web 视图与本机用户界面和 API 组合在一起。