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

Passing interface argument in SF cross-service remoting call #491

Closed
WhitWaldo opened this issue Mar 24, 2020 · 2 comments
Closed

Passing interface argument in SF cross-service remoting call #491

WhitWaldo opened this issue Mar 24, 2020 · 2 comments
Labels
status-last6months status-olderthan1year Created earlier than current year and no activity

Comments

@WhitWaldo
Copy link

I would ideally like to export a method such as the following on a service using the v2.1 serialization:

public async Task<MyObj> GetObject(IIdentifier ident, CancellationToken cancellationToken)

From within a service, I can pass concrete objects that implement IIdentifier without any issue at all. However, when calling that method from a separate service, it fails with the following exception:

Type 'X.Y.Z.MyIdentifierA with data contract name '...' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute or by adding them to the list of known types passed to DataContractSerializer.

My concrete implementation of MyIdentifierA looks like:

[DataContract(Name="MyIdentifierA", Namespace = "..."]
public class MyIdentifierA: IIdentifier 
{
  public MyIdentifier()  {}

  public MyIdentifierA(string ident)
  {
    Value = ident;
  }

  [IgnoreDataMember]
  public IdentityContext Context => IdentityContext.A;

  [DataMember]
  public string Value {get;set;
}

[DataContract(Name="IdentityContext", Namespace="..."]
public enum IdentityContext 
{
  [EnumMember(Value="A")]
  A,
  [EnumMember(Value="B")]
  B
}

Presumably this should serialize correctly. Per the documentation, the enumeration is marked with the appropriate attribute values, the class has an empty constructor and I'm not using generics here.

There aren't any complex types being referenced by the class, so I can't really put the [KnownTypes] at the top of MyIdentifierA, so I'm not really sure where the exception is suggesting I put that attribute.

I believe I could write a custom serializer that could handle this, but I'd rather not if I can help it.

The remoting documentation describes the remoting V2_1 stack as being compatible with the V1 stack (but there's really not much in this page that explains the differences between the two), but specifically indicates that this one is "interface compatible". If that's the case, what am I doing incorrectly here?

Thanks!

@JustinKaffenberger
Copy link

Hello, Service Fabric consumer here,

It's my understanding that all of the remoting uses DataContract serialization, which isn't even really a Service Fabric-specific technology. The problem you will run into is that your client knows about IIdentifier AND MyIdentifierA, while the Service Fabric service that is hosting the endpoint only knows about IIdentifier, and not the concrete type. At the end of the day, de-serialization has to be done to a concrete type, or some kind of runtime generated proxy type that implements IIdentifier. To my knowledge, while Service Fabric CAN generate the proxy client for your service, the individual parameters for your methods will not have proxy types generated for them.

My recommendation, and how we accomplish this at my company, is to define a base type, and use the KnownType attribute to support polymorphism. So, for example:

[DataContract]
[KnownType(typeof(MyIdentifierA)]
[KnownType(typeof(MyIdentifierB)]
// …
[KnownType(typeof(MyIdentifierZ)]
public class Identifier {
    //...
}

[DataContract]
public class MyIdentifierA : Identifier {
   // ...
}

Then, on the server side you can have logic like:

public async Task<MyObj> GetObject(Identifier ident, CancellationToken cancellationToken) {
    return ident
        {
            MyIdentifierA id => await DoStuffA(id),
            MyIdentifierB id => await DoStuffB(id),
            // ...
            MyIdentifierZ id => await DoStuffZ(id)
            _ => throw new InvalidOperationException()
        };
}

Hope this helps and gives you some ideas!

@gkhanna79 gkhanna79 transferred this issue from microsoft/service-fabric-issues Apr 28, 2020
@ghost ghost added the status-olderthan1year Created earlier than current year and no activity label Sep 2, 2020
@ghost ghost added the status-last6months label Dec 17, 2020
@WhitWaldo
Copy link
Author

I'm sorry I never wrote back to you on this @JustinKaffenberger - thank you very much for the assist here!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status-last6months status-olderthan1year Created earlier than current year and no activity
Projects
None yet
Development

No branches or pull requests

2 participants