Silverlight程序中動態設置WCF服務地址的方法

在Silverlight项目中添加服务引用后会在Silverlight项目中生成一个ServiceReferences.ClientConfig文件,这个文件中包含了引用服务的绑定(bindings)和终结点(Endpoint)的配置信息。下面是引用一个WCF服务

    在Silverlight项目中添加服务引用后会在Silverlight项目中生成一个ServiceReferences.ClientConfig文件,这个文件中包含了引用服务的绑定(bindings)和终结点(Endpoint)的配置信息。下面是引用一个WCF服务后自动生成的配置信息:

 <configuration>
     <system.serviceModel>
         <bindings>
             <basicHttpBinding>
                 <binding name="BasicHttpBinding_IService1" maxBufferSize="2147483647"
                     maxReceivedMessageSize="2147483647">
                     <security mode="None" />
                 </binding>
             </basicHttpBinding>
         </bindings>
         <client>
             <endpoint address="http://localhost:4177/Services/Service1.svc"
                 binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
                 contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
         </client>
     </system.serviceModel>
 </configuration>
 
在程序开发阶段,使用上面的配置信息不会有什么错误。在部署程序时,服务的地址通常和开发时使用的服务地址是不一样的,这时就需要修改上面的配置信息中的终结点的地址。但是Silverlight项目编译后会将ServiceReferences.ClientConfig嵌入到生成的xap文件中,这给修改带来了一定难度。关于动态设置服务终结点的地址Tim Heuer在Managing service references and endpoint configurations for Silverlight applications(http://zdd.me/managingendpoint)中介绍了几种方法,大家可以参考。下面是我在开发过程使用的两种方法。

一、WCF和Silverlight位于同一站点下

这种情况下可以通过Silverlight程序的Application.Current.Host.Source和服务的相对地址来获得服务的地址。例如名为Service1.svc的服务放在网站的Services文件夹下,可以通过下面的代码取得它的地址:

 Uri serviceUri = new Uri(Application.Current.Host.Source, "../Services/Service1.svc")

然后就可以通过在引用服务时自动生成的客户端代理类的重载构造函数

 public Service1Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 
                 base(binding, remoteAddress) {
         }
来新建一个该类实例了
 BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
             binding.MaxReceivedMessageSize = int.MaxValue;
             binding.MaxBufferSize = int.MaxValue;
             ServiceReference1.Service1Client client = new ServiceReference1.Service1Client(binding, new EndpointAddress(
                 new Uri(Application.Current.Host.Source, "../Services/Service1.svc")));

二、WCF位于单独的站点下

这种情况下WCF的地址和网站的地址不一样了,因此就不能使用上面的方法了。这时可以将服务的地址配置在网站的web.config中,然后通过Silverlight的InitParams参数将服务地址传送到Silverlight程序中。下面是实现方法:
1、在web.config中配置服务的地址

<appSettings>
    <!-- 格式 服务名svc  -->
    <add key="Service1svc" value="http://localhost:1468/Service1.svc"/>
</appSettings>

2、通过使用Literal控件动态生成Silverlight的InitParams参数

然后在网页的Page_Load中从web.config中获取到配置的服务地址并设置InitParams参数的值

 protected void Page_Load(object sender, EventArgs e)
         {
             if (!Page.IsPostBack)
             {
                 Hashtable services = new Hashtable();
                 foreach (string key in ConfigurationManager.AppSettings.AllKeys)
                 {
                     if (key.EndsWith("svc", StringComparison.CurrentCultureIgnoreCase))
                     {
                         services.Add(key,ConfigurationManager.AppSettings[key]);
                     }
                 }
 
                 StringBuilder sb = new StringBuilder();
 
                 foreach (string key in services.Keys)
                 {
                     sb.Append(string.Format(",{0}={1}", key, services[key]));
                 }
 
                 this.SLInitParams.Text = string.Format("<param name=\"InitParams\" value=\"{0}\" />", sb.ToString());
             }
        }

3、接着在Silverlight的Application_Startup中将参数中的值存储在ResourceDictionary,以备使用。然后在Silverlight项目中添加一个ServiceType枚举,用于列出在web.config中配置的所有服务地址的键值。

4、新建一下ServiceHelper泛型类,该类包括两个静态方法:1)GetInstance接受一个ServiceType类型的参数,通过反射创建一个服务的客户端代理类的实例;2)GetServiceAddress同样接受一个ServiceType类型的参数,获取服务的地址。

public class ServiceHelper<T> where T : class
    {
        /// <summary>
        /// 根据服务类型新建一个服务实例
        /// </summary>
        /// <param name="serviceType">服务类型</param>
        /// <returns></returns>
        public static T GetInstance(ServiceType serviceType)
        {
            T _instance = null;
            string serviceUri = GetServiceAddress(serviceType);
            if (string.IsNullOrEmpty(serviceUri)) return null;

            object[] paras = new object[2];
            BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
            binding.MaxBufferSize = int.MaxValue;
            binding.MaxReceivedMessageSize = int.MaxValue;
            EndpointAddress address = new EndpointAddress(new Uri(serviceUri, UriKind.Absolute));

            paras[0] = binding;
            paras[1] = address;

            ConstructorInfo constructor = null;

            try
            {
                Type[] types = new Type[2];
                types[0] = typeof(System.ServiceModel.Channels.Binding);
                types[1] = typeof(System.ServiceModel.EndpointAddress);
                constructor = typeof(T).GetConstructor(types);
            }
            catch (Exception)
            {
                return null;
            }

            if (constructor != null)
                _instance = (T)constructor.Invoke(paras);

            return _instance;
        }

        /// <summary>
        /// 取得服务地址
        /// </summary>
        /// <param name="serviceType">服务类型</param>
        /// <returns></returns>
        public static string GetServiceAddress(ServiceType serviceType)
        {
            return App.Current.Resources[serviceType.ToString()].ToString();
        }
    }
然后就可以通过下面的代码创建一下服务的客户端代理类的实例了

示例代码下载ServiceEndpoints.zip


為了你的幸福,我一直在努力!