Question

I have the following project structure:

  • Api Project: Contains the WCF svc endpoints
  • ClientServer Library Project: Contains the classes that are used by the server

What I want to do is distribute my ClientServer library as an all encompassing API wrapper that clients can use to connect to the API and pull information.

As the API uses the types defined in the ClientServer Library I would have hoped that adding a ServiceReference in the ClientServer Library would understand that the API return types are actually from the same library as the references will be.

The reason I am doing this is so that I only have to define the classes that get sent to and from the server in one place but also provide an "in-built" mechanism for clients to use the API without any knowledge of how it connects and without and additional depenency librarys (such as a dedicated model library).

Below is a basic example of how I wish it to work:

ClientServer Library:

public class Person {
  public string Name { get; set; }
  public int Age { get; set; }
  .....
}

[ServiceContract]
public interface IPeopleService {

  [OperationContract]
  public Person[] Find(Person person);
}

Api Project

public class PeopleService : ClientServerLibrary.IPeopleServer {      
  public ClientServerLibrary.Person Find(ClientServerLibrary.Person person) {
    // implementation for finding people based on the input person criteria.
  }      
}

Given the example above I want to add a reference to the PeopleService into the ClientServer library so that people who use my library can do something along the lines of:

PeopleServiceClient people = new PeopleServiceClient() // Generated from the service references

// Here "Person" needs to be of type ClientServerLibrary.Person
Person person = people.Find(new Person() { Name = "Gary" });

But currently its regenerating all the classes. I have tried ticking and unticking all the options in the "Add Service Dialogue" but its always the same result.

Hope I have explained my intentions well? Thanks.

EDIT: All projects are using C# .NET v4 so no discrepancies there.

Was it helpful?

Solution 2

OK so in the end in order to fully honour all my requirements I have basically had to ditch the idea of using the automated tools and hand craft each client myself.

This isn't as hard as it sounds though.


I basically created an abstract generic base class ServiceClient where T is a ServiceContract interface (which I had already defined)

The job of the ServiceClient it similar to the ones generated by the VS and CLI tools (svcutil, Etc). It exposed a "Channel" to the derived classes through a ServiceChannel Property. This is achieved using:

// T here is the ServiceContract as configured by the derived class.
ChannelFactory<T>.CreateChannel(binding, endpoint);

I now have the ability to create clients as follows:

class PeopleServiceClient : ServiceClient<IPeopleService>, IPeopleService {
    public Person[] Find(Person person) {
        // Delegate all responsability to the channel (which is connected to the API)
        return base.ServiceChannel.Find(person);
    }
}

Because everything is associated with the IPeopleService interface I can guarantee that each method have been implemented, or at least changes will be noticed at compile time.

I can now use the client like this:

using(var client = new PeopleServiceClient()) {
    Person[] people = client.Find(new Person() { Name = "Gary" });
}

So to answer my question:

Yes, you can have an all encompassing library containing models, contracts and clients; You just have to code it all yourself

So if you don't mind coding a few more classes and not rely on the VS generated code then I think this approach is acceptable.

In case you find it helpful, below a first draft of the ServiceClient class (there WILL be bugs and unforeseen issues so please use for learning purposes only):


THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

/// <summary>
/// Abstract base class for Service Clients wishing to utilise an API
/// via some service contract channel.
/// </summary>
public abstract class ServiceClient<T> : IDisposable where T : class {

    /// <summary>
    /// Creates and configures the ServiceClient and opens the connection
    /// to the API via its Channel.
    /// </summary>
    protected ServiceClient(EndpointAddress endpoint, Binding binding) {
        this.ServiceChannel = ChannelFactory<T>.CreateChannel(binding, endpoint);
        (this.ServiceChannel as ICommunicationObject).Open();
    }

    /// <summary>
    /// Closes the client connection.
    /// </summary>
    public void Close() {
        (this.ServiceChannel as ICommunicationObject).Close();
    }

    /// <summary>
    /// Releases held resources.
    /// </summary>
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Closes the client connection and releases any additional resources.
    /// </summary>
    protected virtual void Dispose(bool disposing) {
        if (disposed) return;

        if (disposing) {
            if (this.ServiceChannel != null) {
                this.Close();
                this.ServiceChannel = null;
                this.disposed = true;
            }
        }
    }

    /// <summary>
    /// Provides a derived class access to the API via a dedicated channel.
    /// </summary>
    protected T ServiceChannel { get; private set; }

    /// <summary>
    /// Indicates if this instance has been disposed.
    /// </summary>
    private bool disposed = false;
}

OTHER TIPS

As explained here, the WCF Proxy Generator (either through svcutil or Add Service Reference) doesn't look in the current assembly.

You'll have to separate your data contracts and service interfaces into another library, like this:

  • Service.Library
    • Person class
    • IPeopleService interface
  • Service.Implementation, references Service.Library
    • PeopleService class
  • Service.Web, references Service.Library and Service.Implementation
    • Web.config (WCF)
    • PeopleService.svc (endpoint)
  • ClientLibrary, references Service.Library
    • PersonServiceClient class (generated from Service.Web, using Service.Library.Person)

So there seems to be no easy way to do this while respecting your criterion "[without] dedicated model library".

If your only goal is to be able to deliver one DLL file that contains all a consumer needs, take a look at ILMerge to combine the assemblies.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top