DCSIMG
berniea

berniea

Hit The Road Jack...

Well, its time to go on a new road again, after two fulfilling years at Sela I've joined up with some very talented people to fund a new road in our own new company - CodeValue.
We are planning to focus on new interesting products in the cloud and mobile areas. Also since we don't wish to be like a startup craving for money, we will continue to provide quality service in consulting, development and projects outsourcing. We have an excellent group of people at CodeValue which can do just about everything you can imagine. Among them you'll find Alon FliessShay FriedmanJosh Reuven, Ariel Ben-HoreshEran StillerAmir ZuckerEli Arbel, and more...

For myself I'm feeling like a part of an amazing Dream Team of technology experts but most important also great people.

 
I really would like to thank Oren Eini aka Ayende for his support and great tips on going on this new road, and also to wish Sasha Goldshtein the best of luck in his new position as Sela's CTO.

WCF 4.0 Routing with WAS

A few days ago I wanted to build a simple message broker so I’ve decided to tryout the new routing feature WCF 4.0 is offering. When doing so I’ve encountered two problems:

The first problem was that I needed to figure out a way on how to do the routing when hosting the services under WAS (Windows Process Activation Service) since I’ve been using netTcpBinding for my services.

And the second problem was how to do XPath Message Filtering on a service operation parameters (not a Data Contract).

I’ve googled a bit on WCF 4.0 Routing but I didn’t find any samples on routing when hosting WCF in WAS. And as regards to my second problem I only came up with samples of Message Filtering only on a Data Contract.

Let’s see how to resolve this problems, to demonstrate the resolutions I’ve created a small test project called WcfTestRouting as my test case project.

First lets create the service contract and the services.

namespace WcfTestRouting
{
  [ServiceContract]
  public interface IAccountManager
  {
    [OperationContract]
    WithrawResult Withdraw(WithrawRequest request);

    [OperationContract]
    WithrawResult AnotherWithdraw(int accountId, double amount);
  }
}

As you can see I’ve added two operation contracts: one receiving a Data Contract and the other receiving just primitive method parameters.

Now I’ve added two services which I wanted to route between.

The Account Manager Service (AccountManager.svc):

namespace WcfTestRouting
{
  public class AccountManager : IAccountManager
  {
    public WithrawResult Withdraw(WithrawRequest request)
    {
      var result = new WithrawResult
      {
        Output = WithdrawalOutput.Failed,
        Remark = "Regular"
      };

      return result;
    }

    public WithrawResult AnotherWithdraw(int accountId, double amount)
    {
      var result = new WithrawResult
      {
        Output = WithdrawalOutput.Failed,
        Remark = "Regular"
      };

      return result;
    }
  }
}

And the Account Manager VIP Service (AccountManagerVip.svc):

namespace WcfTestRouting
{
  public class AccountManagerVip : IAccountManager
  {
    public WithrawResult Withdraw(WithrawRequest request)
    {
      var result = new WithrawResult
      {
        Output = WithdrawalOutput.Successful,
        Remark = "Vip"
      };

      return result;
    }

    public WithrawResult AnotherWithdraw(int accountId, double amount)
    {
      var result = new WithrawResult
      {
        Output = WithdrawalOutput.Successful,
        Remark = "Vip"
      };

      return result;
    }
  }
}

The difference between them is that the first one returns: Failed, “Regular” and the second one returns: Successful, “Vip”;

In order to deploy the services in WAS we need to do the following steps:

First, add the following service element under the services element in your web.config (you’ll need one for each service of course):

 

<service name="WcfTestRouting.AccountManager" behaviorConfiguration="GeneralBehavior">
  <host>
    <baseAddresses>
      <add baseAddress="net.tcp://localhost:808/WcfTestRouting"/>
    </baseAddresses>
  </host>
  <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
  <endpoint name="AccountManagerNetTcpEndpoint"
     address=""
     binding="netTcpBinding"
     bindingConfiguration="Tcp1Binding"
     contract="WcfTestRouting.IAccountManager" />
</service>

 

Second, configure your Web Application to allow net.tcp bindings, choose your Web Application in the IIS management console and click on the advanced settings Action:

image

You’ll receive the following window:

image

Make sure that under Behavior / Enabled Protocols, you have also a definition for “net.tcp” protocol.

Now for the routing configuration:

First let’s add the two client endpoints in the web.config file:

<client>
  <endpoint name="AccountManagerClientEndPoint"
            address="net.tcp://localhost/WcfTestRouting/AccountManager.svc"
            binding="netTcpBinding"
            bindingConfiguration="Tcp1Binding"
            contract="*" />

  <endpoint name="AccountManagerVipClientEndPoint"
            address="net.tcp://localhost/WcfTestRouting/AccountManagerVip.svc"
            binding="netTcpBinding"
            bindingConfiguration="Tcp1Binding"
            contract="*" />
</client>

Then add the routing definitions:

<routing>
      <namespaceTable>
        <add prefix="custom" namespace="http://schemas.datacontract.org/2004/07/WcfTestRouting"/>
        <add prefix="temp" namespace="http://tempuri.org/"/>
      </namespaceTable>
      <filters>
        <filter name="AccountManager_Withdraw" filterType="XPath" filterData="//custom:AccountId = 1"/>
        <filter name="AccountManagerVip_Withdraw" filterType="XPath" filterData="//custom:AccountId = 2"/>
        <filter name="AccountManager_AnotherWithdraw" filterType="XPath" filterData="//temp:accountId = 1"/>
        <filter name="AccountManagerVip_AnotherWithdraw" filterType="XPath" filterData="//temp:accountId = 2"/>
      </filters>
      <filterTables>
        <filterTable name="filterTable1">
          <add filterName="AccountManager_Withdraw" endpointName="AccountManagerClientEndPoint" priority="0"/>
          <add filterName="AccountManagerVip_Withdraw" endpointName="AccountManagerVipClientEndPoint" priority="0"/>
          <add filterName="AccountManager_AnotherWithdraw" endpointName="AccountManagerClientEndPoint" priority="0"/>
          <add filterName="AccountManagerVip_AnotherWithdraw" endpointName="AccountManagerVipClientEndPoint" priority="0"/>
        </filterTable>
      </filterTables>
    </routing>

After that, we need to add the routing service. Since the routing is done using System.ServiceModel.Routing.RoutingService and we are hosting the service under WAS, you need to expose it as a valid URI in your Web Application hosting the other WCF services (This can be done also in a separated Web Application). In order to do that add a blank svc file called AccountManagerRoutingService.svc to your Web Application, you can do that by simply adding a text file and changing the extension to svc.

Open the file and add the following:

 

<%@ ServiceHost Language="C#" Debug="true" Service="System.ServiceModel.Routing.RoutingService, System.ServiceModel.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"%>

This will create the route service with a valid URI you need in order to be able to do the routing in your WCF application.

Then add a service definition in your web.config as we’ve done with the previous services:

 

<service name="System.ServiceModel.Routing.RoutingService" behaviorConfiguration="RoutingBehavior" >
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:808/WcfTestRouting"/>
          </baseAddresses>
        </host>
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
        <endpoint name="RoutingServiceNetTcpEndpoint"
           address=""
           binding="netTcpBinding"
           bindingConfiguration="Tcp1Binding"
           contract="System.ServiceModel.Routing.IRequestReplyRouter" />
      </service>

 

Don’t forget to add the following line in the service behavior configuration:

<routing routeOnHeadersOnly="false" filterTableName="filterTable1" />

This will allow the routing to be performed also by inspecting the message body and will associate the route service it with the desired filter table.

In order to test this I’ve also written a Console Application consuming the service. Just note that when adding a Service Reference don’t add it to the Routing Service, since the routing service implements another service contract (IRequestReplyRouter) we need to add the Service Reference to one of the original services implementing our service contract (IAccountManager).

image

A resulting test would look something like this:

using (var client = new AccountManagerClient())
      {
        var request = new WithrawRequest
                        {
                          AccountId = 1,
                          Amount = 10
                        };

        var withrawResult = client.Withdraw(request);

        Console.WriteLine(withrawResult.Remark);
        Console.WriteLine(withrawResult.Output);
      }

 

In order to make this work against the Router Service we need to change the endpoint address in the clients app.conifg file. Therefore the endpoint address should be net.tcp://localhost/WcfTestRouting/AccountManagerRoutingService.svc and the client’s binding would look like this:

<client>
  <endpoint
    address="net.tcp://localhost/WcfTestRouting/AccountManagerRoutingService.svc"
    binding="netTcpBinding"
    bindingConfiguration="Tcp1Binding"
    contract="AccountManagerSrvRef.IAccountManager"
    name="RoutingServiceNetTcpEndpoint" />
</client>

Note that the client is using to the IAccountManager Service Contract.

Now when applying a different account id – the service will be routed as configured in the routing section.

The second problem was how to apply filtering when using primitive parameters in the service operation. For example when using the following operation:

public WithrawResult AnotherWithdraw(int accountId, double amount)
{
  var result = new WithrawResult
  {
    Output = WithdrawalOutput.Failed,
    Remark = "Regular"
  };

  return result;
}

The trick is very simple, lets have a look again at the filter definition in the routing section:

<namespaceTable>
  <add prefix="temp" namespace="http://tempuri.org/"/>
</namespaceTable>
<filters>
  <filter name="AccountManager_AnotherWithdraw" filterType="XPath" filterData="//temp:accountId = 1"/>
  <filter name="AccountManagerVip_AnotherWithdraw" filterType="XPath" filterData="//temp:accountId = 2"/>
</filters>

As you can see I’ve added the http://tempuri.org/ to the namespaceTable and used it as the prefix for the filter data. This is due to the fact that when the Namespace is unspecified in the Service Contract it defaults to http://tempuri.org/ .

SDP – ORM Day

Just a reminder for the Sela Developer Practice Conference, on the 28th we're having a whole day on ORM:

We'll (Erez Harari, Ido Flatow, Gil Fink and myself) be presenting the following sessions:

Part 1 – ORM and Entity Framework
Part 2 – Comparing ORMs
Part 3 – Entity Framework Deep Dive
Part 4 – Real World Data Access with ORM
Part 5 – Best Practices – Experts Panel

Also I’m glad to say that Oren Eini has accepted my invitation to join our Experts Panel (everyone else but me are all about EF 4.0 so I need some reinforcement on the NHibernate side Wink) so I’m sure it's going to be interesting.

Posted: Dec 26 2009, 10:48 PM by berniea | with no comments
תגים:, , ,

Real World Data Access with ORM at the upcoming SDP

Erez and I are going to give a P&P (Patterns and Practices) Lecture at the upcoming Sela SDP on the 28th of December.

ORM’s are great tools and they can give you a lot of value in your apps. But and there is a but, when mistreated they will just complicate things for you.

We are going to talk about some of the patterns that ORM’s are built upon and other patterns complementing the use of ORM’s.

And last but not least, a lot of Real World Code Examples and Demo’s(Yes, we are going to show code – so beware) of how this patterns are implemented and used.

Be there or be square Open-mouthed

A Quest With EF 4.0 (Entity Framework) And NHibernate Part 2

Usually I don’t really like generated stuff, somehow it seems like I always reach a certain point where I start cursing the $#$%@! wretched generator. So this post will be all about POCO (Plain Old CLR Objects) using just objects. This time I’ll start from the NHibernate Perspective.

Lets consider the following Model (Continuing from my previous post):

image 

It was quite easy to do even with NHibernate – just write a bunch of classes like:

public class User
{
  private IList<Category> _categories = new List<Category>();

  public virtual string Username { get; set; }
  public virtual string Password { get; set; }
  public virtual string FirstName { get; set; }
  public virtual string LastName { get; set; }

  public virtual IList<Category> Categories
  {
    get { return _categories ; }
    set { _categories  = value; }
  }
}
 

Write an hbm (mapping) file:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping
  xmlns="urn:nhibernate-mapping-2.2"
  assembly="Sample.DomainModel"
  namespace="Sample.DomainModel">
  <class name="User" table="Users" >
    <id name="Username" length="255" type="System.String" >
      <generator class="assigned" />
    </id>
    
    <property name="Password" />
    <property name="FirstName" />
    <property name="LastName" />

    <bag name="Categories" 
         table="UserCategory" >      
      <key column="Moderators_Username" />
      <many-to-many class="User" 
                    not-found="ignore" 
                    column="Categories_Id" />
    </bag>    
  </class>
</hibernate-mapping>

 

And that's about it, you have a simple and persistable object.

The following test will show you how easy it is to do some basic operations on the User entity I’ve created, such as creating a new one, loading an existing one and deleting the entity

public void CreateAndDeleteUserTest()
{
  string username = "myusernamefortest";
  string firstName = "SomeFirstName";

  using (ISession session = OpenSession())
  using (ITransaction trn = session.BeginTransaction())
  {
    var usr = new User
                 {
                    FirstName  = firstName,
                    LastName = "SomeLastName",
                    Username = username,
                    Password = "SomePaswword"
                 };

    session.Save(usr);

    trn.Commit();
  }


  using (ISession session = OpenSession())
  using (ITransaction trn = session.BeginTransaction())
  {
    var usr = session.Load<User>(username);
    
    Assert.IsNotNull(usr);
    Assert.AreEqual(usr.FirstName, firstName);
    
    session.Delete(usr);

    trn.Commit();
  }
}

Now, can it be done with EF 4.0?

Let’s have a look:

First I Created a Project called “Sample.DomainModel.POCO” this is where I want to create Persistence Ignorant Objects so I’ve added the following classes:

User, Category, Message and Thread.

And got the following model (quite similar to the one at the beginning of this post):

image

 

Now I’ve Created a Project to hold the EDM –> “Sample.DomainModel.EF”

I’ve added an EDM file to the project (Right Click, Add new item, ADO .NET Entity Data Model). and named it SampleModel.edmx.

This time I was a bit lazy (or deferred, if you like the MS jargon) and I’ve chosen to generate the model from the database, In my last post I’ve generated the model from the EDM, so I’ve decided to use the same one.

And following the instructions from the following post in the ADO .NET Team Blog, I’ve done the following on the properties of SampleModel.edmx I’ve erased the CustomTool Entry and left it blank.

image

Now I’ve added the to “Sample.DomainModel.EF” the ForumsEntities class :

public class ForumsEntities : ObjectContext
  {
    private ObjectSet<Category> _categories = null;
    private ObjectSet<User> _users = null;
    private ObjectSet<Thread> _threads = null;
    private ObjectSet<Message> _messages = null;

    public ForumsEntities()
      : base("name=ForumsEntities", "ForumsEntities")
    {
      _categories = CreateObjectSet<Category>();
      _users = CreateObjectSet<User>();
      _threads = CreateObjectSet<Thread>();
      _messages = CreateObjectSet<Message>();
    }
    
    public ForumsEntities(string connectionString)
      : base(connectionString, "ForumsEntities")
    {     
    }  

    public ObjectSet<Category> Categories 
    {
      get 
      {
        return _categories;
      }
    }    
    public ObjectSet<User> Users
    {
      get
      {
        return _users;
      }
    }

    public ObjectSet<Thread> Threads
    {
      get
      {
        return _threads;
      }
    }

    public ObjectSet<Message> Messages
    {
      get
      {
        return _messages;
      }
    }
  }

And that's about it. I’ve run the same tests I’ve used in my previous post, and for my surprise it actually worked!

Summing up, at least from the basic point of view it is not that hard now to implement POCO with EF 4.0 which makes it a bit more appealing for me, although from what I’ve read at the ADO .NET team blog there are some issues using POCO in regards to Inheritance when using Proxies (Which is also an issue with NHibernate), Change Tracking, and probably in a real life project some other tweaks will occur.

A Quest With EF 4.0 (Entity Framework) And NHibernate Part 1

I’ve been using ORM for some time now, and I have quite an acquaintance with NHibernate from its first days, thanks to Oren Eini AKA Ayende. For the last few months with the upcoming buzz on VS 2010 and .Net Framework 4.0 I had a some discussions with colleagues of mine regarding what would be the best choice for an ORM tool should we use a well known and mature solution like NHibernate or should we follow the crowd on the Microsoft path? Personally I think that this kind of questions are a bit like religious questions when you have zealots from each direction. Therefore I’ve decided to start and investigate the issue on my own and try my best to do an unbiased comparison between the two.

In my place of work we have a great team of professionals but we are lacking with a quite basic tool for collaborating Information between each other (a forum), so my test case will be on a simple forum for our consultants group at Sela Technology Center.

From my perspective EF v1 was really premature, so I’m starting my quest with EF 4.0 (Also I like having some fun with new stuff).

In my first attempt to use EF 4.0 I’ve wanted to use the Model First support – this way I can model my domain and than create the underlying database, from my experience this would be a better approach when you are starting a new application from scratch (although in most real life scenarios you would probably have some kind of a legacy database from which you’ll start modeling your domain).

I’ve modeled my domain to the following structure:

 

image

 

Then all you need to do is right click on your EDM designer and choose “Generate Database Script From Model…”

 

image

 

This will produce a script that you can run and generate the appropriate tables and constraint in the database (Currently this feature only works from MS SQL Server 2005 and 2008).

If you’ve ever had to design an application with the database schema as well, this feature can save you quite some time going back and forth between your model and database. Unfortunately my first attempt was unsuccessful, I’ve kept getting errors when trying to create the ObjectContext (maybe this feature will work better in the release) so I’ve started again with a simpler model, that looks like this:

 

image

 

 

This attempt actually worked so I’ve proceeded with completing my model to desired result.

The nice things about this is that when generating the script (or regenerating after making changes to the model) also the MSL (Mapping Specification Language) is automatically configured with the correct mappings.

So far so good, but does this really work? I’ve written a simple console app and added a few tests AddUser, AddCategories and GetCategoriesFromRoot. For the meantime I’ve decided to avoid getting into testability issues (although this is quite a major issue when thinking about DDD).

private static void AddUser()
{
  using (ForumsContainer context = new ForumsContainer())
  {
    User user = new User
                {
                  Username = "user1",
                  Password = "password",
                  FirstName = "SomeFirstName",
                  LastName = "SommeLastName"
                };

    context.Users.AddObject(user);
    context.SaveChanges();
  }
}
 

 

private static void AddCategories()
{
  using (ForumsContainer context = new ForumsContainer())
  {
    User moderator = context.Users.FirstOrDefault((item) => item.Username == "berniea");
    Category cat1 = new Category
    {
      Id = Guid.NewGuid(),
      Parent = null,
      Title = "Category 1 Title",
      Description = "A long description",
      Moderators = { moderator },
      Children = { new Category
                    {
                      Id = Guid.NewGuid(),
                      Title = "Sub Category 2 Title",
                      Description = "A long description",
                      Moderators = { moderator }
                    },
                    new Category
                    {
                      Id = Guid.NewGuid(),
                      Title = "Sub Category 3 Title",
                      Description = "A long description",
                      Moderators = { moderator }
                    }
                 }
    };
    context.Categories.AddObject(cat1);
    context.SaveChanges();
  }
}
 
private static void GetCategoriesFromRoot()
{
  using (ForumsContainer context = new ForumsContainer())
  {
    context.ContextOptions.DeferredLoadingEnabled = true;

    var cats = from cat in context.Categories
               where cat.Parent == null
               select cat;

    foreach (var item in cats)
    {
      Console.WriteLine("Category Name: {0}", item.Title);
      foreach (var childCat in item.Children)
        Console.WriteLine("\t Child Cat Name: {0}", childCat.Title);
    }
  }
}

Doing something similar with NHibernate is also possible just without the nice designer, unfortunately it will require some coding .

NHibernate will generate the database schema from the mappings (hbm files) and you’ll still have to code your own classes, which is not necessarily a bad idea (if your really lazy there’s a contrib project that generates classes from hbm files).

 

I’ve created the same model (just classes and mapping files):

 

image

 

A few mapping files like Category.hbm.xml:

 

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
                   assembly="Sample.DomainModel" 
                   namespace="Sample.DomainModel">
  <class name="Category" table="Categories" >    
    <id name="Id" type="System.Guid">      
      <generator class="guid" />
    </id>    
    <property name="Title"  />
    <property name="Description" />    
    
    <bag name="Moderators" table="UserCategory" >
      <key column="Categories_Id"/>
      <many-to-many class="User" not-found="ignore" column="Moderators_Username" />
    </bag>
    
    <many-to-one name="Parent" class="Category" column="Parent_Id" 
                 not-found="ignore" not-null="false"/>

    <bag name="Children" table="Categories" lazy="true" 
         cascade="save-update" inverse="true">
      <key column="Parent_Id"/>
      <one-to-many class="Category" not-found="ignore" />
    </bag>    
  </class>
</hibernate-mapping>
 

And the rest (not REST) is history, or just a few lines of code:

 

public void CreateSchemaFromConfig()
{
  var script = new StringBuilder();

  Configuration cfg = GetConfiguration();
  
  new SchemaExport(cfg)
      .Execute(s => script.AppendLine(s), false, false);

  string wholeScript = script.ToString();

  Console.WriteLine(wholeScript);
}
 

This will produce an SQL script a a bit similar to the one produced by the EDM designer, also you can do a SchemaUpdate after making changes to your model.

Host your own WPF Designer

Last week I had to find a WPF Designer which can be hosted in a .Net application, the result of my brief research was Aurora XAML Designer. As far as I sow it isn't possible to host cider (VS WPF Designer) in your own application so lucky for me I found a reasonably good alternative in Aurora XAML Designer.

I've contacted Patrick from Mobiform and he was kind to send me a Trail Version for me to test. The result looks quite promising when I was able to easily take control of different parts of the designer such as manipulate the Toolbox create Tool-bars Pans and so on. Eventually it seems like a nice solution if you want to enable the end user to edit / create simple WPF forms from your application without having to install Visual Studio or Expression Blend.

Here are some pictures of my own self hosted Aurora XAML Designer:

image

image

Posted: Dec 07 2008, 11:22 AM by berniea | with 1 comment(s)
תגים:

Extending your C# application with IronPython

Recently I’ve started messing around with IronPython; IronPython is the first language of a set of languages that rely on the DLR (Dynamic Language Runtime), the DLR is a hosting platform and a dynamic type system, capable of running dynamic languages on top of the CLR.

I really like the concept of a dynamic language, although it isn’t such a new concepts, I think it will be revived and gain more audience with the upcoming .Net Framework 4.0. One very useful thing you can do with IronPython (or any other language on top of the DLR) is to use it as an embedded scripting engine in your.Net application.

What can we gain from such a scripting engine?

I think that in almost every enterprise application project at some point you’ll come to the conclusion that you need extension points in the system. It can be a Rule Engine, a Workflow of some sort or a pluggable User Interface. So now you want to give someone else the ability to extend or modify the application’s behavior with a minimal effort and time. Of course it’s possible to achieve this with custom tools and code generators that compile and load at runtime, but these solutions are complex and time consuming. Utilizing the DLR as an embedded scripting engine is easy and for my opinion quite elegant (although this “magic” isn’t totally free, it comes with a certain performance penalty).

Embedding IronPython in a C# application

A very common scenario would be to allow a user to write some calculation expressions.

string code = @"100 * 2 + 4 / 3";

ScriptEngine engine = Python.CreateEngine();
ScriptSource source =
  engine.CreateScriptSourceFromString(code, SourceCodeKind.Expression);

int res = source.Execute<int>();
Console.WriteLine(res);

As you can see in this example I use the ScriptEngine to execute an expression that can easily be provided from various external sources. The ScriptEngine represents the language Hosting API (in this case the IronPython implementation on the DLR) It serves as the base entry point for embedding any DLR based language in other applications. The ScriptEngine can be used to create a ScriptSource and a ScriptScope which are used to execute the IronPython code.

In many cases we would like this calculation to operate on entities from our own domain, for example:

ScriptEngine engine = Python.CreateEngine();
ScriptRuntime runtime = engine.Runtime;
ScriptScope scope = runtime.CreateScope();

string code = @"emp.Salary * 0.3";

ScriptSource source =
  engine.CreateScriptSourceFromString(code, SourceCodeKind.Expression);

var emp = new Employee { Id = 1000, Name = "Bernie", Salary = 1000 };

scope.SetVariable("emp", emp);

var res = (double)source.Execute(scope);

In this example I also needed to define a ScriptScope object the ScriptScope object is an execution unit for code. The host can set or get variable values from it and more…

These are very simple scenarios where we have expressions coming from an external source (which can be a file, database etc.). Another possible implementation could be to use this within a Business Rule Engine when each expression / code snippet / .py file, becomes an external business rule.

You’ll see in the code sample that I’ve added a class library named berniea.ironPython.extending.ClassLib I’ve created two classes that will serve us in the following sample, SaleBasket and Line as you can see in Figure 1.

image

Figure 1 - Sample Classes

As you can see SaleBasket hold Lines and has a Total, the total is the Sum of Lines Amount. The Line Represents a product added to the basket, the Line class holds the Product’s name, Price and Quantity of items added. The Amount property represents Price * Quantity.

As in the previous samples first I’ve created the ScriptEngine, ScriptRuntime and ScriptScope. The I’ve instantiated a new SaleBasket with some lines:

var saleBasket = new SaleBasket
{
  Lines = new List<Line>
   {
    new Line { ProductName = "Prod1", ProductPrice = 100, Quantity = 2, Amount = 100 * 2},
    new Line { ProductName = "Prod2", ProductPrice = 20, Quantity = 1, Amount = 20 * 1},
    new Line { ProductName = "Prod3", ProductPrice = 45.8, Quantity = 2, Amount = 45.8 * 2},
    new Line { ProductName = "Prod4", ProductPrice = 3.9, Quantity = 10, Amount = 3.9 * 10},
    new Line { ProductName = "Prod5", ProductPrice = 555.5, Quantity = 10, Amount = 555.5 * 10}
   }
};

I’ve added a directory to store rules under the console application. We can store py files in this directory, when we wish execute the rules we can extract them from our Rules directory as shown in the following sample:

First getting all the files is quite easy; we can do something like this:

string rootDir = AppDomain.CurrentDomain.BaseDirectory;
string rulesDir = Path.Combine(rootDir, "Rules");

var files = new List<string>();


foreach (string path in Directory.GetFiles(rulesDir))
  if (path.ToLower().EndsWith(".py"))
     files.Add(path);

If we want the ScriptRuntime to be able to recognize our own classes we need to load the assembly into the ScriptRuntime. For example:

string path = Assembly.GetExecutingAssembly().Location;
string dir = Directory.GetParent(path).FullName;
string libPath = Path.Combine(dir,"berniea.ironPython.extending.ClassLib.dll");

Assembly assembly = Assembly.LoadFile(libPath);
runtime.LoadAssembly(assembly);

Without doing so our IronPython scripts will not recognize classes from our own domain. Like the following script:

from berniea.ironPython.extending.ClassLib import *

for line in saleBasket.Lines:

if line.ProductName == 'Prod1':
discount = line.Amount * 0.2
line.Amount = line.Amount - discount
print 'discount given: ' + discount.ToString()

if line.Quantity >= 10:
line.Amount = line.Amount - line.ProductPrice
print 'discount given: ' + line.ProductPrice.ToString()

You can see here that I’m importing all classes from berniea.ironPython.extending.ClassLib by using import * (this is similar to adding a “using” directive in a C# program). Then I use a “for loop” to iterate all the lines in the saleBasket in order to apply some business logic on them.

After all this preparations the rest is quite easy we need to set the “saleBasket” variable in the scope and execute all script files.

scope.SetVariable("saleBasket", saleBasket);

foreach (var file in files)
{
   source = engine.CreateScriptSourceFromFile(file);
   source.Execute(scope);
}

That’s about it, we have created an extensible rule engine which we can easily add new rules to and modify old ones without changing the core functionality of our application.

The samples I gave here are just the tip of the iceberg, there is a lot more that can be done with an embedded scripting engine in your C# application from writing your own DSL’s, enable plug-ins to your applications and allow other people to extend the applications UI. The nice thing about it is that you could actually build an entire new application using IronPython and very easily hook it to an existing application.

A word of caution, using these techniques is not a substitution for a good design. One very possible mistake could be to build an entire application around the ScriptEngine. I think that this approach will lead to a very messy and hard to maintain application. You need to carefully consider where you want to enable extension points in your application and only there provided the means.