简体   繁体   中英

WCF - Specify Known Types (PCL / .NET Standard)

This question comes up again, and again. This wasn't an issue on .NET and Silverlight, but on every other platform since, I've never seen a way to specify known types without physically typing them in to your ServiceContract. This means that this list can't be altered dynamically at runtime. It's a problem in Xamarin, UWP, and probably other platforms. So, let's look at this.

Originally, one solution for this problem on .NET and Silverlight was to specify a method for getting the known types on ServiceKnownType like this:

[ServiceKnownType("GetKnownTypes", typeof(GetTypesHelper))]

This has always worked well on .NET and Silverlight, but it does not work on UWP, or Xamarin. I tried this today, and this is the error I get:

System.InvalidOperationException: ServiceKnownTypeAttribute specifies method GetKnownTypes in type Adapt.XivicClient.WCF.ServiceContracts.GetTypesHelper which does not exist. The method must be static and takes one parameter of type ICustomAttributeProvider

Of course, PCL, and .NET Standard libraries do not have a ICustomAttributeProvider class, so this can not be done. So, I tried this other possible solution: https://stackoverflow.com/a/2104482/1878141

This works by specifying a Service Behaviour. But, again, PCL, and Standard do not have a IServiceBehavior class, and neither does say Android.

I tried this code because I thought I could replace the DataContractSerializer, but I get a NotImplementedException on Android.

        dataAccessServiceClient.Endpoint.EndpointBehaviors.Add(new XivicServicBehaviour());

public class XivicServicBehaviour : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

So, what are our options?

I have an answer to my own question. It was staring me in the face for a long time and is very simple.

I don't know why, but we are usually encouraged to use arguments to the OperationContract to specify known types. For something like 8 years, I thought that this was the only way to do this. I have always known that you can specify known types when manually serializing/deserializing with a DataContractSerializer, but I didn't know how to override WCF's default functionality to instantiate a DataContractSerializer that I specify.

Anyway, there is another way, and it's dead simple. You simply pass in a list, or an interface which retrieves a list of types to the constructor of your proxy, and then manually add the types to the operations like so:

This is for a .NET Standard proxy (Task Based Operations)

public class ServiceClientBase<T> : ClientBase<T>, IServiceClient, ICommunicationObject where T : class
{
    #region Constructor
    public ServiceClientBase(Binding binding, EndpointAddress endpointAddress, sc.IKnownTypeGetter knownTypeGetter) : base(binding, endpointAddress)
    {
        foreach (var operation in Endpoint.Contract.Operations)
        {
            var knownTypes = knownTypeGetter.GetKnownTypes();

            foreach (var type in knownTypes)
            {
                operation.KnownTypes.Add(type);
            }
        }
    }
    #endregion

    #region Open/Close
    public virtual Task OpenAsync()
    {
        var communicationObject = this as ICommunicationObject;
        return Task.Factory.FromAsync(communicationObject.BeginOpen(null, null), new Action<IAsyncResult>(communicationObject.EndOpen));
    }

    public virtual Task CloseAsync()
    {
        var communicationObject = this as ICommunicationObject;
        return Task.Factory.FromAsync(communicationObject.BeginClose(null, null), new Action<IAsyncResult>(communicationObject.EndClose));
    }
    #endregion
}

Here is another flavour:

public partial class WCFClientBase<T> : ClientBase<T> where T : class
{
    public WCFClientBase(Binding binding, EndpointAddress endpointAddress, IKnownTypeGetter knownTypeGetter) : base(binding, endpointAddress)
    {
        foreach (var operation in Endpoint.Contract.Operations)
        {
            var knownTypes = knownTypeGetter.GetKnownTypes();

            foreach (var type in knownTypes)
            {
                operation.KnownTypes.Add(type);
            }
        }
    }
}

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