Using Castle Windsor WcfFacility to create client

2019-02-07 06:14发布

I have created three assemblies. A web site, a WCF service and a contracts assembly that holds the interfaces that the services implement. I would like to use Castle Windsor to create the services for me on the client (website) so that I do not have to have an endpoint in the web.config of the web site for each service that I wish to use.

I would like to look at the contract assembly and get all the service interfaces in a namespace. Right now for every service I have something like the following when registering the components with the container.

container.Register(Component.For<ChannelFactory<IMyService>>().DependsOn(new { endpointConfigurationName = "MyServiceEndpoint" }).LifeStyle.Singleton);
container.Register(Component.For<IMyService>().UsingFactoryMethod((kernel, creationContext) => kernel.Resolve<ChannelFactory<IMyService>>().CreateChannel()).LifeStyle.PerWebRequest);

and in my web.config I have the setup code.

  <system.serviceModel>
      <extensions>
         <behaviorExtensions>
            <add name="AuthToken" type="MyNamespace.Infrastructure.AuthTokenBehavior, MyNamespace.Contracts" />
         </behaviorExtensions>
      </extensions>
      <behaviors>
         <endpointBehaviors>
            <behavior>
               <AuthToken />
            </behavior>
         </endpointBehaviors>
      </behaviors>

      <bindings>
         <wsHttpBinding>
            <binding maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00">
               <readerQuotas maxStringContentLength="2147483647" maxArrayLength="2147483647"></readerQuotas>
               <security mode="None" />
            </binding>
         </wsHttpBinding>
      </bindings>

      <client>
         <endpoint name="MyServiceEndpoint" address="http://someurl/MyService.svc" binding="wsHttpBinding" contract="MyNamespace.Contracts.IMyService"></endpoint>
      </client>
   </system.serviceModel>

I end up with multiple service endpoints that all look almost exactly the same and when we deploy onto clients machines they have to set the address of every endpoint even though the base url is the same for every one.

I would like to have a base url in my web.config that is grabbed through code and then have the services registered with the container using reflection on the contracts assembly. I do need the specialised endpoint behaviour that is in the above config file.

Where so I start? the WcfFacility looks great but the doco is a bit lacking...

1条回答
我只想做你的唯一
2楼-- · 2019-02-07 06:29

I agree the docs for the wcf facility are lacking and that is sad because it is a really great tool and it would be a real shame if people didn't use it because they could not get started, so let me see if I can help you out a little bit if I can...

Let's create a three project application that has:

  1. A class library for shared contracts
  2. A console application that acts as a server
  3. A console application that acts as a client

The idea is that we want to be able to use the service names when we register the services and to share a base URL (I think that is what you were asking and if not, hopefully you can extrapolate from here). So, firstly, the shared contracts simply has this in it (nothing special, normal WCF fare):

[ServiceContract]
public interface IMyService1
{
    [OperationContract]
    void DoSomething();
}

[ServiceContract]
public interface IMyService2
{
    [OperationContract]
    void DoSomethingToo();
}

Now the server console application looks like this, we firstly implement the service contracts (again nothing special there, just classes implementing interfaces) and then just register them all as services (notice no need for any configuration file here and you can change the way you decide what are services etc using all the options that Windsor gives you - my scheme is a bit limited but it gives you an idea):

namespace Services
{
    public class MyService1 : IMyService1
    {
        public void DoSomething()
        {
        }
    }

    public class MyService2 : IMyService2
    {
        public void DoSomethingToo()
        {
        }
    }
}

//... In some other namespace...

class Program
{
    // Console application main
    static void Main()
    {
        // Construct the container, add the facility and then register all
        // the types in the same namespace as the MyService1 implementation
        // as WCF services using the name as the URL (so for example 
        // MyService1 would be http://localhost/MyServices/MyService1) and
        // with the default interface as teh service contract
        var container = new WindsorContainer();            
        container.AddFacility<WcfFacility>(
            f => f.CloseTimeout = TimeSpan.Zero);
        container
            .Register(
                AllTypes
                    .FromThisAssembly()
                    .InSameNamespaceAs<MyService1>()
                    .WithServiceDefaultInterfaces()
                    .Configure(c =>
                               c.Named(c.Implementation.Name)
                                   .AsWcfService(
                                       new DefaultServiceModel()
                                           .AddEndpoints(WcfEndpoint
                                                             .BoundTo(new WSHttpBinding())
                                                             .At(string.Format(
                                                                 "http://localhost/MyServices/{0}",
                                                                 c.Implementation.Name)
                                                             )))));

        // Now just wait for a Q before shutting down
        while (Console.ReadKey().Key != ConsoleKey.Q)
        {
        }
    }
}

And that is the server, now how to consume these services? Well, actually that is quite easy, here is a client console application (it references just the contracts class library):

class Program
{
    static void Main()
    {
        // Create the container, add the facilty and then use all the
        // interfaces in the same namespace as IMyService1 in the assembly 
        // that contains the aforementioned namesapce as WCF client proxies
        IWindsorContainer container = new WindsorContainer();

        container.AddFacility<WcfFacility>(
            f => f.CloseTimeout = TimeSpan.Zero);

        container
            .Register(
                Types
                    .FromAssemblyContaining<IMyService1>()
                    .InSameNamespaceAs<IMyService1>()
                    .Configure(
                        c => c.Named(c.Implementation.Name)
                                 .AsWcfClient(new DefaultClientModel
                                                  {
                                                      Endpoint = WcfEndpoint
                                                          .BoundTo(new WSHttpBinding())
                                                          .At(string.Format(
                                                              "http://localhost/MyServices/{0}",
                                                              c.Name.Substring(1)))
                                                  })));

        // Now we just resolve them from the container and call an operation
        // to test it - of course, now they are in the container you can get
        // hold of them just like any other Castle registered component
        var service1 = container.Resolve<IMyService1>();
        service1.DoSomething();

        var service2 = container.Resolve<IMyService2>();
        service2.DoSomethingToo();
    }
}

That's it - hopefully this will get you started (I find that experimenting and using the intellisense usually gets me where I need to go). I showed you both the service and client sides but you can just use one or the other if you prefer.

You should be able to see where the binding is configured and how I have gone about constructing the URLs so in your case you could easily just pluck your base URL from a configuration file or whatever you want to do.

One last thing to mention is that you can add your custom endpoint behaviour by adding it as an extension to the endpoint, so in the client example you would have something like this:

Endpoint = WcfEndpoint
    .BoundTo(new WSHttpBinding())
    .At(string.Format("http://localhost/MyServices/{0}", c.Name.Substring(1)))
    .AddExtensions(new AuthTokenBehavior())
查看更多
登录 后发表回答