grpc-dotnet icon indicating copy to clipboard operation
grpc-dotnet copied to clipboard

Replacement of Server functionality for .NET Framework (4.8)

Open TFTomSun opened this issue 4 years ago • 47 comments

It was announced here that Grpc.Core will be phased out and grpc-dotnet should be used instead. As far as I know Grpc.Core is the only package that supports a gRPC server on the old .NET Framework. Will there be support for a server implementation from grpc-dotnet side that supports the old .NET Framework (e.g. 4.8) ?

TFTomSun avatar Aug 11 '21 13:08 TFTomSun

No there won't be support for .NET Framework server in grpc-dotnet. The new server implementation is built on ASP.NET Core and requires HTTP/2 support that is only available in .NET Core 3 or later.

JamesNK avatar Aug 11 '21 20:08 JamesNK

Hm, they write that moving from Grpc.Core to grpc dotnet should be possible with a few changes, but if a dotnet framework upgrade is required to do so, where alot of legacy stuff has been removed, it is not so easy as they assume. I hope they know that they phased out the .NET Framework 4.x support with that decision.

TFTomSun avatar Aug 11 '21 21:08 TFTomSun

Yes, they are aware of that.

JamesNK avatar Aug 13 '21 20:08 JamesNK

What's the suggestion from Microsoft for duplex inter process communication between a legacy .NET server and a modern .NET 5+ client? I read somewhere, Microsoft suggest gRPC as the replacement for WCF, but if the .NET framework support is dropped in gRPC, what alternatives do we have?

btw. One year ago, GRPC.Core was suggested here: https://github.com/grpc/grpc-dotnet/issues/1032

TFTomSun avatar Aug 13 '21 21:08 TFTomSun

Grpc.Net.Client supports .NET Framework

JamesNK avatar Aug 13 '21 21:08 JamesNK

So Microsoft's suggestion is to invert client and server?

TFTomSun avatar Aug 13 '21 21:08 TFTomSun

I’m giving you options. I don’t know your app and what is best for you depends on your situation.

JamesNK avatar Aug 13 '21 22:08 JamesNK

Same problem here. We have to use a 3rd party .NET Framework 4.7.2 client library that relies on .NET remoting to communicate with a .NET server application. To use the client library from a .NET 5 application, we need to bridge the gap between .NET 5 and .NET Framework 4.7.2 by running different processes and have IPC between them.

We cannot use gRPC for this without .NET Framework support.

It seems we are left alone by Microsoft. There are no other IPC recommendations I am aware of. Do we really need to implement this ourselves with ProtoBuf over pipes or something?

shuebner avatar Sep 23 '21 04:09 shuebner

Grpc.Net.Client supports .NET Framework today.

There is zero chance that Grpc.AspNetCore will ever support .NET Framework.

There are no other IPC recommendations I am aware of.

WCF.

JamesNK avatar Sep 23 '21 05:09 JamesNK

@JamesNK I can understand that decision not to support .NET Framework in the new implementation. But deprecating the old one in such a short time frame is the problem. And my assumption was that there was a misunderstanding between 2 teams in regard to what the new implementation supports and what not. This assumption is also based on their migration Q&A.

There you can find this:

Since Grpc.Core and grpc-dotnet are two distinct libraries, there are going to be some code changes necessary in your project. Since both implementations share the same API for invoking and handling RPCs (we’ve intentionally designed them to be that way), we believe that the code changes necessary should be fairly minimal. For many applications you’ll simply need to change the way you configure your gRPC channels and servers; that is usually only a small portion of your app’s implementation and tends to be separate from the business logic.

This is definitely not the case if we are forced to migrate gRPC server implementations from .NET 4.x to .NET 5. And I think that's a valid general point and has nothing to do with "my app".

TFTomSun avatar Sep 23 '21 06:09 TFTomSun

If your problem is the time frame that Grpc.Core is being deprecated, wouldn't it make more sense to bring this up at its repo? https://github.com/grpc/grpc

JamesNK avatar Sep 23 '21 06:09 JamesNK

@JamesNK Client support is not enough for me though. I need both ends.

WCF would have been my first choice anyway because of its out-of-the-box pipe transport. But that has no .NET Core support. I am not going to maintain WCF on one end and gRPC on the other (should that even be possible).

I will probably go with using SharedMemory and some serializer like MessagePack.

shuebner avatar Sep 24 '21 04:09 shuebner

I saw already that @jtattermusch is active in this repo, too. So it probably doesn't matter where to bring up that point

TFTomSun avatar Sep 24 '21 19:09 TFTomSun

Yeah, this makes me a bit nervous. We moved to gRPC for our GIS application that relies on the MapInfo map engine, which in turn supports integration with .NET Framework 4.x apps. So we have a dependency that blocks us from moving to .NET 5+ right now.

We in turn have a plugin system that uses gRPC for IPC because it looked like it was a good way forward, so that when your map engine was ever ready to move to .NET 5+ we would already be comfortable with a modern technology there.

Further, gRPC felt like a simple-by-design (way moreso than WCF) and stable, slow moving standard to build upon so we felt bold in moving towards it. I absolutely didn't expect it to drop server-side support for .NET Framework while Microsoft still supported it, and even distributes it all the way into Windows 11 in 2021 and beyond.

I understand the design decision but it's unfortunate that this simple and efficient IPC mechanism suddenly needs to be more strongly tied to ASP .NET Core. It is so much more than that. It's indeed a popular scenario to find it used in but, still, it is undeniably ultimately a subset of what gRPC was designed for.

We don't want to use WCF with a ten foot pole as this would be going backwards and put is in a much worse position for migration to .NET 5+ in the future.

jonasnordlund avatar Oct 01 '21 12:10 jonasnordlund

I will probably go with using SharedMemory and some serializer like MessagePack.

Thank you for posting these ideas. From a cursory glance it looks like they might work. We're really only after an abstracted and easy to use IPC mechanism and no ties into other big frameworks.

jonasnordlund avatar Oct 01 '21 12:10 jonasnordlund

I will probably go with using SharedMemory and some serializer like MessagePack.

Thank you for posting these ideas. From a cursory glance it looks like they might work. We're really only after an abstracted and easy to use IPC mechanism and no ties into other big frameworks.

I would hardly call the SharedMemory library a framework, even less a big one. Same with MessagePack.

FYI, I already implemented it on our project. Dependency on MessagePack is two lines in total. Dependency on SharedMemory is less than ten lines (both client and server side). I isolated those few lines of usage even further away behind two simple custom interfaces ISerializer and IBinaryRpcChannel.

Most of the actual RPC stubbing code is boilerplate and could even be generated from the interface definition. Maybe I will write a Source Generator just for fun. It seems to be working fine for our simple "call a method and get a result back" use-cases (i. e. no fancy Exception stacktrace serialization or instance proxying or whatever else .NET Remoting offers).

Atm, the server side process is running as a raw Main method registering the message handling callback at SharedMemory's RpcBuffer. If we need a bit more convenient management support, we may run it as a IHostedService. Maybe not.

That's it. Very simple, at least for our project's modest requirements there. I can imagine your requirements are way more sophisticated, though.

shuebner avatar Oct 01 '21 15:10 shuebner

This indeed sounds great for us, as well as given your description of how the implementation ended up like. While our project is pretty large, our IPC needs are basic. I meant that this grp-core package is getting ties into ASP .NET Core, a big framework, and this is what I find unfortunate as it probably played a role in why it can no longer support .NET Framework 4. I wish it had remained more separate from concrete technologies.

jonasnordlund avatar Oct 04 '21 08:10 jonasnordlund

If anyone is interested in my first try, I have uploaded a minimal example demonstrating the whole chain from .NET 5 to .NET Framework 4.8:

https://github.com/shuebner/RpcOverIpcDemo

I made the .NET Framework 4.8 projects target 32 bit, just for fun.

shuebner avatar Oct 04 '21 12:10 shuebner

Another option that supports .NET Framework 4.6.2 and .NET Standard 2.0 might be ServiceWire @ GitHub // Why ServiceWire?

jonasnordlund avatar Oct 06 '21 12:10 jonasnordlund

This is an update from the dirty real world. Skip this comment if you don't care about exceptions.

I hope I do not end up building a worse version of half of .NET remoting, but I also need exceptions to cross the process boundaries now. I even have to rethrow them at the client side.

Serializing and deserializing exceptions is deliberately limited in .NET Core. The issue of throwing exceptions across processes is comprehensively discussed. Some approaches mentioned in the discussion are actively discouraged by Microsoft. There seems to be no standard solution

My current solution happens to be very similar to this comment. It is independent of the chosen transport or serialization method.

I have a catch-all on the server side and return a special ExceptionResponse that contains at least some rudimentary information about the original exception that is easily serializable.

public sealed record ExceptionResponse(
    int CorrelationId,
    ExceptionInfo ExceptionInfo);

public sealed record ExceptionInfo(
    string ExceptionTypeAssemblyQualifiedName,
    string Message,
    ExceptionInfo? InnerExceptionInfo = null);

The server side logs the full exception along with a random correlationId (I used MEL's EventId). The client side logs the ExceptionInfo (the record's ToString() does a decent enough job) along with the correlationId. Using the correlationId, the server's log statement can be used manually or automatically to enrich the rudimentary information in the client's log.

For throwing on the client side, I check if the client runtime knows the exception type. If yes, I naively try to construct an instance of that type with one of the constructors taking a message and possibly an inner exception. If that succeeds, I throw it. If it fails (e. g. when the server side threw a RemotingException and the client side is .NET 5), the RPC libary cannot decide on its own which exception to throw instead because it does not know how the specific application handles exceptions. The library thus requires the specific application to supply a "default exception reconstructor" to use in such cases. In my particular project there is a base exception class that the RPC target interface defines and that can thus be used as the default exception.

I may update my proof of concept at some point with this, but I make no promises ;-)

shuebner avatar Oct 08 '21 07:10 shuebner

Hi All,

I'd like to understand more about what's being asked for in this issue. I totally understand that requiring to run your server on ASP.NET Core might be difficult do for some workloads, but currently there's no other way to get a well-supported HTTP/2 server-side stack on .NET - besides the native dependency trickery used by Grpc.Core, which has lots of its own problems, as described in the blogpost. I think it's inevitable that over time, there will be less and less software that supports .NET Framework 4.x (which is now "Legacy" and that's been known for a long time) and more software will require some of the more modern .NET versions. gRPC is no exception and since we're trying to stay up-to-date and relevant, it's also quite understandable that at some point we'd need make this decision, especially if dropping the support for legacy .NET frameworks allow us to have a cleaner implementation and reduces the maintenance burden by a lot. So, if we accept that at some point gRPC's support for the legacy .NET Framework will inevitably go away, what are some concrete steps we could take to make this easier for the users?

  • One of the obvious answers is "don't drop Grpc.Core support just yet", but I'm not sure if this would actually help with anything, since supporting Grpc.Core for longer time would just increase the duration of the status quo, without giving any real answers (and eventually everyone would need to move grpc-dotnet anyway).
  • Besides that, is there anything else that can realistically be done? At this point I'm just trying to brainstorm ideas what could be done to make it easier for users to transition off of Grpc.Core.

jtattermusch avatar Oct 19 '21 09:10 jtattermusch

  • Besides that, is there anything else that can realistically be done? At this point I'm just trying to brainstorm ideas what could be done to make it easier for users to transition off of Grpc.Core.

We cannot transition to gRPC server, period. We are stuck with .NET Framework on parts of our application because of 3rd party dependencies. The 3rd party has declined to target anything but .NET Framework because they heavily rely on .NET remoting and do not want to refactor everything. There is nothing we can do about that. We also do not want that to keep us from going forward with .NET 5+ as much as possible.

This means we need a way to have a small .NET Framework RPC server where we can encapsulate everything that depends on .NET Framework. I personally do not care how, as long as it is highly performant. The client should be .NET 5+.

I assume that WCF is more established and more widely used and supported compared to gRPC.Core for .NET Framework. If a .NET 5 gRPC client could be configured to communicate with e. g. a WCF server, that would be enough for us. I may not like configuring two complex products, but using tested off-the-shelf libraries instead of some code from some guy (in this case me) has an appeal.

shuebner avatar Oct 19 '21 10:10 shuebner

Yes, we're also simply in the situation of relying on .NET Framework components as explained earlier. We don't want to be where we are, but it is what it is. Other than a migration guides to competing technologies I don't know much that can be done really. I've contacted Precisely about .NET Core/5+ support in MapInfo Professional and the answer was basically "Yes, but it's hard." They're in turn in the unfortunate situation of wrapping a multi-year effort to migrate their platform off C++ to .NET Framework right when .NET Core was announced. I can understand their situation too.

Since all we're really looking for is a simple IPC mechanism that doesn't even need to use HTTP (but it doesn't hurt either), I'm going to look into one of the other technologies brought up in this thread... when I have time to. ;-) (but I will have had time by the time Grpc.Core is finally sunset for .NET Framework)

So no worries from me, really. At least there are options and we'll move to .NET 5+ as soon as we can!

jonasnordlund avatar Oct 19 '21 17:10 jonasnordlund

@jtattermusch one possible answer could be: deprecate grpc.core when .net framework is deprecated. As far as I know, there's not even a deprecation date yet for .net 4.8.

As stated by the other comments, there are plenty of reasons why a software cannot be migrated easily within one year, some not even within 5 years. In our case it's simply the size of the software project. We have over 1000 developers working on it in for a quite conservative business. I feel comparing an ipc framework with any other library doesn't fit. An IPC framework is used to cross the technology boundaries, at least that's one of the promises that a micro service architecture is giving us. Saying that there's a newer technology stack available now and leaving us in the old technology stack without any possibility to break out of it is the point that I don't understand. grpc promises to be cross platform, cross technology... there are still alot of grpc.core users... you can see it from the download numbers of grpc.core. Just because the technology stack we rely on is old, taking away an existing implementation for it by deprecating it within one year is disappointing for all those who are partly caught within an old technology stack. The assumption of the blogpost that it's the preference of C# devs to migrate to grpc-dotnet (on sever side) is only true for those who can easily migrate to .net 5 or who are already on .net 5... I'm not even sure if this is alresdy the majority right now.

In contrast to the others answers we would like to use the grpc ipc framework and not any other ipc framework because of its compatibility with many other technology stacks, it's performance and maturity.

TFTomSun avatar Oct 19 '21 19:10 TFTomSun

I'd also like to add that since .NET 6 is the very first LTS release since unification of .NET Core and .NET Framework, the announcement came off as a bit of a surprise given that .NET Framework was supported in Grpc.Core. Indeed, .NET 6 will have been released by the time of Grpc.Core abandoning .NET Framework support, but the time frame between these two events are amounting to months which is barely anything to work with for a large enterprise product with its own set of features planned a long time ahead.

Yes, there has been years with .NET Core 1.0 to 3.0 as well as .NET 5, but these products have either been severely lacking in some areas for users coming from .NET Framework, not been LTS releases and thus unsuitable for the enterprise, or both.

This feels like a typical issue of different perspectives where a move to .NET Core tech is nothing special for someone living in the world of IPC and web servers, but a big deal for someone living in the world of GUI-based enterprise applications.

I also do agree with the above post and might emphasize that using gRPC would have been optimal for us (it's well documented and supported in many scenarios), but this looks like it will become a forced choice with no real migration path, so this is why we need to look at alternatives.

jonasnordlund avatar Oct 20 '21 07:10 jonasnordlund

Could someone please confirm or refute my understanding that grpc-dotnet supports neither the .NET Framework (confirmed ad nauseam above) nor macOS in production (until https://github.com/dotnet/runtime/issues/27727 can be fixed)?

The docs at https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-5.0&tabs=visual-studio and https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-5.0#unable-to-start-aspnet-core-grpc-app-on-macos state:

macOS doesn't support ASP.NET Core gRPC with TLS. Additional configuration is required to successfully run gRPC services on macOS.

To work around this issue, configure Kestrel and the gRPC client to use HTTP/2 without TLS. You should only do this during development. Not using TLS will result in gRPC messages being sent without encryption.

Smaug123 avatar Nov 07 '21 09:11 Smaug123

Why wouldn't WCF be the answer if you needed to host a .NET Framework server component and call it from .NET Core/5+?

davidfowl avatar Nov 07 '21 16:11 davidfowl

@davidfowl that's the first real alternative proposal I got and if that is the long term suggestion from Microsoft it would be good to have it in the docs. Currently the docs suggest to migrate away from WCF to GRPC. Just out of curiosity ... can WCF services be called from other technology stacks as well? Are there some client generators available .e.g for typescript?

TFTomSun avatar Nov 07 '21 22:11 TFTomSun

@davidfowl that's the first real alternative proposal I got and if that is the long term suggestion from Microsoft it would be good to have it in the docs.

I agree. The guidance today isn't nuanced and that might be one of the reasons for confusion. We ported the WCF client to .NET Core in the first version because of situations like this. If you must communicate with a .NET Framework server then your only real options are WCF/HTTP as there's no remoting on .NET Core/5+.

can WCF services be called from other technology stacks as well? Are there some client generators available .e.g for typescript?

I'm not sure there's a first class client generation experience for typescript here but I'm not sure what the scenario is. The guidance might require a flow chart ending with some technology depending on what you're trying to accomplish. Does your .NET Framework application need to expose a cross platform interface for all languages? Can it only be exposed to another .NET Core/5+ process instead?

davidfowl avatar Nov 07 '21 23:11 davidfowl

@davidfowl Thanks for bringing up WCF again. Where were you a month ago :wink:? I agree with your comment about the documentation. It seemed to always say: WCF is not supported in .NET Core. Migrate to gRPC. This turns out to be false.

It seems to me that this entire discussion would have been unnecessary if the documentation (or even just the community .NET Core WCF server project) would have mentioned that there is an official .NET Core WCF client.

The doc about remoting being obsolete links to .NET Framework technologies unavailable on .NET Core and .NET 5+, which explicitly mentions WCF. Although I could swear the link was not there when I read it first, it now links to a ommunity-driven port of WCF server to .NET Core. This is still in pre-release, though, so not exactly an attractive ready-to-go alternative for legacy production code-bases yet. Curiously, it does not link to the official .NET Core WCF client, which is the only missing piece to support our common use-case.

I may look into using a .NET Framework WCF server and the .NET Core WCF client later. I hope I do not get performance issues or other trouble when going down the less travelled road of using WCF with pipe transport, which the official .NET Core client seems to also offer. For now, I will stay with my own already-working approach until the first problems arise (and I am sure they will :wink:).

shuebner avatar Nov 08 '21 05:11 shuebner