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

Monday, November 16, 2009

INotifyPropertyChanged and databinding in Silverlight

In the previous article, I wrote about IronPython and databinding in WPF applications. The last note was it does not work in Silverlight. Thanks to Shri Borde (IronPython/IronRuby dev lead) who updated clrtype module, the note is not true any more.

Let's create a small Silverlight app in IronPython from scratch. I use IronPython 2.6 RC2. Follow http://lists.ironpython.com/pipermail/users-ironpython.com/2009-October/011543.html to avoid bugs in IronPython 2.6 RC2.

Create a new project:

C:\IronPython-2.6\Silverlight\script\sl.bat python BindTest

Change the app.xaml to

<usercontrol x:Class="System.Windows.Controls.UserControl"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <stackpanel x:Name="DataPanel"
        Orientation="Horizontal">
        <textblock Text="Size"/>
        <textblock Text="{Binding size}"/>
        <textbox x:Name="tbSize"
            Text="{Binding size, Mode=TwoWay}" />
        <button x:Name="Button"
            Content="Set Initial Value"></Button>
    </StackPanel>
</UserControl>

The difference comparing to WPF version is we have to specify binding mode because the default mode for TextBox in Silverlight is OneWay. And we cannot use UpdateSourceTrigger=PropertyChanged because Silverlight does not have such UpdateSourceTrigger.

Silverlight binding is limited comparing to WPF. That's why we have to create CLR properties to Silverlight be able to see them. DevHawk has a nice serie about clr types on his blog.

Creating CLR property with clrtype.py is easy. Shri described it on IronPython mailing list. Because I use my enhanced @notify_property decorator, I can write:

class ViewModel(NotifyPropertyChangedBase):
    __metaclass__ = clrtype.ClrClass
    _clrnamespace = "BindTest"
    
    def __init__(self):
        super(ViewModel, self).__init__()
        # must be string to two-way binding work
        # correctly
        self.size = '10'

    @notify_property
    @clrtype.returns(str)
    def size(self):
        return self._size

    @size.setter
    @clrtype.accepts(str)
    def size(self, value):
        self._size = value
        print 'Size changed to %r' % self.size

The NotifyPropertyChangedBase class is the same as for WPF version. The enhanced @notify_property decorator calls automatically clrtype.accepts() for getter and clrtype.returns() for setter so we do not need to call them manually for every property:

class notify_property(property):

    def __init__(self, getter):
        def newgetter(slf):
            #return None when the property does not
            # exist yet
            try:
                return getter(slf)
            except AttributeError:
                return None
        getter = clrtype.accepts()(getter)
        clrtype.propagate_attributes(getter, newgetter)
        super(notify_property, self).__init__(newgetter)

    def setter(self, setter):
        def newsetter(slf, newvalue):
            # do not change value if the new value is
            # the same, trigger PropertyChanged event
            # when value changes
            oldvalue = self.fget(slf)
            if oldvalue != newvalue:
                setter(slf, newvalue)
                slf.OnPropertyChanged(setter.__name__)
        setter = clrtype.returns()(setter)
        clrtype.propagate_attributes(setter, newsetter)
        return property(
            fget=self.fget,
            fset=newsetter,
            fdel=self.fdel,
            doc=self.__doc__)

Then App looks similarly to the WPF counterpart:

class App:
    def __init__(self):
        self._vm = ViewModel()
        self.root = Application.Current.LoadRootVisual(
                UserControl(), "app.xaml")
        self.DataPanel.DataContext = self._vm
        self.Button.Click += self.OnClick

    def OnClick(self, sender, event):
        # must be string to two-way binding work
        # correctly
        self._vm.size = '10'

    def __getattr__(self, name):
        # provides easy access to XAML elements
        # (e.g. self.Button)
        return self.root.FindName(name)

a = App()

Run Chiron with the BindTest app

C:\IronPython-2.6\Silverlight\script\sl.bat python BindTest

and check the application in the browser on http://localhost:2060/index.html.

Whatever you write into the text box appears in the label in front of the text box when the text box loses the focus. When you click the button, the value is reseted. You can also change the value from the console:

a._vm.size= '3'

Download app.xaml and app.py. You also need clrtype.py and pyevent.py (from C:\IronPython-2.6\Tutorial\pyevent.py) in the BindTest folder.

Wednesday, November 11, 2009

INotifyPropertyChanged and databinding in IronPython WPF

INotifyPropertyChanged is important interface for building WPF or Silverlight applications using M-V-VM concept (MSDN article).

In simple language, you have a Model which provides access to your data (e.g in database, files, web, etc.). Then you have a ViewModel that access data in the Model via Model's interface and provides data to a View which is XAML file with UI layout. Linkage between ViewModel and View is done by binding that utilizes PropertyChanged event to properly update all UI elements.

I have found two examples how to implement INotifyPropertyChanged interface in IronPython. The first one uses __setattr__ hook. Personally, I don't like it - it is not clear and easily readable code. The second one is better because it uses properties. But you have to write self.OnPropertyChanged("my_property_name") for every property. Not ideal.

That's why I sit down and write a notify_property:

class notify_property(property):

    def __init__(self, getter):
        def newgetter(slf):
            try:
                return getter(slf)
            except AttributeError:
                return None
        super(notify_property, self).__init__(newgetter)

    def setter(self, setter):
        def newsetter(slf, newvalue):
            oldvalue = self.fget(slf)
            if oldvalue != newvalue:
                setter(slf, newvalue)
                slf.OnPropertyChanged(setter.__name__)
        return property(
            fget=self.fget,
            fset=newsetter,
            fdel=self.fdel,
            doc=self.__doc__)

With this subclass I aimed several goals:

  • usage simple as @property decorator (actualy no other usage is possible as I implemented __init__ with just one parameter that must be the getter)
  • when property is on yet defined, it should return None
  • automaticaly handle PropertyChanged event when and only when property has changed

We also need to implement INotifyPropertyChanged interface in IronPython so we can call OnPropertyChanged method. See Overiding events in IronPython\Doc\dotnet-integration.rst to understand what means add_ and remove_ methods.

class NotifyPropertyChangedBase(INotifyPropertyChanged):
    PropertyChanged = None

    def __init__(self):
        self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()

    def add_PropertyChanged(self, value):
        self.PropertyChanged += value

    def remove_PropertyChanged(self, value):
        self.PropertyChanged -= value

    def OnPropertyChanged(self, propertyName):
        if self.PropertyChanged is not None:
            self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))

Now we can implement a simple class with properties with change notification:

class DataObject(NotifyPropertyChangedBase):
    
    def __init__(self, size):
        super(DataObject, self).__init__()
        self.size = size

    @notify_property
    def size(self):
        return self._size

    @size.setter
    def size(self, value):
        self._size = value

You can see it is very easy - just like any other property in Python.

Finaly, let's put all together. When you run the code below, it shows a window with label, textbox and button. The label is updated as you type into the textbox and a message is written into the console as well. By default, the textbox is updated when it looses focus, so I have to change UpdateSourceTrigger to PropertyChanged. When you click the button, the value is reset. Note if you use type int instead of string the two-way bindign would not work.

notpropwpf.py

import clr
import System
clr.AddReference('PresentationFramework')
clr.AddReference('PresentationCore')

from System.Windows.Markup import XamlReader
from System.Windows import Application, Window
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
import pyevent

XAML_str = """<window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="250" Height="62">
    <stackpanel x:Name="DataPanel" Orientation="Horizontal">
        <label Content="Size"/>
        <label Content="{Binding size}"/>
        <textbox x:Name="tbSize" Text="{Binding size, UpdateSourceTrigger=PropertyChanged}" />
        <button x:Name="Button" Content="Set Initial Value"></Button>
    </StackPanel>
</Window>"""

class notify_property(property):

    def __init__(self, getter):
        def newgetter(slf):
            #return None when the property does not exist yet
            try:
                return getter(slf)
            except AttributeError:
                return None
        super(notify_property, self).__init__(newgetter)

    def setter(self, setter):
        def newsetter(slf, newvalue):
            # do not change value if the new value is the same
            # trigger PropertyChanged event when value changes
            oldvalue = self.fget(slf)
            if oldvalue != newvalue:
                setter(slf, newvalue)
                slf.OnPropertyChanged(setter.__name__)
        return property(
            fget=self.fget,
            fset=newsetter,
            fdel=self.fdel,
            doc=self.__doc__)

class NotifyPropertyChangedBase(INotifyPropertyChanged):
    PropertyChanged = None

    def __init__(self):
        self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()

    def add_PropertyChanged(self, value):
        self.PropertyChanged += value

    def remove_PropertyChanged(self, value):
        self.PropertyChanged -= value

    def OnPropertyChanged(self, propertyName):
        if self.PropertyChanged is not None:
            self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))

class ViewModel(NotifyPropertyChangedBase):
    
    def __init__(self):
        super(ViewModel, self).__init__()
        # must be string to two-way binding work correctly
        self.size = '10'

    @notify_property
    def size(self):
        return self._size

    @size.setter
    def size(self, value):
        self._size = value
        print 'Size changed to %r' % self.size

class TestWPF(object):

    def __init__(self):
        self._vm = ViewModel()
        self.root = XamlReader.Parse(XAML_str)
        self.DataPanel.DataContext = self._vm
        self.Button.Click += self.OnClick
        
    def OnClick(self, sender, event):
        # must be string to two-way binding work correctly
        self._vm.size = '10'

    def __getattr__(self, name):
        # provides easy access to XAML elements (e.g. self.Button)
        return self.root.FindName(name)

tw = TestWPF()
app = Application()
app.Run(tw.root)

You need pyevent.py from IronPython\Tutorial\ folder to run to example.

Unfortunately, this does not work in Silverlight, probably because the property is not .NET field. See next atricle for Silverlight version.