简体   繁体   中英

Getting IEnumerable<T> semantics with a NetTcpBinding WCF service?

First, this is not a duplicate of IEnumerable<T> as return type for WCF methods , I think I understand that the WCF architecture only allows concrete types to be transferred that can be stuffed into a message.

Second, our setup however is not a general service but connecting up a bundle of proprietary apps via C# + WCF + NetTcpBinding + Protobuf (only) so we may have more room for some tricks that something that needs to be more binding neutral.

Third, it is neither my place nor this question's to propose a different RPC or messaging framework.


"IEnumerable semantics", for the purpose of this question, are:

  • The returned sequence can be arbitrarily large -- it is therefore not possible to convert the sequence to a List or similar.
  • It is not known in advance how many items will be returned
  • Caller can just use foreach and be done with it.

In a local assembly, a C# interface my look like this:

interface IStuffProvider {
  IEnumerable<Stuff> GetItems(); // may open large file or access database
}

You can't map that directly to a WCF service. Something that might achieve the same could look like:

[ServiceContract(SessionMode = SessionMode.Required)]
interface IStuffService {
  [OperationContract]
  void Reset(); // may open large file or access database
  [OperationContract]
  List<Stuff> GetNext(); // return next batch of items (empty list if no more available)
}

Of course, using IStuffService will be more error prone than a IStuffProvider and add in to the mix than many usage scenarios would involve using both service and client on the same machine, so for "user code" it wouldn't be super important to be aware that "the network" is involved, the user code is just interested in a simple interface.

One option would be of course to have a client side interface wrapper implementation, that exposes IStuffProvider and internally forwards to and uses IStuffService . However, it seems it would really be desirable to not have to maintain two interfaces, one for user code, one solely for the WCF communication, especially as these applications are all tightly coupled anyway, so the additional abstraction just seems overhead.

What are the options we have here with WCF?


Note that after reading up on it, the Streamed Binding seems a poor solution, as I would still need a wrapper on the client side and the service interface would get more complex for no real gain in my case: I don't need maximum binary transfer efficiency, I would like good implementation + maintenance efficiency.

Some time ago we faced the same WCF "restriction" in our project. To be short, we ended up with

interface IStuffProvider {
  List<Stuff> GetItems(int page, int pageSize); // may open large file or access database
}

Yes, it is not the same as IEnumerable<Stuff> GetItems(); Yes, we can get in troubles when some item added/removed on already received page. Yes, it is required some server-side tweaks, if server works with items in terms of IEnumerable<Stuff> . But it is still strictly typed and does not bring much additional logic in the client or the server.

What I did in the end is have:

a) The OO interface IStuffProvider as above with the GetLines() member as above.

b) The WCF service interface (and it's implementation) implements an access pattern like this:

    [OperationContract]
    ReadToken StartReadingLines(...);

    [OperationContract]
    // return next batch of items (empty list if no more available)
    List<Stuff> ReadNextLines(ReadToken readToken);

    [OperationContract]
    void FinishReadingLines(ReadToken readToken);

c) The client accesses the service through a proxy class that implements IStuffProvider and maps a call to the GetLines() function to the above three functions:

    // implementation in the proxy class:
    public IEnumerable<Stuff> GetLines()
    {
        var readToken = _dataService.StartReadingLines(...);
        try {
            for (List<Stuff> lines = _dataService.ReadNextLines(readToken); lines.Count > 0; lines = _dataService.ReadNextLines(readToken)) {
                foreach (var line in lines) {
                    yield return line;
                }
            }
        } finally {
            _dataService.FinishReadingLines(readToken);
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM