Sunday, April 3, 2011

WCF RIA Services and ServiceReferences.ClientConfig

There is no built in support for WCF RIA Services to use ServiceReferences.ClientConfig to config the generated DomainContext.  Following code example shows one of the approach to work around this issue:

Source code:

    using System;
    using System.Collections.Generic;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.DomainServices.Client;
 
    public static class DomainContextExtension
    {
        #region Methods
 
        public static void ApplyConfiguration<TDomainContext, TDomainServiceContract>(
            this TDomainContext domainContext, 
            ChannelFactory<TDomainServiceContract> userDefinedChannelFactory) 
            where TDomainContext : DomainContext
            where TDomainServiceContract : class
        {
            if (userDefinedChannelFactory == null)
            {
                throw new ArgumentNullException("userDefinedChannelFactory");
            }
 
            using (userDefinedChannelFactory)
            {
                var userDefinedCustomBinding = userDefinedChannelFactory.Endpoint.Binding as CustomBinding;
                var defaultWebDomainClient = domainContext.DomainClient as WebDomainClient<TDomainServiceContract>;
                var defaultCustomBinding = defaultWebDomainClient.ChannelFactory.Endpoint.Binding as CustomBinding;
 
                defaultWebDomainClient.ChannelFactory.Endpoint.Address = userDefinedChannelFactory.Endpoint.Address;
                UpdateDefaultTransportBindingElement(userDefinedCustomBinding, defaultCustomBinding);
                UpdateDefaultCustomBindingTimeoutSettings(userDefinedCustomBinding, defaultCustomBinding);
            }
        }
 
        public static ChannelFactory<TDomainServiceContract> CreateUserDefinedChannelFactory<TDomainServiceContract>()
            where TDomainServiceContract : class
        {
            using (var userDefinedChannelFactory = new ChannelFactory<TDomainServiceContract>("*"))
            {
                userDefinedChannelFactory.Endpoint.Contract.Operations.ForAll(description =>
                    GetKnownTypes().ForAll(knownType =>
                        description.KnownTypes.Add(knownType)));
 
                return userDefinedChannelFactory;
            }
        }
 
        private static List<Type> GetKnownTypes()
        {
            var knownTypes = new List<Type>()
                {
                    typeof(QueryResult),
                    typeof(DomainServiceFault),
                    typeof(ChangeSetEntry),
                    typeof(EntityOperationType),
                    typeof(ValidationResultInfo)
                };
 
            return knownTypes;
        }
 
        private static void UpdateDefaultCustomBindingTimeoutSettings(CustomBinding userDefinedCustomBinding, CustomBinding defaultCustomBinding)
        {
            defaultCustomBinding.CloseTimeout = userDefinedCustomBinding.CloseTimeout;
            defaultCustomBinding.OpenTimeout = userDefinedCustomBinding.OpenTimeout;
            defaultCustomBinding.ReceiveTimeout = userDefinedCustomBinding.ReceiveTimeout;
            defaultCustomBinding.SendTimeout = userDefinedCustomBinding.SendTimeout;
        }
 
        private static void UpdateDefaultTransportBindingElement(CustomBinding userDefinedCustomBinding, CustomBinding defaultCustomBinding)
        {
            var userDefinedTransportBindingElement = userDefinedCustomBinding.Elements[0] as TransportBindingElement;
            userDefinedTransportBindingElement.ManualAddressing = true;
            defaultCustomBinding.Elements.RemoveAt(1);
            defaultCustomBinding.Elements.AddRange(userDefinedTransportBindingElement);
        }
 
        #endregion Methods
    }

Usage example:

    var myDomainContext = new MyDomainContext();
    myDomainContext.ApplyConfiguration<MyDomainContextMyDomainContext.IMyDomainServiceContract>(
        DomainContextExtension.CreateUserDefinedChannelFactory<MyDomainContext.IMyDomainServiceContract>());

ServiceReferences.ClientConfig example:

<configuration>
  <system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="CustomBinding_IMyDomainServiceContract"
                 closeTimeout="00:10:00"
                 openTimeout="00:10:00"
                 receiveTimeout="00:10:00"
                 sendTimeout="00:10:00">
          <httpTransport maxBufferSize="2147483647"
                         maxReceivedMessageSize="2147483647" />
        </binding>
      </customBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:4114/RiaService-MyDomainService.svc/binary"
                binding="customBinding"
                bindingConfiguration="CustomBinding_IMyDomainServiceContract"
                contract="RiaService.MyDomainContext+IMyDomainServiceContract"
                name="CustomBinding_IMyDomainServiceContract" />
    </client>
  </system.serviceModel>
</configuration>

With the above approach, we can switch to different Endpoint (Dev, QA or Production) without recompiling the code, just open silverlight XAP file and edit ServiceReferences.ClientConfig file either programatically or manually.