在 .Net Core 中,什么替代了 WCF?

140

我习惯使用Class Library(.Net Framework)从零开始创建.Net Framework控制台应用程序,并通过WCF服务公开Add(int x, int y)函数。 然后,我使用控制台应用程序在服务器上代理调用此函数。

但是,如果我使用Console App (.Net Core) 和 Class Library (.Net Core),则System.ServiceModel不可用。 我已经搜索了一些文献,但尚未弄清楚在这种情况下应该使用什么“替代”WCF。

如何在.Net Core中将一个类库中的Add(int x,int y)函数公开给控制台应用程序? 我看到有System.ServiceModel.Web存在,既然这是跨平台的,那我是否需要创建一个RESTful服务呢?


据我所知,是的,您必须创建一个RESTful服务(或者使用一些第三方解决方案,但我不知道任何适用于.NET Core的)。 - Christoph Fink
4
WCF 不太可能移植到 .NET Core,因为大部分的代码都依赖于 Windows 内部库。你可以使用 ASP.NET Core 吗?在那里你将拥有一个方便跨平台的 HTTP 服务器。 - Camilo Terevinto
2
WCF客户端已经得到支持(我不知道有多少),而服务器端则是一个备受争议和投票的功能请求。 - H H
这是一个针对.NET Core的端口: https://github.com/dotnet/wcf - Flupp
7
简而言之:CoreWCF是一个开源项目,可帮助开发人员在.NET Core上构建和运行基于SOAP的服务。 - Ognyan Dimitrov
显示剩余2条评论
14个回答

86

你可以使用 gRPC 在 .NET Core 应用程序中托管 Web 服务。

enter image description here

介绍

  1. gRPC是由Google最初开发的高性能、开源的RPC框架。
  2. 该框架基于客户端-服务器模型的远程过程调用。客户端应用程序可以直接调用服务器应用程序上的方法,就像它是本地对象一样。

示例

服务器代码

class Program
{
    static void Main(string[] args)
    {
        RunAsync().Wait();
    }

    private static async Task RunAsync()
    {
        var server = new Grpc.Core.Server
        {
            Ports = { { "127.0.0.1", 5000, ServerCredentials.Insecure } },
            Services =
            {
                ServerServiceDefinition.CreateBuilder()
                    .AddMethod(Descriptors.Method, async (requestStream, responseStream, context) =>
                    {
                        await requestStream.ForEachAsync(async additionRequest =>
                        {
                            Console.WriteLine($"Recieved addition request, number1 = {additionRequest.X} --- number2 = {additionRequest.Y}");
                            await responseStream.WriteAsync(new AdditionResponse {Output = additionRequest.X + additionRequest.Y});
                        });
                    })
                    .Build()
            }
        };

        server.Start();

        Console.WriteLine($"Server started under [127.0.0.1:5000]. Press Enter to stop it...");
        Console.ReadLine();

        await server.ShutdownAsync();
    }
}

客户端代码

class Program
{
    static void Main(string[] args)
    {
        RunAsync().Wait();
    }

    private static async Task RunAsync()
    {
        var channel = new Channel("127.0.0.1", 5000, ChannelCredentials.Insecure);
        var invoker = new DefaultCallInvoker(channel);
        using (var call = invoker.AsyncDuplexStreamingCall(Descriptors.Method, null, new CallOptions{}))
        {
            var responseCompleted = call.ResponseStream
                .ForEachAsync(async response => 
                {
                    Console.WriteLine($"Output: {response.Output}");
                });

            await call.RequestStream.WriteAsync(new AdditionRequest { X = 1, Y = 2});
            Console.ReadLine();

            await call.RequestStream.CompleteAsync();
            await responseCompleted;
        }

        Console.WriteLine("Press enter to stop...");
        Console.ReadLine();

        await channel.ShutdownAsync();
    }
}

客户端和服务器之间共享的类

[Schema]
public class AdditionRequest
{
    [Id(0)]
    public int X { get; set; }
    [Id(1)]
    public int Y { get; set; }
}

[Schema]
public class AdditionResponse
{
    [Id(0)]
    public int Output { get; set; }
}

服务描述符

using Grpc.Core;
public class Descriptors
{
    public static Method<AdditionRequest, AdditionResponse> Method =
            new Method<AdditionRequest, AdditionResponse>(
                type: MethodType.DuplexStreaming,
                serviceName: "AdditonService",
                name: "AdditionMethod",
                requestMarshaller: Marshallers.Create(
                    serializer: Serializer<AdditionRequest>.ToBytes,
                    deserializer: Serializer<AdditionRequest>.FromBytes),
                responseMarshaller: Marshallers.Create(
                    serializer: Serializer<AdditionResponse>.ToBytes,
                    deserializer: Serializer<AdditionResponse>.FromBytes));
}

序列化器/反序列化器

public static class Serializer<T>
{
    public static byte[] ToBytes(T obj)
    {
        var buffer = new OutputBuffer();
        var writer = new FastBinaryWriter<OutputBuffer>(buffer);
        Serialize.To(writer, obj);
        var output = new byte[buffer.Data.Count];
        Array.Copy(buffer.Data.Array, 0, output, 0, (int)buffer.Position);
        return output;
    }

    public static T FromBytes(byte[] bytes)
    {
        var buffer = new InputBuffer(bytes);
        var data = Deserialize<T>.From(new FastBinaryReader<InputBuffer>(buffer));
        return data;
    }
}

输出

示例客户端输出

示例服务器输出

参考资料

  1. https://blogs.msdn.microsoft.com/dotnet/2018/12/04/announcing-net-core-3-preview-1-and-open-sourcing-windows-desktop-frameworks/
  2. https://grpc.io/docs/
  3. https://grpc.io/docs/quickstart/csharp.html
  4. https://github.com/grpc/grpc/tree/master/src/csharp

基准测试

  1. http://csharptest.net/787/benchmarking-wcf-compared-to-rpclibrary/index.html

8
截至2019年3月,此答案更为相关。请参考https://github.com/grpc/grpc-dotnet(以及.NET Core 3.0中的ASP.NET Core更新)。 - resnyanskiy
2
我认为这是最接近的答案,但仍然遗憾的是,它没有提供任何行为或节流支持。 - joe
4
请注意,截至目前为止,在VS 2019(16.0.2)中,gRPC无法使用.net native工具链进行编译,因此无法在UWP上运行。 - Samuel
2
如果你正在寻找命名管道支持,我编写了一个 gRPC 传输:https://github.com/cyanfish/grpc-dotnet-namedpipes - Cyanfish
1
请注意(截至2020-04-06),grpc-dotnet没有ARM的软件包。 - GafferMan2112
还要注意的是,它使用了HTTP/2协议。https://blog.netsil.com/http-2-and-grpc-the-next-generation-of-microservices-interactions-aff4ffa6faed - Latency

40

由于WCF是一种特定于Windows的技术,而.NET Core旨在跨平台,因此不支持WCF。

如果您正在实现进程间通信,请考虑尝试使用IpcServiceFramework项目。

它允许创建类似于WCF风格的服务,如下所示:

  1. 创建服务契约
public interface IComputingService
{
    float AddFloat(float x, float y);
}
  • 实现该服务

  • class ComputingService : IComputingService
    {
        public float AddFloat(float x, float y)
        {
            return x + y;
        }
    }
    
  • 在控制台应用程序中托管服务

  • class Program
    {
        static void Main(string[] args)
        {
            // configure DI
            IServiceCollection services = ConfigureServices(new ServiceCollection());
    
            // build and run service host
            new IpcServiceHostBuilder(services.BuildServiceProvider())
                .AddNamedPipeEndpoint<IComputingService>(name: "endpoint1", pipeName: "pipeName")
                .AddTcpEndpoint<IComputingService>(name: "endpoint2", ipEndpoint: IPAddress.Loopback, port: 45684)
                .Build()
                .Run();
        }
    
        private static IServiceCollection ConfigureServices(IServiceCollection services)
        {
            return services
                .AddIpc()
                .AddNamedPipe(options =>
                {
                    options.ThreadCount = 2;
                })
                .AddService<IComputingService, ComputingService>();
        }
    }
    
  • 从客户端进程调用服务

  • IpcServiceClient<IComputingService> client = new IpcServiceClientBuilder<IComputingService>()
        .UseNamedPipe("pipeName") // or .UseTcp(IPAddress.Loopback, 45684) to invoke using TCP
        .Build();
    
    float result = await client.InvokeAsync(x => x.AddFloat(1.23f, 4.56f));
    

    3
    好的!也许值得更新以利用.Net Core System.IO.Pipelines。https://blogs.msdn.microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-performance-io-in-net/ - Sigex
    3
    是的,你所忽略的是,这个简短的示例展示了IpcServiceFramework和WCF一样,允许你在不同的消息传递技术之间实现近乎无缝的切换。 - Chris F Carroll
    4
    在某些协议中,WCF可能被认为是特定于Windows的,但SOAP服务并不是。如何在.NET Core中创建SOAP Web服务? - Jeremy
    2
    WCF并不是“仅限于Windows的技术”,而是一种实现互操作SOAP/WSDL堆栈的.NET方式。一个不支持它的Web框架没有简单的方法连接到已经实现的数千个服务。这绝对是未来.NET核心需要解决的问题。 - Wiktor Zychla
    3
    注意:这个项目的作者写了以下评论:“伙计们,由于个人原因,几个月以来我没有时间维护这个项目。与此同时,.NET Core 3.0发布了gRPC功能。”(https://github.com/jacqueskang/IpcServiceFramework/issues/84#issuecomment-556247986)。请参见第二个答案,了解关于 gRPC 的信息。 - gerard
    显示剩余4条评论

    36

    这是一个误导性的答案。微软只移植了WCF客户端。WCF主机或ServiceHost不可用,他们也没有意图这样做。我通过艰难的方式学到了这一点。gRPC才是正确的选择。 - user1034912
    1
    @user1034912,你是不正确的。CoreWCF 是一个轻量级的 WCF 服务器,它被移植到了 .NET core 上。它有一些限制,但对于某些情况来说,它是一个不错的选择。 - Access Denied
    @user1034912 不,服务器端是可用的。https://github.com/CoreWCF/CoreWCF/blob/master/src/Samples/NetCoreServer/Startup.cs - Access Denied
    https://github.com/CoreWCF/CoreWCF/blob/master/src/Samples/DesktopServer/Program.cs 和 https://github.com/CoreWCF/CoreWCF/blob/master/src/Samples/NetCoreServer/Program.cs - Access Denied
    此外,在BingAds中,我们利用CoreWCF库处理数十亿次请求。因此,现在对于端口来说相当稳定。 - AAATechGuy
    显示剩余2条评论

    16

    WCF有很多作用;它是在一台机器上使用命名管道在两个应用程序(进程)之间进行远程过程调用的简便方式,可以通过TCP/IP使用二进制序列化在.NET组件之间提供高容量的内部客户端-服务器通信通道,或者可以通过SOAP提供标准化的跨技术API。它甚至支持通过MSMQ进行异步消息传递。

    对于.NET Core,根据其用途有不同的替代方案。

    对于跨平台API,您可以使用ASP.NET替换为REST服务。

    对于进程间连接或客户端-服务器连接,gRPC是一个很好的选择,@Gopi提供了一个很好的答案。

    因此,“什么可以替代WCF”这个问题取决于您所使用它的用途。


    7

    5
    .NET 5 / Windows Communication Foundation的更新内容 最初的Windows Communication Foundation(WCF)实现仅支持Windows。但是,.NET Foundation提供了可用于客户端的端口,该端口完全开源、跨平台,并得到微软的支持。
    由社区维护与前述客户端库相补充的服务器组件。GitHub存储库位于CoreWCF。 这些服务器组件不受微软官方支持。如果需要WCF的替代方案,请考虑使用gRPC

    4

    据我的研究,最佳解决方案不包括自动生成的代理类。最佳解决方案是创建一个RESTful服务,并将响应主体序列化为模型对象。这些模型是MVC设计模式中通常找到的模型对象。

    感谢您的回复。


    2
    请点击此处查看 - https://learn.microsoft.com/zh-cn/dotnet/core/additional-tools/wcf-web-service-reference-guide - jamiebarrow
    是的,我需要自动生成的代理类。我正在使用RESTful服务/RPC来实现这个功能。 - Sigex
    这个仓库只是为客户端库而设立的。 - orellabac

    2

    您也可以自行托管ASP.NET Core Web API。

    <!-- SelfHosted.csproj -->
    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.1</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <!-- see: https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#framework-reference -->
        <FrameworkReference Include="Microsoft.AspNetCore.App" />
        <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.0" />
      </ItemGroup>
    
    </Project>
    

    // Program.cs
    using System.IO;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    
    namespace SelfHosted
    {
        class Program
        {
            static void Main(string[] args)
            {
                CreateHostBuilder(args).Build().Run();
            }
    
            public static IHostBuilder CreateHostBuilder(string[] args)
            {
                // see: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1
                return Host.CreateDefaultBuilder(args)
                    .ConfigureHostConfiguration(configHost =>
                    {
                        configHost.SetBasePath(Directory.GetCurrentDirectory());
                        configHost.AddJsonFile("appsettings.json", optional: true);
                        configHost.AddEnvironmentVariables(prefix: "SelfHosted_");
                        configHost.AddCommandLine(args);
                    })
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.CaptureStartupErrors(true);
                        webBuilder.UseStartup<Startup>();
                    });
            }
        }
    }
    

    // Startup.cs
    using System;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    namespace SelfHosted
    {
        public class Startup
        {
            public Startup(IConfiguration configuration, IWebHostEnvironment env)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            public void ConfigureServices(IServiceCollection services)
            {
                // see: https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/web-api/index/samples/3.x
                services.AddControllers();
            }
    
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Error");
                    app.UseHsts();
                }
    
                app.UseHttpsRedirection();
                app.UseStaticFiles();
    
                app.UseRouting();
    
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }
        }
    }
    

    // Controllers\TestController.cs
    using System.Net.Mime;
    using Microsoft.AspNetCore.Mvc;
    
    namespace SelfHosted.Controllers
    {
        [ApiController]
        [Produces(MediaTypeNames.Application.Json)]
        [Route("[controller]")]
        public class HelloController : SelfHostedControllerBase
        {
            [HttpGet]
            public ActionResult<string> HelloWorld() => "Hello World!";
    
            [HttpGet("{name}")]
            public ActionResult<string> HelloName(string name) => $"Hello {name}!";
        }
    }
    

    Asp core web api不支持像wcf一样在单个端口上进行双向通信。 - user1034912
    @user1034912,你是正确的。为此,你应该使用websocket/gRPC。 - DoronG

    1

    14
    我认为这个端口是用于从Core向WCF进行通信,而不是用于在Core中编写WCF。 - hal9000
    7
    这个链接的 Github 存储库清楚地说明:“此存储库包含针对客户端的 WCF 库,使基于 .NET Core 构建的应用程序能够与 WCF 服务进行通信。” - Bahaa

    0
    // I found a way to implement WCF client proxy in .Net 6.0 (Core):
    
    
    //--------------------------------------WCF duplex fix------------------------------
    // I found a way to fix my problem.. it took me a week of research
    // So here it is:
    // How to generate WCF Service (.Net Framework 4.8) proxy in client (.Net 6.0):
    
    // If using a callback I need duplex communication
    
        [ServiceContract(CallbackContract = typeof(IEventsService))]
    
    // Just do as explain here but dont expect it to work for Client .Net 6.0 it will
    // only work for client .net Framework 4.8 as Wcf service is .Net Framework 4.8
    

    https://www.codeproject.com/articles/663333/understanding-events-in-wcf#:~:text=Background%20While%20events%20in%20WCF%20are%20nothing%20more,typical%20relationship%20between%20a%20client%20and%20a%20service.

    的内容与编程有关。
    // so here is what I need to do to make it work in .Net 6.0 client:
    // Use netHttpBinding for duplex
    // Put this on web.config of your Wcf service
    
        <service name="HomeManagerWcfService.HomeManagerService" behaviorConfiguration="HomeManagerServiceBehavior">
            <host>
                <baseAddresses>
                <add baseAddress="http://localhost:53318/HomeManagerService"/>
                </baseAddresses>
            </host>
        
        <endpoint address="" binding="netHttpBinding" contract="HomeManagerWcfService.IHomeManagerService"/>
        <!--<endpoint address="" binding="wsDualHttpBinding" contract="HomeManagerWcfService.IHomeManagerService"/>-->
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        </service>
        </services>
        
        <behaviors>
            <serviceBehaviors>
            <!-- HomeManagerService Behavior -->
            <behavior name="HomeManagerServiceBehavior">
                <serviceMetadata httpGetEnabled="true"/>
                <serviceDebug includeExceptionDetailInFaults="true "/>
            </behavior>
            </serviceBehaviors>
        </behaviors>
    
    // Generate files for your client proxy on VisualStudio.Tools.Command line.Developer command prompt
    // The WCF service must be running
    
        svcutil http://localhost:53318/HomeManagerService.svc
    
    //copy both files generated in your client project.
    // if using the VS UI generator (Managed connected service) it won't work, there is a bug in it I guess.
    
    // I also need System.ServiceModel.Http
    // & I need System.ServiceModel.Duplex
    // in the client
    // use NetHttpBinding for duplex communication
    // Use IHomeManagerServiceCallback to implement the callback function 
    // where you want it to run the callback
    
        InstanceContext iCntxt = new InstanceContext(this);// "this" is where i implement the callback for my case
        var endpointAddress = new EndpointAddress("http://localhost:53318/HomeManagerService.svc");
        var binding = new NetHttpBinding();
        var factory = new DuplexChannelFactory<IHomeManagerService>(iCntxt, binding, endpointAddress);
        var clientWCF = factory.CreateChannel();
        
        EmailMessage emailMessage = new EmailMessage();
        emailMessage.Name = "ww23";
        emailMessage.Body = "SSWDDW223";
        emailMessage.EmailAddress = "EMAIL AD dsf2223";
        
        clientWCF.SubscribeCalculatedEvent(); // where we register to the event on the server
        clientWCF.AddEmail(emailMessage);   // the callback event call is in this function on the server
    
    //----------------------------------------------------------------------------------
    
    // for IIS 
    // In order to make sure this mapping appears you need to go to control panel
        -> turn windows features on or off
        -> .NET Framework 4.8 Advanced Services
        -> WCF Services -> HTTP Activation
    

    目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何撰写好答案的更多信息。 - Community

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