在Windows窗体应用程序中托管ASP.NET Core API

29

背景:我正在开发一个涉及WinForms应用程序的项目。客户希望通过REST API(或类似方式)公开本地HTTP服务器,以允许其他应用程序触发正在运行的WinForms应用程序实例上的功能。首选是使用ASP.NET Core来实现上述API。

我的问题是:如何在同一进程中结构化一个项目,使其具有ASP.NET Core API和WinForms GUI?是否有任何需要注意的潜在问题?


为什么你想要在同一个进程中运行这两个程序? - citronas
@citronas 允许其他应用程序通过REST API触发正在运行的WinForms应用程序实例上的功能,基本上是进程间通信。 - Reza Aghaei
@Matthew 以“什么是最好的方法…”开头的问题通常是基于个人观点,因此不适合作为讨论话题。您是否有关于特定问题的具体疑问? - Reza Aghaei
这样做毫无意义。即使您在同一进程下运行两者,它们之间会有什么“交互”呢?这违反了各种架构原则和最佳实践。封装、单一职责、层设计、适当的分层设计都被破坏了。 - Jonathan Alfaro
至于陷阱...线程模型可能非常不同,因此事情可能不会像您期望的那样工作。 - Jonathan Alfaro
显示剩余3条评论
3个回答

51

在Windows窗体应用程序中托管ASP.NET CORE API并与窗体进行交互

这是一个基本的逐步示例,演示如何创建一个项目,在Windows窗体应用程序内托管ASP.NET CORE API,并执行一些与窗体的交互。

要这样做,请按照以下步骤进行:

  1. 创建一个名为 MyWinFormsApp 的Windows窗体应用程序。

  2. 在设计模式下打开 Form1 ,并在其上放置一个 TextBox

  3. 在设计器中更改 textBox1 Modifiers 属性为 Public 并保存它。

  4. 安装 Microsoft.AspNetCore.Mvc 包。

  5. 安装 Microsoft.AspNetCore 包。

  6. 在项目的根目录下创建一个 Startup.cs 文件,并复制以下代码:

     using Microsoft.AspNetCore.Builder;
     using Microsoft.AspNetCore.Hosting;
     using Microsoft.AspNetCore.Mvc;
     using Microsoft.Extensions.Configuration;
     using Microsoft.Extensions.DependencyInjection;
     namespace MyWinFormsApp
     {
         public class Startup
         {
             public Startup(IConfiguration configuration)
             {
                 Configuration = configuration;
             }
             public IConfiguration Configuration { get; }
             public void ConfigureServices(IServiceCollection services)
             {
                 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
             }
             public void Configure(IApplicationBuilder app, IHostingEnvironment env)
             {
                 if (env.IsDevelopment())
                 {
                     app.UseDeveloperExceptionPage();
                 }
                 app.UseMvc();
             }
         }
     }
    
  7. 将以下代码复制到 Program.cs 中:

  8.  using System;
     using System.Threading;
     using System.Windows.Forms;
     using Microsoft.AspNetCore;
     using Microsoft.AspNetCore.Hosting;
    
     namespace MyWinFormsApp
     {
         public class Program
         {
             public static Form1 MainForm { get; private set; }
    
             [STAThread]
             public static void Main(string[] args)
             {
                 CreateWebHostBuilder(args).Build().RunAsync();
    
                 Application.EnableVisualStyles();
                 Application.SetCompatibleTextRenderingDefault(false);
                 MainForm = new Form1();
                 Application.Run(MainForm);
             }
    
             public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
                 WebHost.CreateDefaultBuilder(args)
                     .UseStartup<Startup>();
         }
     }
    
  9. 在项目根目录下创建一个名为Controllers的文件夹。

  10. Controllers文件夹中创建一个名为ValuesController.cs的文件,并将以下代码复制到文件中:

  11.  using System;
     using Microsoft.AspNetCore.Mvc;
    
     namespace MyWinFormsApp.Controllers
     {
         [Route("api/[controller]")]
         [ApiController]
         public class ValuesController : ControllerBase
         {
             [HttpGet]
             public ActionResult<string> Get()
             {
                 string text = "";
                 Program.MainForm.Invoke(new Action(() =>
                 {
                     text = Program.MainForm.textBox1.Text;
                 }));
                 return text;
             }
    
             [HttpGet("{id}")]
             public ActionResult Get(string id)
             {
                 Program.MainForm.Invoke(new Action(() =>
                 {
                     Program.MainForm.textBox1.Text = id;
                 }));
                 return Ok();
             }
         }
     }
    
  12. 运行该应用程序。

  13. textBox1 中输入“hi”。

  14. 打开浏览器并浏览 http://localhost:5000/api/values → 您将看到响应中的 hi

  15. http://localhost:5000/api/values/bye → 您将在 textBox1 中看到 bye

进一步阅读

您可能还对如何在Windows窗体(WinForms)中使用依赖注入(DI)感兴趣。


3
如我在评论中提到的,答案并不是关于最佳实践的,而更多是一个概念证明,然而这个解决方案有以下几点值得注意:(1) 总体来说,这个想法还是可行的,这是一个进程间通信的方法。(2) 在生产环境中,我使用了类似的方法,使用WCF在WinForms应用程序之间进行IPC,没有遇到任何问题。(3) 我在测试环境中为此目的使用了ASP.NET WEB API,但我还没有尝试过使用ASP.NET CORE。(4) 只要使用Invoke来访问UI线程,就没有问题去访问UI线程。 - Reza Aghaei
2
谢谢Reza,你的回答非常有效。 - morteza jafari
@mortezajafari 太棒了!很高兴听到它对你有用! - Reza Aghaei
感谢您的代码,但不支持高于2的NuGet包, System.MethodAccessException:'尝试通过方法'Microsoft.Extensions.Logging.Configuration issue - Zanyar Jalal
@ZanyarJ.Ahmed 我记不起我用来编写这段代码的.NET版本了。但是一旦我有时间,我可能会更新为.NET 6的步骤。而且这些包与你在ASP.NET CORE项目中使用的包是相同的。 - Reza Aghaei
显示剩余7条评论

1
我有一个用来实现这个功能的类。基本上,我在.NET 6.0 Web API Controller起始模板中创建了一个函数。
public async Task Listen(IServiceCollection services, List<Type> controllers, int port, CancellationToken token)
{
    var builder = WebApplication.CreateBuilder();
    builder.WebHost.ConfigureKestrel(options => options.Listen(System.Net.IPAddress.Parse("127.0.0.1"), port));
    // Add services to the container.

    builder.Services.AddControllers();
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();

    builder.Services.Add(services);
    //builder.Services.AddSingleton<HostedCheckoutService>(hc);

    // Add our external card processor controller. This mocks a web page that is returned
    // in the Hosted Checkout iframe.
    //var assembly = typeof(LocalCardProcessorController).Assembly;
    foreach (var controller in controllers)
    {
        var assembly = controller.Assembly;
        builder.Services.AddControllers().PartManager.ApplicationParts.Add(new Microsoft.AspNetCore.Mvc.ApplicationParts.AssemblyPart(assembly));
    }

    var app = builder.Build();

    //Configure the HTTP request pipeline.
    if (true)//app.Environment.IsDevelopment())
    {
        // Need to manually navigate to http://127.0.0.1:16600/swagger/index.html
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c => c.SwaggerEndpoint("v1/swagger.json", "MyAPI V1"));
    }

    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints => endpoints.MapControllers());
           
    await app.RunAsync(token);
    await app.DisposeAsync();

}

在你的表单中这样调用它;

APIServer server = new APIServer();

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<SomeService1>();
serviceCollection.AddSingleton<SomeService2>();
server.Listen(serviceCollection, new() { typeof(SomeController1), typeof(SomeController2) }, 9001, new CancellationToken());

请注意,Swagger正在使用Swashbuckle.AspNetCore(6.3.0)


1
我安装了 Microsoft.AspNetCore.MvcMicrosoft.AspNetCore 包但无法使用。
我查看了这个 WebApplication.CreateBuilder 方法文档,发现需要 Microsoft.AspNetCore.dll,但我无法使用它。
希望这能帮助其他人。
请安装 Microsoft.AspNetCore.App 包。
dotnet add package Microsoft.AspNetCore.App --version 2.2.8

最小化API:
Microsoft.AspNetCore.Builder.WebApplication app = Microsoft.AspNetCore.Builder.WebApplication.Create(new string[] { });
app.MapGet("/", () => "Hello World!");
app.RunAsync();

好的,现在你可以打开http:localhost:5000来查看Hello World!

因为很少有人在WinForm中使用参数,所以这里省略了string[] args,并使用new string[] { }代替


如果您想使用控制器,就像ASP.NET Core一样,那么您可以使用以下内容:

var builder = Microsoft.AspNetCore.Builder.WebApplication.CreateBuilder(new string[]{});
builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();
app.RunAsync();

HomeController.cs

using Microsoft.AspNetCore.Mvc;

namespace RuyutWinFormsApi;

[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("This is a test api");
    }
}

您可以打开 http://localhost:5000/api/home 查看 这是一个测试API

顺便说一句,如果您想更改端口,可以添加以下内容:

app.Urls.Add("http://0.0.0.0:8080");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接