Showing posts with label wcf. Show all posts
Showing posts with label wcf. Show all posts

Sunday, November 22, 2009

WCF Service in pure IronPython with config file

I was wrong when I wrote in the last post that the IronPython service cannot be saved into an assembly. It can. Which opens a way to use .config file to configure the service.

This is a simple config file for the service:

ConfigService.exe.config

<?xml version="1.0"?>
<configuration>
<system.serviceModel>
    <services>
      <service name="ConfigService.myService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:9000/myWcfService"/>
          </baseAddresses>
        </host>
        <endpoint address=""
            binding="basicHttpBinding"
            contract="TestServiceInterface.ImyService"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>

The interface is the same as in the previous version. The only difference in the service to the previous version is in the ServiceHost initialization - we omit the service configuration parameters because they are in the .config file. I also changed the clr namespace:

ConfigService.py

import clr
import clrtype
clr.AddReference('System.ServiceModel')
from TestServiceInterface import ImyService
from System import Console, Uri
from System.ServiceModel import (ServiceHost,
        BasicHttpBinding, ServiceBehaviorAttribute,
        InstanceContextMode)

class myService(ImyService):
    __metaclass__ = clrtype.ClrClass
    _clrnamespace = "ConfigService"
    _clrclassattribs = [ServiceBehaviorAttribute]

    def GetData(self, value):
        return "IronPython config service: You entered: %s" % value

sh = ServiceHost(myService)
sh.Open()
Console.WriteLine("Press  to terminate\n")
Console.ReadLine()
sh.Close()

If you want to run this script, you must save the ConfigService.exe.config as ipy.exe.config to the folder with the IronPython interpreter ipy.exe.

To save the service as an assembly, run the following command:

C:\IronPython-2.6\ipy.exe C:\IronPython-2.6\Tools\Scripts\pyc.py  /out:ConfigService /target:exe /main:ConfigService.py clrtype.py TestServiceInterface.py

The ConfigService.dll and ConfigService.exe are created. Add the ConfigService.exe.config to the same folder and when you run ConfigService.exe, the service starts. Note you also need all IronPython .dlls in the same folder.

You can adjust the .config file to expose a MEX endpoint (ConfigService.mex.exe.config) but I don't see a big point in it because svcutil.exe generates C# or VB code. Anyway - here are the generated files: myService.cs, myService.config

You can run the old TestClient.py and it will successfully retrieve value from the service. But the old TestClient.py does not use .config file. If we want to use .config file for the client, we have to rewrite the WCF client. First, here is the sample client .config file:

ConfigClient.exe.config

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <client>
        <endpoint address="http://localhost:9000/myWcfService"
            binding="basicHttpBinding"
            contract="TestServiceInterface.ImyService"/>
    </client>
  </system.serviceModel>
</configuration>

You can see it is very similar to the generated one. We do not specify details of the binding but we specify full name of the contract interface.

If you check the generated client proxy class by svcutil.exe, you see it is based on System.ServiceModel.ClientBase and the interface ImyService. There are some empty constructors and all methods from ImyService interface return result of the same method name call on Channel property. That's why I have created WcfClient helper function. The client source then looks like the following:

ConfigService.py

import clr
clr.AddReference('System.ServiceModel')
import System.ServiceModel
from TestServiceInterface import ImyService

def WcfClient(interface):

    class WcfClientBase(System.ServiceModel.ClientBase[interface]):

        def __getattr__(self, name):
            # if name is method from interface, return the Channel method
            if name in (k[0] for k in interface.emitted_methods.keys()):
                return getattr(self.Channel, name)

    return WcfClientBase()

wcfcli = WcfClient(ImyService)
print "WCF config client returned:\n%s" % wcfcli.GetData(11)

The WcfClient helper function returns an instance of class based on System.ServiceModel.ClientBase. The __getattr__ checks if the requested attribute name is interface method and if so, it returns the Channel's method with the same name. Which is the same behavior as the generated client proxy class in couple of lines of code.

To save the client as an assembly, run the following command:

C:\IronPython-2.6\ipy.exe C:\IronPython-2.6\Tools\Scripts\pyc.py /out:ConfigClient /target:exe /main:ConfigClient.py clrtype.py TestServiceInterface.py

The ConfigClient.dll and ConfigClient.exe are created. Add the ConfigClient.exe.config to the same folder and when you run ConfigClient.exe, the client calls the service.

Having this I think there is only a small step to use the IronPython WCF services in IIS. Unfortunately, I do not know how to do it...

Tuesday, November 17, 2009

WCF Service in pure IronPython

I wrote about implementing WCF service in IronPython a couple of weeks ago. Meanwhile I pushed Shri a little bit with the clrtype.py and he has implemented ClrInterface metaclass there so we can create the whole WCF service in IronPython now.

The IronPython interface implementation is straightforward:

TestServiceInterface.py

import clr
import clrtype
clr.AddReference('System.ServiceModel')
from System.ServiceModel import (
        ServiceContractAttribute,
        OperationContractAttribute)
OperationContract = clrtype.attribute(
        OperationContractAttribute)

class ImyService(object):
    __metaclass__ = clrtype.ClrInterface
    _clrnamespace = "TestServiceInterface"
    _clrclassattribs = [ServiceContractAttribute]

    @OperationContract()
    @clrtype.accepts(int)
    @clrtype.returns(str)
    def GetData(self, value):
        raise RuntimeError("this should not get called")

Also switching from C# interface to IronPython interface is easy:

TestService.py

import clr
import clrtype
clr.AddReference('System.ServiceModel')
from TestServiceInterface import ImyService
from System import Console, Uri
from System.ServiceModel import (ServiceHost,
        BasicHttpBinding, ServiceBehaviorAttribute,
        InstanceContextMode)

class myService(ImyService):
    __metaclass__ = clrtype.ClrClass
    _clrnamespace = "myWcfService"
    _clrclassattribs = [ServiceBehaviorAttribute]

    def GetData(self, value):
        return "IronPython: You entered: %s" % value

sh = ServiceHost(myService, Uri(
        "http://localhost:9000/myWcfService"))
sh.AddServiceEndpoint(clr.GetClrType(ImyService),
        BasicHttpBinding(), "")
sh.Open()
Console.WriteLine("Press  to terminate\n")
Console.ReadLine()
sh.Close()

Notice that we call ServiceHost with myService which is the type and not the instance of our service. Because of this, the ServiceBehavior attribute does not need to have InstanceContextMode.Single parameter.

Finally, here is the test client:

TestClient.py

import clr
clr.AddReference('System.ServiceModel')
import System.ServiceModel
from TestServiceInterface import ImyService

mycf = System.ServiceModel.ChannelFactory[ImyService](
        System.ServiceModel.BasicHttpBinding(),
        System.ServiceModel.EndpointAddress(
            "http://localhost:9000/myWcfService"))
wcfcli = mycf.CreateChannel()
print "WCF service returned:\n%s" % wcfcli.GetData(11)

The disadvantage of having just a single service instance is gone, the harder configuration remains. One new disadvantage can be it is not possible (yet) to compile the interface and save it to disk nor use it from other .NET languages.

Edit 22. 11. 2009: See WCF Service in pure IronPython with config file

Friday, October 30, 2009

WCF Service in IronPython

Edit 17. 11. 2009: See the article about WCF service in pure IronPython.

Until IronPython 2.6, it was not possible to create WCF service host in pure IronPython. The closest way was to create stub in C# and subclass it in IronPython or create the whole service in C# and run it from IronPython. It is now much simpler with IronPython 2.6 although you still have to write a little C# code.

Simple WCF service implemented in C# looks like this:

TestServiceInterface.cs

using System;
using System.ServiceModel;

namespace TestServiceInterface
{
    [ServiceContract]
    public interface ImyService
    {
        [OperationContract]
        string GetData(int value);
    }
}

TestService.cs

using System;
using System.ServiceModel;
using TestServiceInterface;

namespace myWcfService
{
    public class myService : ImyService
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }
    }

    public class mySvc
    {
        public static void Main()
        {
            ServiceHost sh = new ServiceHost(
                typeof(myService),
                new Uri("http://localhost:9000/myWcfService"));
            sh.AddServiceEndpoint(
                typeof(ImyService),
                new BasicHttpBinding(),
                "");
            sh.Open();
            Console.WriteLine("Press  to terminate\n");
            Console.ReadLine();
            sh.Close();
        }
    }
}

You build it:

csc /target:library TestServiceInterface.cs
csc /r:TestServiceInterface.dll TestService.cs

The reason I put TestServiceInterface into separate file is that you cannot create interfaces in IronPython. So this is the only part written in C# when implementig WCF service in IronPython.

The implementation then looks like this:

TestService.py

import clr
import clrtype
clr.AddReference('System.ServiceModel')
clr.AddReference('TestServiceInterface')
from TestServiceInterface import ImyService
from System import Console, Uri
from System.ServiceModel import (ServiceHost, BasicHttpBinding,
        ServiceBehaviorAttribute, InstanceContextMode)
ServiceBehavior = clrtype.attribute(ServiceBehaviorAttribute)

class myService(ImyService):
    __metaclass__ = clrtype.ClrMetaclass
    _clrnamespace = "myWcfService"
    _clrclassattribs = [
            ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]

    def GetData(self, value):
        return "IronPython: You entered: %s" % value

sh = ServiceHost(
        myService(),
        Uri("http://localhost:9000/myWcfService")
    )
sh.AddServiceEndpoint(
        clr.GetClrType(ImyService),
        BasicHttpBinding(),
        "")
sh.Open()
Console.WriteLine("Press  to terminate\n")
Console.ReadLine()
sh.Close()

The myService class must have InstanceContextMode.Single ServiceBehavior attribute because we are passing service instance to the ServiceHost constructor. This is done via new __clrtype__ metaclass. See the error if we don't use the attribute. I was not able to pass type into the ServiceHost constructor.

To test the service, you can use C# or IronPython client implementation:

TestClient.cs

using System;
using System.ServiceModel;
using TestServiceInterface;

namespace myWcfClient
{
    public class cli
    {
        public static void Main()
        {
   ChannelFactory mycf = new ChannelFactory(
     new BasicHttpBinding(),
        new EndpointAddress("http://localhost:9000/myWcfService"));
   ImyService wcfcli = mycf.CreateChannel();
   Console.WriteLine("Calling GetData(33) returns:\n{0}", wcfcli.GetData(33));
        }
    }
}

TestClient.py

import clr
clr.AddReference('System.ServiceModel')
import System.ServiceModel
clr.AddReference('TestServiceInterface')
from TestServiceInterface import ImyService

mycf = System.ServiceModel.ChannelFactory[ImyService](
        System.ServiceModel.BasicHttpBinding(),
        System.ServiceModel.EndpointAddress("http://localhost:9000/myWcfService"))
wcfcli = mycf.CreateChannel()
print "WCF service returned:\n%s" % wcfcli.GetData(11)

Disadvantages:

  • You can have only single instance of the service because you are passing the service instance instead of service type.
  • You cannot easily use .config file to configure your service.