Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derive from MarshalByRefObject throws not serializable exception #46

Open
AlleSchonWeg opened this issue Aug 3, 2023 · 3 comments
Open
Assignees

Comments

@AlleSchonWeg
Copy link

Describe the bug
Hi,
i have to migrate a very big client server app from .Net Framework 4.8 to .Net 7. This app communicate via .Net Remoting. I have extended your examples to test some things, which works in TaskDemoAppNetRemoting but not in MigratedTaskDemoAppNetRemoting.

To Reproduce
Create new class in Shared Project:

    public class TestClass : MarshalByRefObject
    {
        public string GetData()
        {
            return AppDomain.CurrentDomain.FriendlyName;
        }
    }

In TodoService add property and create object from TestClasss:

  public class TodoService : MarshalByRefObject, ITodoService
  {
      public TodoService()
      {
          MyProp = new TestClass();
      }
      public TestClass MyProp { get; set; }



  }

Extend interface ITodoService:

    public interface ITodoService
    {
        List<Todo> GetTodoList();

        Todo SaveTodo(Todo item);

        void DeleteTodo(Guid id);

        TestClass MyProp { set; get; } // added Property
    }

Call GetData from Client:

 var test = _todoServiceProxy.MyProp.GetData();

The call throws an exception:

CoreRemoting.RemoteInvocationException: 'Der Typ "MigratedTaskDemoAppNetRemoting.Shared.TestClass" in Assembly "MigratedTaskDemoAppNetRemoting.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" ist nicht als serialisierbar gekennzeichnet.'

Expected behavior
No exception should happen. Same code in the .Net Remoting example runs fine.
The expected behavior is, that _todoServiceProxy.MyProp.GetData(); is called on the server, because TestClass in derived from MarshalByRefObject. AppDomain.CurrentDomain.FriendlyName should be MigratedTaskDemoAppNetRemoting.Server.

Additional notes
If the SerializableAttribute was added:

    [Serializable]
    public class TestClass : MarshalByRefObject
    {
        public string GetData()
        {
            return AppDomain.CurrentDomain.FriendlyName;
        }
    }

then it works as expected. The call _todoServiceProxy.MyProp.GetData(); returns MigratedTaskDemoAppNetRemoting.Client.

@theRainbird
Copy link
Owner

Hi AlleSchonWeg,

when decorating TestClass with [Serializable], a serialized copy is sent to client, not a reference. On client side a new instance is deserialized. What you expect is automatic creation of a proxy on client side that refers to the remote TestClass instance.

MarshalByRefObj inheritance is ignored by CoreRemoting. In CoreRemoting, it is not the base class that is decisive for remote accessibility, but whether the type is registered as a service on the server side. CoreRemoting is not an exact clone of classic .NET Remoting. Some concepts are different.

It is not possible in CoreRemoting to return an object by reference, that is not registered as service.
TestClass must be registered as service in order to be callable by reference from client.

A service must implement an interface. In CoreRemoting a proxy can only be created for interfaces, not for classes. This is a limitation of the Castle.DynamicProxy, which is used internally to create proxies. A service must be registered with an interface.

@theRainbird theRainbird self-assigned this Aug 24, 2023
@AlleSchonWeg
Copy link
Author

Hi,
i understand that the concepts are different. With .Net Remoting you can chain classes which derive from MarshalByRefObj. Then all happens on the server side. This is a huge benefit compared to WCF, gRpc and other.
You can do something like this:
var test = _todoServiceProxy.MyProp.NextProp.NextProp1.NextProp2.GetData();
As long as MyProp, NextProp, NextProp1, NextProp2 only derive (there classes) from MarshalByRefObj the GetData method is called on the server, because Remoting creates a tranparent proxy for the properties.

@theRainbird
Copy link
Owner

Hi AlleSchonWeg,

yes, but using such proxy chains tends not to lead to a clean, evolvable and performant software architecture,
Remote calls are slow (ms instead of ns) and may fail (network problems, server tenporary offline, ...).

To get this behavior in CoreRemoting up and running, every object used in the chain must be registered as service at the DI container. A service needs an interface (Castle.DynamicProxy cannot create proxies for classes, but only for interface). To select the right interface, the interface may be provided by an attribute. This attribute could be used as replacement for MarshalByRefObject. To allow registration of different instances of the same interface, the services must be registered with an unique name. The name must be generated (maybe a hash).
The next problem is the lifetime. Singleton lifetime will lead to a big memory leak, because the instances will live forever. SingleCall lifetime will create a new instance on every call from client. So we must create an explicitly controllable lifetime scope. This lifetime scope must be fit to the used DI container. For Castle.Windsor this might be possible, for Microsoft DI not (as far as I know).

I don't think it's worth the effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants