网站页面通过 JS 调用 WCF 服务接口

WCF是一门古老的技术,在十几年前非常火,微软雄心勃勃想要把 WCF 做成统一 .net 平台所有通信技术的集大成者,但是现在基本上已经被淘汰,具体原因就不多说了,想必每个使用过 WCF 技术的开发人员,都有自己的心理体会和苦逼岁月。

最近因为工作需求,不得已需要使用 WCF 技术,所以干脆把具体应用做成一个 Demo 进行总结,方便后续快速使用或者老项目的维护工作。在本篇博客的最后会提供源代码的下载。


一、需求场景说明

先说一下我本人所遇到的项目实际需求场景,方便大家对本篇博客的 Demo 功能的理解:

  • 客户要求登录网站的用户账号,要与电脑的硬件信息绑定,确保用户账号只能在固定的一些电脑上使用
  • 客户要求用户在使用网站时,能够操作运行在本机上的 WinForm 客户端(弹出相关的业务模块操作界面)。
    因为客户端有一些功能和业务非常复杂,没必要在网站上去实现。网站和客户端属于两个不同的业务系统,但是需要协作完成整体业务。(很奇葩的需求。。。)

本篇博客的 Demo 主要实现的功能是:

  • WCF 宿主在 WinForm 窗体上,并且对外提供 Http 接口
  • 页面通过 js 调用 WCF 接口操作 WinForm (本博客让 WinForm 弹窗作为样例)
  • 页面通过 js 调用 WCF 接口获取电脑的硬件信息(本博客样例为获取 CPU 号和 MAC 地址)

需要特别说明的是:

网站是部署在服务器上的,不是部署在用户本机上的,所以网站想要调用宿主在用户本机电脑上的 WCF 接口,只能通过网站的 js 进行调用,无法使用网站后端的服务器代码进行调用(我也很想使用服务器后端代码进行调用,因为这样就不存在像 js 调用所遇到的跨域问题)。目前我只实现了页面 js 通过 jsonp 跨域 Get 请求 WCF 接口的方案,对于我所遇到的实际业务已经足够用了。


二、搭建 WCF 服务

新建一个 WinForm 项目(基于 .net framework 4 或者更高版本),添加 4 个程序集的 DLL 引用:

  • Microsoft.VisualBasic (主要实现 WinForm 单实例启动)
  • System.ServiceModel 和 System.ServiceModel.Web (WCF 服务需要使用)
  • System.Management (主要用来获取硬件信息)

让后创建 WCF 服务的接口和实现类(本 Demo 创建两个 WCF 服务),具体实现细节为:

using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Windows.Forms;

namespace JobsWcfDemo
{

    [ServiceContract]
    public interface ITest
    {
        /// <summary>
        /// 测试 Get 请求
        /// 由于 js 调用宿主在 Winform 上的 WCF 接口,存在跨域问题,所以必须使用 jsonp 方式请求
        /// </summary>
        [OperationContract]
        [WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
                ResponseFormat = WebMessageFormat.Json,RequestFormat = WebMessageFormat.Json)]
        string GetTest(string p1, string p2);


        /// <summary>
        /// 测试 Post 请求(WebInvoke 中的 Method 中的值 POST 必须要大写)
        /// 由于宿主在 Winform 上的 WCF 服务,暂时没有找到跨域问题解决的方法,所以可以采用 PostMan 工具进行测试
        /// 也可以使用后端代码 C# 发送 POST 请求进行调用,请求的方式为发送 json 数据
        /// </summary>
        [OperationContract]
        [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest,
                   ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        string PostTest(string p1, string p2);


        /// <summary>
        /// 让 Winform 弹出自己开发的窗体 PopForm
        /// </summary>
        [OperationContract]
        [WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
                ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        void ShowForm();


        /// <summary>
        /// 让 WinForm 弹出 MessageBox 消息框,并显示传递过来的文本内容
        /// </summary>
        [OperationContract]
        [WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
                ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        void ShowMessage(string msg);
    }


    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class TestImpl : ITest
    {
        public string GetTest(string p1, string p2)
        {
            return "Get测试成功,获取参数值:p1=" + p1 + " , p2=" + p2;
        }

        public string PostTest(string p1, string p2)
        {
            return "Post测试成功,获取参数值:p1=" + p1 + " , p2=" + p2;
        }

        public void ShowForm()
        {
            PopForm pf = new PopForm();
            pf.Show();

            //让弹出的窗体,显示在前面
            pf.TopMost = true;
            pf.TopMost = false;
        }

        public void ShowMessage(string msg)
        {
            MessageBox.Show(msg, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Management;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace JobsWcfDemo
{
    [ServiceContract]
    public interface IComputer
    {
        /// <summary>
        /// 获取 CPU 号
        /// </summary>
        [OperationContract]
        [WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
                ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        string GetCPU();


        /// <summary>
        /// 获取 MAC 地址
        /// </summary>
        [OperationContract]
        [WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
                ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        string GetMAC();
    }


    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class ComputerImpl : IComputer
    {
        public string GetCPU()
        {
            List<string> list = new List<string>();
            try
            {
                //获取CPU序列号代码
                ManagementClass mc = new ManagementClass("Win32_Processor");
                ManagementObjectCollection moc = mc.GetInstances();
                string cpu;
                foreach (ManagementObject mo in moc)
                {
                    cpu = mo.Properties["ProcessorId"].Value.ToString().Trim();
                    if (!string.IsNullOrWhiteSpace(cpu))
                    {
                        list.Add(cpu);
                    }
                }
                return string.Join(",", list);
            }
            catch (Exception ex)
            {
                return "获取cpu号有误:" + ex.Message;
            }
        }

        public string GetMAC()
        {
            List<string> list = new List<string>();
            try
            {
                //获取网络适配器
                ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
                ManagementObjectCollection moc = mc.GetInstances();
                string mac;
                foreach (ManagementObject mo in moc)
                {
                    if ((bool)mo["IPEnabled"] == true)
                    {
                        mac = mo["MacAddress"].ToString().Replace(":", "-");
                        //Array ar = (System.Array)(mo.Properties["IpAddress"].Value);
                        //ip = ar.GetValue(0).ToString();
                        list.Add(mac);
                    }
                }
                return string.Join(",", list);
            }
            catch (Exception ex)
            {
                return "获取mac地址有误:"+ ex.Message;
            }
        }
    }
}

然后编写一个用户创建并启动 WCF 服务的类,具体细节如下:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;

namespace JobsWcfDemo
{
    public class MyWcfService
    {

        ServiceHost host;

        /// <summary>
        /// 实例化Wcf服务,并启动服务
        /// </summary>
        /// <param name="typeInerface">接口</param>
        /// <param name="typeClassImpl">实现接口的类</param>
        /// <param name="serviceUrl">服务基础url,例如:http://127.0.0.1:9000/service名称 </param>
        public MyWcfService(Type typeInerface, Type typeClassImpl, string serviceUrl)
        {
            host = new ServiceHost(typeClassImpl);
            WebHttpBinding webBing = new WebHttpBinding();
            webBing.CrossDomainScriptAccessEnabled = true;

            WebHttpBehavior behavior = new WebHttpBehavior();
            behavior.AutomaticFormatSelectionEnabled = true;
            behavior.DefaultOutgoingResponseFormat = WebMessageFormat.Json;

            var end = host.AddServiceEndpoint(typeInerface, webBing, serviceUrl);
            end.Behaviors.Add(behavior);
            if (host.State != CommunicationState.Opened)
            {
                host.Open();
            }
        }

        /// <summary>
        /// 关闭服务
        /// </summary>
        public void Close()
        {
            if (host != null && host.State == CommunicationState.Opened)
            {
                host.Close();
            }
        }
    }
}

最后在 WinForm 的主界面窗体打开时,使用相同的端口,启动两个 WCF 服务,具体细节如下:

using System;
using System.Windows.Forms;

namespace JobsWcfDemo
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        MyWcfService testService;
        MyWcfService compService;

        private void MainForm_Load(object sender, EventArgs e)
        {
            string serviceUrl = "http://127.0.0.1:9000";

            testService = new MyWcfService(typeof(ITest),
                              typeof(TestImpl), serviceUrl + "/test");

            compService = new MyWcfService(typeof(IComputer),
                              typeof(ComputerImpl), serviceUrl + "/comp");
        }
    }
}

这里使用了相同的端口,后面跟不同的二级地址(可以随便指定,这里使用了 test 和 comp)。

到此为止,提供 Http 接口的 2 个 WCF 服务已经搭建完毕,启动 WinForm 即可。(很简单吧。。。)

需要注意的是:WCF 宿主在 WinForm 上,对于 Win7 操作系统来说,可以直接启动,但是对于更高版本的操作系统来说(比如 Win10),则需要以管理员身份进行启动运行,否则会报错。建议在使用 Visual Studio 打开本 Demo 项目时,通过鼠标右键选择【以管理员身份运行】来打开 Visual Studio ,这样后续就可以很方便的调试和运行本 Demo 了。

另外有关 WinForm 单示例启动的相关资料,可以参考我之间发布的博客,主要实现的效果是:无论双击点击运行多少次 exe 程序,只会启动一个程序。毕竟本 Demo 中的 WinForm 窗体启动之后,需要启动 WCF 服务并监听端口。如果启动多个实例的话,会报端口冲突错误。


三、测试 WCF 服务接口

本项目的 Demo 专门开发了一个用于测试 WCF 服务接口的客户端程序。在客户端程序中,可以打开静态页面进行操作,测试 js 调用 WCF 接口。另外本博客的 WCF 也提供了一个 Post 接口,由于我本人没有研究成功 js 如何跨域 Post 调用 WCF 接口,所以就提供了 C# 中的 WebClient 发送 Post 请求接口的代码示例。具体测试的页面代码如下:

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>测试 js 调用 wcf 接口</title>
</head>
<body>
    <h1>目前 js 调用宿主在 WinForm 上的 WCF 接口,暂时只找到了通过 jsonp 跨域 get 请求的方案</h1>
    <h1>有关 WCF 提供的 Post 接口,可以通过 PostMan 工具进行测试</h1>
    <h1>也可以让 js 调用网站自己的后端方法,后端方法 C# 可以 Post 调用 WCF 接口</h1>
    <h1>由于本 Demo 是静态页面,所以只演示通过 jsonp 跨域 get 请求 WCF 接口</h1>
    <fieldset>
        <legend>传递参数请求测试</legend>
        <br />
        参数1:<input type="text" id="p1" /><br />
        参数2:<input type="text" id="p2" />
        <br /><br />
        <input type="button" value="Get请求测试" id="btnGet" />
    </fieldset>
    <hr />
    <fieldset>
        <legend>操作WinForm弹窗测试</legend>
        <br />
        <input type="button" value="弹出自己开发的 PopForm" id="btnPop" />
        <br /><br />
        提示信息内容:<input type="text" id="p3" />
        <input type="button" value="弹出提示信息框" id="btnMsg" />
    </fieldset>
    <hr />
    <fieldset>
        <legend>获取电脑硬件信息</legend>
        <br />
        <input type="button" value="获取 CPU 号" id="btnCPU" />
        <br /><br />
        <input type="button" value="获取 MAC 地址" id="btnMAC" />
    </fieldset>

    <script src="js/jquery-3.6.0.min.js"></script>
    <script>

        $(function () {

            //Get 请求测试
            $('#btnGet').click(function () {

                var p1value = $('#p1').val();
                var p2value = $('#p2').val();

                $.ajax({
                    type: "get", //这行代码可以取消,因为 jsonp 只能是 get 请求
                    dataType: "jsonp",
                    url: "http://localhost:9000/test/GetTest?p1=" + p1value + "&p2=" + p2value,
                    success: function (data) {
                        alert(data);
                    },
                    error: function (err) {
                        alert(err);
                    }
                });
            });

            //弹出自己开发的 PopForm
            $('#btnPop').click(function () {

                $.ajax({
                    dataType: "jsonp",
                    url: "http://localhost:9000/test/ShowForm"
                });
            });

            //弹出提示信息框
            $('#btnMsg').click(function () {

                var p3value = $('#p3').val();

                $.ajax({
                    dataType: "jsonp",
                    url: "http://localhost:9000/test/ShowMessage?msg=" + p3value
                });
            });

            //获取 CPU 号
            $('#btnCPU').click(function () {

                $.ajax({
                    dataType: "jsonp",
                    url: "http://localhost:9000/comp/GetCPU",
                    success: function (data) {
                        alert("CPU号为:" + data);
                    },
                    error: function (err) {
                        alert(err);
                    }
                });
            });

            //获取 MAC 地址
            $('#btnMAC').click(function () {

                $.ajax({
                    dataType: "jsonp",
                    url: "http://localhost:9000/comp/GetMAC",
                    success: function (data) {
                        alert("MAC地址为:" + data);
                    },
                    error: function (err) {
                        alert(err);
                    }
                });
            });

        })

    </script>
</body>
</html>

客户端的 C# 代码调用 WCF 提供的 Post 接口的细节如下:

private void btnPost_Click(object sender, EventArgs e)
{
    //这里没有使用 Newtonsoft.Json 进行 json 的生成
    //主要还是不想让 demo 的体积变的很大

    string p1 = tbp1.Text.Trim();
    string p2 = tbp2.Text.Trim();
    string json = "{\"p1\":\"" + p1 + "\",\"p2\":\"" + p2 + "\"}";

    WebClient wc = new WebClient();
    wc.Encoding = Encoding.UTF8;
    wc.Headers.Add("Content-Type", "application/json");

    string result = wc.UploadString("http://localhost:9000/test/PostTest", json);
    MessageBox.Show(result);
}

然后运行 WCF 服务的客户端程序,然后再运行测试客户端程序,采用静态页面进行测试即可。


WCF 虽然已经过时,但是在某些场景下使用,可能也是一种不错的技术方案。毕竟多掌握一门技术,可以给自己多一些选择。本博客没有描述太多具体的其它方面的辅助细节,可以参考我提供的源代码。

本博客源代码的下载地址为:https://files.cnblogs.com/files/blogs/699532/JobsWcfDemo.zip

posted @ 2022-05-09 16:43  乔京飞  阅读(9489)  评论(0编辑  收藏  举报