随着互联网技术的发展,Web 2.0用户界面和Web服务的需求日益增长。本文将探讨如何利用WCF(Windows Communication Foundation)服务,结合HTML5、CSS3等前端技术,构建现代的Web应用程序。WCF服务不仅可以作为后端业务逻辑层,还可以通过自托管的方式,提供轻量级的Web服务,从而避免在客户端安装.NET Framework。
WCF最初仅支持SOAP消息。从.NET 3.5开始,通过WebHttpBinding等绑定,WCF开始支持直接从JavaScript的Ajax调用中消费Web服务。然而,JSON等数据格式的消费还不是开箱即用的功能,需要编写大量的DataContract。此外,XML等其他数据格式也无法直接利用JavaScript库,因为需要额外的步骤将XML转换为JSON。
本文介绍的方法将'Stream'作为UI交互操作的输入和输出类型,支持基本认证,并且可以扩展为更高级的用法。这种方法不涉及任何层的定制,完全基于.NET 3.5中WCF的开箱即用特性。此外,它也可以与.NET 4.0一起使用。但是,为了格式化JSON对象,使用了JSON.Net。
首先,下载并构建源代码。可以使用Visual Studio 2010或VS命令行。(代码是为.NET 4.0构建的,但也可以与.NET 3.5一起使用。)启动WCFWebApp.exe。如果端口2011不空闲,请更改为不同的端口。并且需要管理员权限。
当服务器运行时,使用支持JavaScript的任何浏览器打开 "http:/<机器名>:<端口>/index.htm"。输入用户名# 'user' 和密码# 'pass' 并点击登录。将登录并加载关于页面。
点击“状态”链接。检查任何状态的访问选项,然后点击更新。更改页面到关于,然后返回到状态并观察上次选择是否已保留。点击右上角的“注销”。所有这些操作都是通过自托管的WCF服务运行的。这展示了诸如认证、获取静态文件、获取和设置数据等功能。以下部分将详细介绍代码。
主类只是使用WebHttpBinding和WebServiceHost启动WCF服务。服务合同定义了一个方法'Files'来服务所有静态HTML文件,另一个方法'Links'服务所有链接文件,如JavaScript、样式表和数据。其他资源如登录、注销、状态和服务操作。值得注意的是,输入和输出都使用'Stream'数据类型。
class Program {
static void Main(string[] args) {
string baseAddress = "http://" + Environment.MachineName + ":2011/";
using (WebServiceHost host = new WebServiceHost(typeof(WebApp), new Uri(baseAddress))) {
WebHttpBinding binding = new WebHttpBinding();
host.AddServiceEndpoint(typeof(IWCFWebApp01), binding, "").Behaviors.Add(new WebHttpBehavior());
host.Open();
// 其他代码省略以简洁
}
}
}
服务实现。由于这种方法主要是为自托管的WCF服务设计的,因此使用单例实例和并发线程就足够了。考虑适用的会话。但与IIS托管的服务不同,自托管服务通常服务于有限的用户,因此默认的并发性就足够了。在功能线上,构造函数只是将数据加载到本地成员上。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class WebApp : IWCFWebApp01 {
JObject states;
public WebApp() {
if (states == null)
states = JObject.Parse(File.ReadAllText("web\\data\\states.json"));
// 其他代码省略以简洁
}
}
当用户首次访问时,会提供多个HTM、CSS和JavaScript文件。这些由'Files'和'Links'方法处理。链接是在index.htm的head部分引用的文件,如JQuery。在'Files'方法中,根据不同的扩展名从不同的文件夹中获取不同类型的文件。
public Stream Links(string path, string resource, string extension) {
// 其他代码省略以简洁
}
public Stream Files(string resource, string extension) {
switch (extension) {
case "htm":
// 其他代码省略以简洁
case "js":
// 其他代码省略以简洁
}
}
当用户发出登录请求时,会在标准头"Authorization"中发送基本认证令牌。这将在稍后描述的单独方法'Authenticate'中进行验证。此外,用户名作为JSON对象发送在请求流中,使用JSON.Net库将其解析为JSON对象。注销方法与登录类似。
public Stream Login(Stream request) {
if (!Authenticate())
return null;
// 其他代码省略以简洁
JObject o = JObject.Parse(data);
}
当用户点击'States'时,请求到达以下方法。由于此资源没有任何扩展名,请求将不会通过'Files'方法。在这里,请求被认证,数据从成员变量发送。
public Stream States() {
if (!Authenticate())
return null;
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
return new MemoryStream(Encoding.ASCII.GetBytes(states.ToString()), false);
}
当用户进行修改并点击'Update'时,将调用以下方法。这将解析状态ID并更新类成员变量,并将更新后的列表返回给客户端。
public Stream State(Stream request) {
// 其他代码省略以简洁
JObject data = JObject.Parse(new string(buffer));
int id = ((int)data["id"]) - 1;
states["states"][id]["visited"] = true;
return States();
}
需要授权的认证方法将调用以下方法:
public bool Authenticate() {
string userName = "user";
string password = "pass";
string basicAuthCode = Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}: {1}", userName, password)));
string token = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
if (token.Contains(basicAuthCode)) {
return true;
} else {
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
return false;
}
}
客户端代码放置在名为'web'的单独文件夹中。在此文件夹的根目录下,放置了所有静态HTM文件。另外,包括用于图像、JavaScript和样式表的单独子文件夹。这些是从服务器代码中的'Files'方法根据扩展名引用的。
客户端遵循单页应用程序设计。因此,只有'index.htm'是一个完整的HTML页面。其他HTML文件通过Ajax调用填充到'content'部分,如下所示:
function StatesPage() {
this.loadStatesPage = function(data) {
content = undefined;
$.each(data, function(index, value) {
if (data[index]["id"] == "content") {
content = data[index].innerHTML;
$("#content")[0].innerHTML = content;
$("#b_update")[0].onclick = updateStates;
loadStatesTable();
}
});
if (content == undefined) { alert("Failed to load page: Content missing"); return; }
}
// 其他代码省略以简洁
}
认证:客户端认证令牌在登录类中。登录后,此令牌在每个调用的'beforeSend'函数的头部分添加。其他客户端代码需要了解Jquery、JavaScript和Ajax概念,这些在Web上都有详细解释。