Partager via


Deciding local-or-remote at runtime

I've seen a number of Web service implementations where there was a possibility that when deployed, the Web service and its client could end up on the same machine. This is most common when the client is a Web (HTML) application. In these scenarios, you certainly wouldn't want your Web application to spin up the TCP stack when it could just call into the local assemblies. But how can the Web application automatically handle both scenarios with the least amount of effort? Hmm, that would make a good interview question ;-) Well, I thought it was an interesting questions so I whipped up this idea for how to handle it.

Keep in mind, when developing Web services, a common best practice is to separate the Web service project from the project that contains the actual business logic. The actual WebMethods should only contain a couple of lines code, which call into the business layer.

To get this started, create a CLR interface that defines the operations on a service. This interface will be implemented by the actual library class that performs the work, and the generated proxy class. Then we can use a simple factory pattern, and the configuration file can be used to create the correct implementation of the interface. This is what I mean.

 // Class library
namespace CalculatorLib
{
  public interface ICalculator
  {
    int Add( int op1, int op2 );
    int Subtract( int op1, int op2 );
    int Multiply( int op1, int op2 );
    int Divide( int op1, int op2 );
  }
 
  public class Calculator : ICalculator
  {
    public int Add( int op1, int op2 )
    {
      return op1 + op2;
    }
    ...
  }
}
 // Client app
namespace CloseAndFarClient
{
  public class ServiceFactory
  {
    public static ICalculator GetCalculatorService()
    {
      ICalculator service = null;
      string svcassm = ConfigurationSettings.AppSettings["ServiceAssembly"];
      string svctype = ConfigurationSettings.AppSettings["ServiceType"];
 
      Assembly assm = Assembly.Load( svcassm );
      service = (ICalculator)assm.CreateInstance( svctype, true );
      return service;
    }
  }
 
  class EntryPoint
  {
    static void Main(string[] args)
    {
      ICalculator calc = ServiceFactory.GetCalculatorService();
      int result = calc.Add( 24, 4 );
    }
  }
}
 <!-- Client's app.config -->
<configuration>
  <appSettings>
      
    <!-- Local -->
    <!-- <add key="ServiceAssembly" value="CalculatorLib" /> -->
    <!-- <add key="ServiceType" value="CalculatorLib.Calculator" /> -->
            
    <!-- Remote (proxy) -->
    <add key="ServiceAssembly" value="CloseAndFarClient" />
    <add key="ServiceType" value="CloseAndFarClient.localhost.CalculatorService" />
            
  </appSettings>
</configuration>

Naturally, the client and the service projects will have references to the class library project. The only thing that isn't shown here is the "using" statement added to the generated proxy and the addition of the interface to the generated proxy's class declaration. It's unfortunate changes have to be made to the proxy, but at least the changes are very minor. Certainly this approach won't support the needs of all such applications ... it's just one approach.

BTW, Christian also tell me WsContractFirst 0.5 will support the creation of interfaces. Yeah, that's right, THE Christian Weyer sends me personal emails, it's true!

 

Comments