Changing service reference configuration in the runtine

Updating service reference without restarting your applcation

Using web services in C# and in general in .NET based applications is really easy. All you need is a service URL and Visual Studio will generate both proxy classes for service communication and configuration for web service endpoints and bindings. For the demo purposes I created a simple console application which uses the forecast web service which WSDL is available at http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL

I simply add a service reference from Visual Studio and it created proxy classes along with service endpoint and binding configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="WeatherSoap" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://wsf.cdyne.com/WeatherWS/Weather.asmx"
                binding="basicHttpBinding" bindingConfiguration="WeatherSoap"
                contract="WeatherService.WeatherSoap" name="WeatherSoap" />
        </client>
    </system.serviceModel>
</configuration>
    

Since I have proxy classes generated I can simply create instance and start using the service in my code:

using System;using System.Collections.Generic;using System.Configuration;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Reflection;using System.IO;using System.ServiceModel.Configuration;namespace ServiceReferenceConfigTest{class Program{static void Main(string[] args){/* Weather service WSDL: http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL */while (true){Console.WriteLine("Enter US ZIP code for forecast:");var zip = Console.ReadLine();Console.Clear();
                var weatherServiceClient = new WeatherService.WeatherSoapClient();
                var response = weatherServiceClient.GetCityForecastByZIP(zip);
                Console.WriteLine("Forecast for: {0}", response.City);
                foreach (var forecast in response.ForecastResult)
                {
                    Console.WriteLine("Date: {0}", forecast.Date);
                    Console.WriteLine("Temperature Daytime High: {0}", forecast.Temperatures.DaytimeHigh);
                    Console.WriteLine("Temperature Morning High: {0}", forecast.Temperatures.MorningLow);
                    Console.WriteLine(forecast.Desciption);
                    Console.WriteLine(string.Concat(Enumerable.Repeat("*", 30)));
                }

            }
        }
    }
}

    

To demonstrate that this is the case with keeping service configuration in app.config I added a infinite loop where you are asked to enter ZIP code to check the weather, simulation windows service long running instance. In case you change the WSDL url during the runtime and initiate service request by entering ZIP code value, you will not get any exception, simply because your changes are not picked up from the config. They are initially loaded on start and they remain loaded on the AppDomain level regardless of the changes in app.config.

The thing is that service configuration is stored in application config file and therefore loaded by ConfigurationManager which is static class and therefore loaded at AppDomain level. This means that it cannot be changed (at least not without using reflection) during the runtime. Basically you are stuck with service reference configuration until your application is restarted.

For web applications this is not a big issue since web application is restarted by itself on every change of web.config. The problem is for long running applications or windows services. In this case you need to manually restart your windows service in order to pick up new service reference configuration. This might be tricky as sometime you might not have access to do this or restarting the service may cause some cascade issues on the applications or other services depending on the one you plan to restart.

For this case ideally would be that you keep you configuration outside the app.config file of the application. I created a separate config file named WeatherService.config and moved service configuration to it from app.config.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="WeatherSoap" />
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://wsf.cdyne.com/WeatherWS/Weather-WRONG.asmx"
          binding="basicHttpBinding" bindingConfiguration="WeatherSoap"
          contract="WeatherService.WeatherSoap" name="WeatherSoap" />
    </client>
  </system.serviceModel>
</configuration>
    

Before you create instance of the service client you need to load this config and this changes our code a bit.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;
using System.ServiceModel.Configuration;

namespace ServiceReferenceConfigTest
{
    class Program
    {
        static void Main(string[] args)
        {
            /* Weather service WSDL: http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL */
            while (true)
            {
                Console.WriteLine("Enter US ZIP code for forecast:");
                var zip = Console.ReadLine();
                Console.Clear();

                string serviceConfigPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "WeatherService.config");
                Configuration serviceConfiguration = ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap { ExeConfigFilename = serviceConfigPath }, ConfigurationUserLevel.None);
                ConfigurationChannelFactory<WeatherService.WeatherSoap> serviceChannelFactory = new ConfigurationChannelFactory<WeatherService.WeatherSoap>("WeatherSoap", serviceConfiguration, null);
                var weatherServiceClient = serviceChannelFactory.CreateChannel();

                //var weatherServiceClient = new WeatherService.WeatherSoapClient();
                var response = weatherServiceClient.GetCityForecastByZIP(zip);
                Console.WriteLine("Forecast for: {0}", response.City);
                foreach (var forecast in response.ForecastResult)
                {
                    Console.WriteLine("Date: {0}", forecast.Date);
                    Console.WriteLine("Temperature Daytime High: {0}", forecast.Temperatures.DaytimeHigh);
                    Console.WriteLine("Temperature Morning High: {0}", forecast.Temperatures.MorningLow);
                    Console.WriteLine(forecast.Desciption);
                    Console.WriteLine(string.Concat(Enumerable.Repeat("*", 30)));
                }

            }
        }
    }
}

    

To demostrate that this is working and that configuration is loaded every time, we can change the service WSDL url in WeatherService.config file and enter new ZIP code for initiating request for forecast data. In this case you get exception thrown by your application which proves that new config value is applied.

Invalid Wsdl

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article