1
votes

I'm working on a simple Service Fabric cluster, where I want to call a stateless service from a stateless ASP.NET Core 2.0 web API.

The first thing I did was create a .NET Standard 2.0 class library with a simple interface and DTO:

public interface IMicroService : IService
{
    Task<MyDto> GetMahDto(int id);
}

public class MyDto
{
    public string Name { get; set; }
}

Then I created a NuGet package out of it, and added the package as a dependency to both my web API (MyServiceApi project) and the stateless service (MyService).

The listener for the service is defined as

        protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        var fabricListener = new ServiceInstanceListener((context) =>
        {
            var fabricRemotingListener = new FabricTransportServiceRemotingListener(
            serviceContext: context,
            serviceRemotingMessageHandler: null,
            remotingListenerSettings: new Microsoft.ServiceFabric.Services.Remoting.FabricTransport.Runtime.FabricTransportRemotingListenerSettings()
            {
                EndpointResourceName = "myendpoint"
            },
            serializationProvider: null);


            return fabricRemotingListener;
        },
       "mylistener");

        return new ServiceInstanceListener[] { fabricListener };
    }

and the interface implementation is simply

public Task<MyDto> GetMahDto(int id)
    {
        return Task.FromResult(new MyDto() { Name=$"Hallo from TheService in FabricSandbox3 project, id: {id}" });
    }

In MyServiceApi, I have a controller method that looks like

[HttpGet]
    public async Task<MyDto> Get()
    {

        var svc = ServiceProxy.Create<ContractsStandard.IMicroService>(new Uri("fabric:/FabricSandbox4/TheService"), listenerName: "mylistener");

        return await svc.GetMahDto(23);
    }

When I start the debugger, I am able to step into the controller method of MyServiceApi, but it throws the following exception:

System.AggregateException: 'One or more errors occurred. (The exception System.ArgumentException was unhandled on the service and could not be serialized for transferring to the client. Detailed Remote Exception Information: System.ArgumentException: No interface found with this Id -488762776 at Microsoft.ServiceFabric.Services.Remoting.V2.ServiceRemotingMessageSerializersManager.GetInterfaceDetails(Int32 interfaceId) at Microsoft.ServiceFabric.Services.Remoting.V2.ServiceRemotingMessageSerializersManager.CreateSerializers(Int32 interfaceId) at System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey key, Func2 valueFactory) at Microsoft.ServiceFabric.Services.Remoting.V2.ServiceRemotingMessageSerializersManager.GetRequestBodySerializer(Int32 interfaceId) at Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime.FabricTransportMessageHandler.CreateRemotingRequestMessage(FabricTransportMessage fabricTransportMessage, Stopwatch stopwatch) at Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime.FabricTransportMessageHandler.d__7.MoveNext())'

I haven't found anything in my diagnostic events that looks promising, and the only mentions I've seen of similar issues (on this site and elsewhere) typically involved types in different assemblies, which isn't an issue given that the shared interface is contained in a NuGet package.

Any ideas where I might be going wrong here?

3

3 Answers

1
votes

See the bottom of this answer to get straight to the final resolution.

I've found an answer that is partially satisfactory. However, I don't entirely know why it works. It was pure happenstance that I stumbled on this. I'd been working on a way to seamlessly pass headers with fabric requests, which is how I ended up hitting this.

Reference the listener code above, and note this line in particular:

 serviceRemotingMessageHandler: null,

To get this working, I created my own implementation of IServiceRemotingMessageHandler. Note that it simply delegates the call to the base type:

class TestRemotingDispatcher : ServiceRemotingMessageDispatcher, IServiceRemotingMessageHandler
{

    public TestRemotingDispatcher(
        ServiceContext serviceContext, IService serviceImplementation, IServiceRemotingMessageBodyFactory serviceRemotingMessageBodyFactory = null) :
        base(serviceContext, serviceImplementation, serviceRemotingMessageBodyFactory)
    {

    }

    public override void HandleOneWayMessage(IServiceRemotingRequestMessage requestMessage)
    {
        base.HandleOneWayMessage(requestMessage);
    }

    public override Task<IServiceRemotingResponseMessageBody> HandleRequestResponseAsync(
        ServiceRemotingDispatchHeaders requestMessageDispatchHeaders, IServiceRemotingRequestMessageBody requestMessageBody, CancellationToken cancellationToken)
    {

        return base.HandleRequestResponseAsync(requestMessageDispatchHeaders, requestMessageBody, cancellationToken);
    }

    public override Task<IServiceRemotingResponseMessage> HandleRequestResponseAsync(IServiceRemotingRequestContext requestContext, IServiceRemotingRequestMessage requestMessage)
    {

        return base.HandleRequestResponseAsync(requestContext, requestMessage);
    }
}

Then the listener becomes

var fabricListener = new ServiceInstanceListener((context) =>
        {
        var fabricRemotingListener = new FabricTransportServiceRemotingListener(
        serviceContext: context,
        serviceRemotingMessageHandler: new TestRemotingDispatcher(context, this),
            remotingListenerSettings: new Microsoft.ServiceFabric.Services.Remoting.FabricTransport.Runtime.FabricTransportRemotingListenerSettings()
            {
                EndpointResourceName = "mngendpoint"
            },
            serializationProvider: null);


            return fabricRemotingListener;
        },
      "mnglistener");

This works as expected. If I figure out why, I'll update.

Edit: I haven't figured out why explicitly assigning serviceRemotingMessageHandler works, but the subtype of ServiceRemotingMessageDispatcher shown above is not required. It will work by assigning the built-in implementation:

 serviceRemotingMessageHandler: new ServiceRemotingMessageDispatcher(context, serviceInstance)

Final Explanation

All this comes down to my mistake. Note I was assigning null to serviceRemotingMessageHandler at first, which is incorrect. After looking at the source, there is a constructor that will create a dispatcher for you. By assigning null the way I did, no dispatcher existed within the listener.

 public FabricTransportServiceRemotingListener(
        ServiceContext serviceContext,
        IService serviceImplementation,
        FabricTransportRemotingListenerSettings remotingListenerSettings = null,
        IServiceRemotingMessageSerializationProvider serializationProvider = null)
        : this(
              serviceContext,
              new ServiceRemotingMessageDispatcher(
                serviceContext,
                serviceImplementation,
                GetMessageBodyFactory(serializationProvider, remotingListenerSettings)),
              remotingListenerSettings,
              serializationProvider)
    {
    }

The exception message definitely sent me down the wrong path, but at least there is a good explanation for the (self-inflicted) problem.

0
votes

I did encounter the same issue once and was not able to resolve it other then using the NonIServiceProxy method:

public async Task<MyDto> Get()
{

    var svc = ServiceProxy.CreateNonIServiceProxy<ContractsStandard.IMicroService>(new Uri("fabric:/FabricSandbox4/TheService"), listenerName: "mylistener");

    return await svc.GetMahDto(23);
}

Basically it removes the requirement to share the same interface between both the service and the client.

0
votes

I just had this issue, for me it was a case of namespace for the interface. Silly mistake I just copied the class into the other project without the namespace.