首页 > 代码库 > 托管 WCF 服务

托管 WCF 服务

托管 WCF 服务

 

WCF 中,最让我感到兴奋的方面是它可以把服务托管在任意程序中,而不一定是网站服务器。这开启了一种可能性,即创建的服务,其实现可以动态改变的能力,因为它们能够托管在 fsi.exe。为了使前面的示例能够运行在 fsi.exe,还需要对它做一些修改,但修改相当简单。

清单 11-13 展示了清单 11-9修改后的版本,它能够运行在 fsi.exe

 

清单 11-13 托管在 F#交互中的服务

 

#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0";;

#r "System.ServiceModel.dll";;

open System

open System.ServiceModel

open System.Runtime.Serialization

 

// a range of greetings that could be used

let mutable f = (fun x -> "Hello: " + x)

f <- (fun x -> "Bonjour: " + x)

f <- (fun x -> "Goedendag: " + x)

 

// the service contract

[<ServiceContract

  (Namespace =

    "http://strangelights.com/FSharp/Foundations/WCFServices")>]

type IGreetingService =

  [<OperationContract>]

  abstract Greet : name:string -> string

 

// the service implementation

type GreetingService() =

  interface IGreetingService with

    member x.Greet( name ) = f name

 

// create a service host

let myServiceHost =

  let baseAddress = new Uri("http://localhost:8080/service")

 

  let temp = new ServiceHost(typeof<GreetingService>, [|baseAddress|])

 

let binding =

  let temp =

    new WSHttpBinding(Name = "binding1",

                     HostNameComparisonMode =

                       HostNameComparisonMode.StrongWildcard,

                     TransactionFlow = false)

    temp.Security.Mode <- SecurityMode.Message

    temp.ReliableSession.Enabled <- false

    temp

 

temp.AddServiceEndpoint(typeof<IGreetingService>, binding, baseAddress)

|> ignore

temp

 

// open the service host

myServiceHost.Open()

 

注意,在清单 11-13 中的类型 IGreetingService GreetingService与清单 11-9相比,大部分没有改变,只是把 GreetingService类型改成了可以使用可变函数,这是为了可以控制运行时的动作;然后需要创建服务主机,完成前面示例中网站服务器和 web.config的工作。可以看到在清单 11-10中的 web.config和在清单 11-9中的服务本身。注意,myServiceHost包含 baseAddress,这是服务监听请求的地址,还有一个 binding,它控制使用的协议;最后,调用 myServiceHost Open 方法,设置服务监听。

然后,对客户端做一改动,使它能重复调用这个服务,如清单 11-14所示,这样,可以看到服务的结果随时间而改变。

 

清单 11-14 客户端访问托管在 F#交互中的服务

 

let client = new GreetingServiceClient()

  while true do

    printfn "%s" (client.Greet("Rob"))

    Console.ReadLine() |> ignore

 

也需要修改客户端的 .config文件,以指向正确的地址:

 

<endpoint address="http://localhost:8080/service"

 

做完这些之后,就可以动态改变服务了,如图 11-5所示。

 

11-5 调用动态的 WCF服务

 

把服务托管在程序的另一个重要原因是能够创建桌面应用程序,监听中心服务器的更新。传统上,这种类应用程序必须选举中心服务器,然而,我们知道,如果这种选举过于频繁,会导致大量不必要的网络流量。

清单 11-15 演示了具体做法。这是一个空窗体,托管了监听来自客户端更新的服务;这里,更新是需要显示的背景图像。服务定义了一个函数 ReceiveImage,它接收组成图像的二进制数据。服务的实现是这样的,每接收到一个图像,就触发事件 newImgEvent;这样,每次接收到新图像以后,窗体就更新。把窗体和事件连接起来非常简单:

 

newImgEvent.Add(fun img -> form.BackgroundImage <- img)

 

只需要调用这个事件的 Add 方法,并把它传递给更新窗体的函数就可以了。你将注意到,托管服务所需要的代码(即,定义 myServiceHost的代码)与前面示例相比,没有改变。

 

清单 11-15 有内置服务的 Windows窗体

 

open System

open System.IO

open System.Drawing

open System.ServiceModel

open System.Windows.Forms

 

let myServiceHost =

  let baseAddress = new Uri("http://localhost:8080/service")

 

  let temp = new ServiceHost((type Service.ImageService), [|baseAddress|])

 

  let binding =

    let temp = new WSHttpBinding()

    temp.Name <- "binding1"

    temp.HostNameComparisonMode <-

      HostNameComparisonMode.StrongWildcard

    temp.Security.Mode <- SecurityMode.Message

    temp.ReliableSession.Enabled <- false

    temp.TransactionFlow <- false

    temp

 

  temp.AddServiceEndpoint((type Service.IImageService), binding, baseAddress)

  |> ignore

  temp

 

myServiceHost.Open()

 

let form = new Form()

 

Service.newImgEvent.Add(fun img -> form.BackgroundImage <- img)

 

[<STAThread>]

do Application.Run(form)

 

为了创建客户端,首先必须创建代理,方法同清单 11-11;运行工具 SvcUtil.exe,把服务的 URL作为参数,这将创建 C#代理,可以把它编译成 .NET程序集,在 F#中使用。这里,代理命名为 ImageServiceClient。清单 11-16中的客户端定义看上去有点复杂,但是,大量的代码只是绘制窗体上的控件,或打开图像文件;真正重要的代码在最后,把一个函数添加到按钮 Send click事件。这段代码从磁盘上读图像,并把它加载到字节数组;字节数组然后传递给代理的 ReceiveImage方法。

 

清单 11-16 给服务器发送图像的客户端

 

open System

open System.IO

open System.Windows.Forms

 

// create a form for sending images

let form =

  // create the form itself

  let temp = new Form(Width=272, Height=64)

  // text box for path to the image

  let imagePath = new TextBox(Top=8, Left=8, Width=128)

  // browse button to allow the user to search for files

  let browse = new Button(Top=8, Width=32, Left=8+imagePath.Right,

                       Text = "...")

  browse.Click.Add(fun _ ->

    let dialog = new OpenFileDialog()

    if dialog.ShowDialog() = DialogResult.OK then

      imagePath.Text <- dialog.FileName)

 

  // send button to send the image to the server

  let send = new Button(Top=8, Left=8+browse.Right, Text = "Send")

  send.Click.Add(fun _ ->

    // open and send the file

    let buffer = File.ReadAllBytes(imagePath.Text)

    let service = new ImageServiceClient()

    service.ReceiveImage(buffer))

 

  // add the controls and return the form

  temp.Controls.Add(imagePath)

  temp.Controls.Add(browse)

  temp.Controls.Add(send)

  temp

 

// show the form

[<STAThread>]

do Application.Run(form)

 

11-6 展示了正在执行中的示例程序;用户将要选择图像发送给客户端。

 

11-6 托管在 Windows窗体中的 WCF服务

 

对于能够监听更新的桌面应用程序来说,这远不是故事的全部。发送更新的“客户端”需要知道它应该发送更新给哪一个服务和桌面应用程序。在这个服务中,我们直接通过硬编码服务的地址,使客户端知道的;在实际环境中,还需要用其他方法实现服务。这个服务应该告诉中心“客户端”服务正在监听更新,当服务停止时,向中心“客户端”发出警报;然后中心“客户端”应该需要循环遍历正在监听更新的所有服务,把数据推给每一个服务。

 

第十一章小结

 

这一章讨论了用 F# 创建分布式应用程序的几个主要选择;学习了通过 F# .NET库的联合,我们能够更集中精力于创建分布式应用程序的关键技术挑战;还学习了使用 F#功能控制应用程序的复杂度。在下一章,我们将讨论面向语言编程(language-oriented programming),这项技术经过函数程序员多年的考验,值得信赖,可以真正使程序员的生活更简单。