WPF + SelfHost 实现窗体自宿主(API,API和窗体通信)

前言

今天研究了在 WPF 中使用 SelfHost 自宿主。 具体的功能是,在 WPF 中使用自宿主服务,外部调用服务的 API,在 API 里面操作窗体的显示等。

技术点

  1. 在 WPF 中集成 SelfHost
  2. API 和窗体间交互

一、集成 SelfHost

现在已有的资料中,使用 SelfHost 做自宿主服务的基本都是用控制台实现 WebAPI 的功能,或者在 WinFrom 中集成。WFP 和这些还是有挺大的区别。 我这里是参考了这个文章:Self-Host 
具体的步骤如下: 
1. 添加引用包 
在 NuGet 中添加 “Microsoft.AspNet.WebApi.SelfHost”和“Microsoft.AspNet.WebApi.Cors”库,其他依赖的类库基本都会添加进来 
2. 初始化服务 
新建类 “InitConfig”在类里添加初始化自宿主的一些设置。 
具体代码如下: 

复制代码
        public static HttpSelfHostConfiguration InitSelfHostConfig(string baseAddress)
        {
            // 配置 http 服务的路由
            HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
            config.Routes.MapHttpRoute(
                "API Default", "api/{controller}/{id}",
                new { id = RouteParameter.Optional }
            );

            // 设置跨域
            var cors = new EnableCorsAttribute("*", "*", "*"); //跨域允许设置
            config.EnableCors(cors);

            config.Formatters
               .XmlFormatter.SupportedMediaTypes.Clear();

            //默认返回 json
            config.Formatters
                .JsonFormatter.MediaTypeMappings.Add(
                new QueryStringMapping("datatype", "json", "application/json"));

            //返回格式选择
            config.Formatters
                .XmlFormatter.MediaTypeMappings.Add(
                new QueryStringMapping("datatype", "xml", "application/xml"));

            //json 序列化设置
            config.Formatters
                .JsonFormatter.SerializerSettings = new
                Newtonsoft.Json.JsonSerializerSettings()
                {
                    //NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
                    DateFormatString = "yyyy-MM-dd HH:mm:ss" //设置时间日期格式化
                };
            return config;
        }
复制代码

  这里返回的会在新建服务的时候调用

3. 添加 Program 类

WPF 默认是没有这个类的,需要自己手动添加这样的一个类。

这个类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
    string baseAddress = string.Format("http://{0}:{1}/",
           System.Configuration.ConfigurationManager.AppSettings.Get("Domain"),
           System.Configuration.ConfigurationManager.AppSettings.Get("APIPort"));
    using (var server = new HttpSelfHostServer(InitConfig.InitSelfHostConfig(baseAddress)))
    {
        server.OpenAsync().Wait();
        Console.WriteLine(String.Format("host 已启动:{0}", baseAddress));
        App app = new App();
        app.Run();
    }
}

  其中 baseAddress 是拼接的服务地址,同时在“属性”里设置项目的启动对象为“Program”。

后面添加一些 API 进行测试。

1
2
3
4
5
6
7
8
public class HomeController : ApiController
{
    [HttpGet]
    public object Get()
    {
        return new { code = 1, msg = "OK HomeController" };
    }
}

  运行项目在浏览器输入对应的地址即可。

二、API 和窗体交互

由于 API 是另外定义的类。不能直接操作窗体,新建窗体实例、传递窗体都有问题。

这里用的是“事件跨类调用”,参考了“C#通过事件跨类调用WPF主窗口中的控件”。

新建一个类继承“EventArgs”:

1
2
3
4
5
6
7
8
9
10
public class MessageArgs : EventArgs
{
    public MessageArgs(string comMessage)
    {
        this.ComMessage = comMessage;
    }
 
    public string ComMessage { get; set; }
 
}

  在 ApiController 中定义事件、触发事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PlayController : ApiController
{
 
    public static event EventHandler<MessageArgs> PartEvent; //定义在 PlayController 中的一个事件,参数是MessageArgs对象
 
 
    public static void InFunction(string comMessage)
    {
        var messageArg = new MessageArgs(comMessage);
        PartEvent?.Invoke(null, messageArg);  //触发事件,执行所有注册过的函数
    }
 
    [HttpGet]
    public object Play(string comd)
    {
        InFunction(comd);
        return new { code = 0, msg = "成功播放!" };
    }
}

  在窗体中注册上面的事件,以及里面的具体逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public MainWindow()
{
    InitializeComponent();
 
    PlayController.PartEvent += OnStep;  //将该类中的函数注册到Monitor静态类的PartEvent事件中。
}
 
/// <summary>
/// 订阅 Monitor 的PartEvent事件,当触发PartEvent事件时(可能并不在类MainWindow对象中),被注册的函数就行做出相应的响应。
/// </summary>
/// <param name="sender"></param>
/// <param name="message"></param>
public void OnStep(Object sender, MessageArgs message)
{
    Application.Current.Dispatcher.Invoke(new Action(() =>
        {
            DoCommand(message.ComMessage);
        }));
}

  这样,API 就可以和窗体进行通信。

总结

到这里,整个的功能都已经完成。

在自己预研的时候,也是一步一步的。从集成,一点点的改进。

到API和窗体通信,也查了看了一些方法。最终选的这些思路。

posted @   漠里  阅读(3168)  评论(2编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
点击右上角即可分享
微信分享提示