Automating Microsoft Team Foundation (TFS) check-ins

Have you ever wanted to automate code check-ins to TSF e.g. at the end of the day or at the custom interval you define? That’s easy to do using TFS remote API provided by Microsoft.

In our exercise we are going to use following packages to make the solution working:

<packages>
 <package id="Microsoft.AspNet.WebApi.Client" version="5.2.2" targetFramework="net452" />
 <package id="Microsoft.AspNet.WebApi.Core" version="5.2.2" targetFramework="net452" />
 <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="2.16.204221202" targetFramework="net452" />
 <package id="Microsoft.TeamFoundationServer.Client" version="14.95.3" targetFramework="net452" />
 <package id="Microsoft.TeamFoundationServer.ExtendedClient" version="14.95.3" targetFramework="net452" />
 <package id="Microsoft.VisualStudio.Services.Client" version="14.95.3" targetFramework="net452" />
 <package id="Microsoft.VisualStudio.Services.InteractiveClient" version="14.95.3" targetFramework="net452" />
 <package id="Microsoft.WindowsAzure.ConfigurationManager" version="1.7.0.0" targetFramework="net452" />
 <package id="Newtonsoft.Json" version="6.0.8" targetFramework="net452" />
 <package id="System.IdentityModel.Tokens.Jwt" version="4.0.0" targetFramework="net452" />
 <package id="WindowsAzure.ServiceBus" version="3.3.1" targetFramework="net452" />
</packages>

As a authorization mechanism, we will use token which can be generated on your Visual Studio Online account.
Tokens are more secure and flexible than using standard user/pass credentials as we can safely share it and allow it to expire after a predefined period of time.

so, in our app.config we will have following settings:

 <add key="TfsURL" value="https://your_name.visualstudio.com/DefaultCollection/"/>
 <add key="workspaceName" value="AUTOMATED_CHECKINS"/>
 <add key="tfsServerFolderPath" value="$/remote_folder"/>
 <add key="localWorkingPath" value="D:\MyCodefolder"/>
 <add key="tfsUser" value="not-used"/>
 <add key="tfsPass" value="-your token--"/>
 <add key="tfsDomain" value=""/>

Our main function will look as following (see inline comments):

static void TFSCheckIn(string comment = "")
{
  var TfsURL = new Uri(ConfigurationManager.AppSettings["TfsURL"]);

  //use it for standard user/pass authentication
  //var credential = new NetworkCredential(ConfigurationManager.AppSettings["tfsUser"], ConfigurationManager.AppSettings["tfsPass"], ConfigurationManager.AppSettings["tfsDomain"]);
  //var collection = new TfsTeamProjectCollection(TfsURL, credential);
  //collection.EnsureAuthenticated();

  //token based authentication
  var simpleWebToken = new SimpleWebToken(ConfigurationManager.AppSettings["tfsPass"]);
  var networkCredential = new NetworkCredential(ConfigurationManager.AppSettings["tfsUser"], ConfigurationManager.AppSettings["tfsPass"]);
  
  var basicCredential = new BasicAuthCredential(networkCredential);
  var tfsCredentials = new TfsClientCredentials(basicCredential);
  tfsCredentials.AllowInteractive = false;
  var collection = new TfsTeamProjectCollection(TfsURL, tfsCredentials);
  collection.EnsureAuthenticated();

  var versionControl = (VersionControlServer)collection.GetService(typeof(VersionControlServer));

  Workspace WS = null;
  try
  {
    //Get the current workspace
    WS = versionControl.GetWorkspace(ConfigurationManager.AppSettings["workspaceName"], versionControl.AuthorizedUser);
  }
  catch (Exception)
  { }

  //create workspace if not yet created
  if (WS == null)
  {
     WS = versionControl.CreateWorkspace(ConfigurationManager.AppSettings["workspaceName"], versionControl.AuthorizedUser);
  }

  //map local folder if not already mapped
  if (!WS.IsLocalPathMapped(ConfigurationManager.AppSettings["localWorkingPath"]))
  {
    //Mapping TFS Server and code generated
    WS.Map(ConfigurationManager.AppSettings["tfsServerFolderPath"], ConfigurationManager.AppSettings["localWorkingPath"]);
  }
 
  //download remote changes (check-out)
  WS.Get();

 //auto-resolve conflicts
 Conflict[] conflicts = WS.QueryConflicts(new string[] { ConfigurationManager.AppSettings["tfsServerFolderPath"] }, true);
  
  foreach (Conflict conflict in conflicts)
  {
    if (WS.MergeContent(conflict, false))
    {
     conflict.Resolution = Resolution.AcceptTheirs;
     WS.ResolveConflict(conflict);
    }

   Console.WriteLine("conflict: " + conflict.FileName);
 }

 //Add all files just created to pending change
 int NumberOfChange = WS.PendAdd(ConfigurationManager.AppSettings["localWorkingPath"], true);

 //Get the list of pending changes
 PendingChange[] pendings = WS.GetPendingChanges(ConfigurationManager.AppSettings["tfsServerFolderPath"], RecursionType.Full);

  if (pendings.Any())
  {
    //Auto check in code to Server
     WS.CheckIn(pendings, "Auto check-in code (#changes: " + pendings.Count() + "). " + comment);
  }
 }

Need  custom solution tailored to your business needs? Contact us today!

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5.00 out of 5)
Loading...Loading...

Using Windows Azure Service Bus Topics in distributed systems

Service Bus topics allow to perform one way communication using publish/subscribe model. In that model the Service Bus topic can be treated as a intermediary queue that multiple users/components can subscribe to.

When publishing a message we can choose to route it to all subscribers or to apply filters for each subscription resulting in each subscriber receiving messages that are addressed to him. With Service Bus topics we can easily scale distributed applications communicating with each other within or across multiple networks.

In this article I will show you how to build and test Service Bus topic on your local computer. In our example we will simulate sending messages from the web, mobile and service application to the Service Bus Topic.

These messages will be then routed to relevant subscriptions based on defined filters we assigned for each of them. Subscription for messages from the web application will be using multiple auto scalable worker roles to process the business logic. Same will apply for service messages. If we don’t expect a lot of traffic coming from mobile application, we can then use single worker role (with failover settings).

Autoscaling worker roles can be performed using Enterprise Library 5.0 – Autoscaling Application Block (aka WASABi). This will ensure that appropriate number of worker roles will be automatically started when traffic increases and stopped if the traffic will ease.

See high level architecture diagram below:

ServiceBusTopic

 

In order to start, we need to first install “Service Bus 1.0 for Windows Server” (runs on Win7 as well). After installation please go to start > Service Bus 1.0 > Service Bus Configuration. Then use the wizard to set up the web farm first and after that, join your computer to that farm – this will essentially create namespace within the namespace on your local machine.

ServiceBus-configuration

After you configure the Service Bus installation you will get endpoint address that your local application will use to connect to the Service Bus. You may notice that after installation there are 2 databases created on you local MSSQL server, see below image:

ServiceBus-tables

In order to connect to our Service Bus we will use the function below. This will create connection string the pass in to the NamespaceManager class.

  //use this setting when deploying to Windows Azure
  <add key="Microsoft.ServiceBus.ConnectionString" value="Endpoint=sb://[your namespace].servicebus.windows.net;SharedSecretIssuer=owner;SharedSecretValue=[your secret]" />

 public static string getLocalServiceBusConnectionString()
 {
    var ServerFQDN = System.Net.Dns.GetHostEntry(string.Empty).HostName;
    var ServiceNamespace = "ServiceBusDefaultNamespace";
    var HttpPort = 9355;
    var TcpPort = 9354;

    var connBuilder = new ServiceBusConnectionStringBuilder();
    connBuilder.ManagementPort = HttpPort;
    connBuilder.RuntimePort = TcpPort;
    connBuilder.Endpoints.Add(new UriBuilder() { Scheme = "sb", Host = ServerFQDN, Path = ServiceNamespace }.Uri);
    connBuilder.StsEndpoints.Add(new UriBuilder() { Scheme = "https", Host = ServerFQDN, Port = HttpPort, Path = ServiceNamespace }.Uri);

    return connBuilder.ToString();
 }

Within the worker role we will use NamespaceManager to create Service Bus Topic (if does not exist). We will also create subscriptions and associated filters.
Please notice that subscription will filter messages using MessageOrigin property. This property will be assigned to the message in the message send method for each application separately (web, mobile, service).

 public void CreateServiceBusTopicAndSubscriptions(NamespaceManager namespaceManager)
 {
    #region Configure and create Service Bus Topic
    var serviceBusTestTopic = new TopicDescription(TopicName);
    serviceBusTestTopic.MaxSizeInMegabytes = 5120;
    serviceBusTestTopic.DefaultMessageTimeToLive = new TimeSpan(0, 1, 0);

    if (!namespaceManager.TopicExists(TopicName))
    {
        namespaceManager.CreateTopic(serviceBusTestTopic);
    }
    #endregion

    #region Create filters and subsctiptions
    //create filters
    var messagesFilter_Web = new SqlFilter("MessageOrigin = 'Web'");
    var messagesFilter_Mobile = new SqlFilter("MessageOrigin = 'Mobile'");
    var messagesFilter_Service = new SqlFilter("MessageOrigin = 'Service'");

    if (!namespaceManager.SubscriptionExists(TopicName, "WebMessages"))
    {
        namespaceManager.CreateSubscription(TopicName, "WebMessages", messagesFilter_Web);
    }

    if (!namespaceManager.SubscriptionExists(TopicName, "MobileMessages"))
    {
        namespaceManager.CreateSubscription(TopicName, "MobileMessages", messagesFilter_Mobile);
    }

    if (!namespaceManager.SubscriptionExists(TopicName, "WCfServiceMessages"))
    {
        namespaceManager.CreateSubscription(TopicName, "WCfServiceMessages", messagesFilter_Service);
    }
    #endregion
}

We also need to create subscription clients in “OnStart” method giving each subscriber a unique name.

 public override bool OnStart()
 {
    // Set the maximum number of concurrent connections 
    ServicePointManager.DefaultConnectionLimit = 12;

    // Create the queue if it does not exist already
    //string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
    var connectionString = getLocalServiceBusConnectionString();
    var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);

    //create topic and subscriptions
    CreateServiceBusTopicAndSubscriptions(namespaceManager);

    // Initialize subscription for web, mobile and service
    SubscriptionClients.Add(SubscriptionClient.CreateFromConnectionString(connectionString, TopicName, "WebMessages"));
    SubscriptionClients.Add(SubscriptionClient.CreateFromConnectionString(connectionString, TopicName, "MobileMessages"));
    SubscriptionClients.Add(SubscriptionClient.CreateFromConnectionString(connectionString, TopicName, "WCfServiceMessages"));

    IsStopped = false;
    return base.OnStart();
}

Inside the run method we will use Task Parallel foreach method to create separate task for each subscriber listening for incoming messages.
This is only to simulate multiple subscribers in one place. Normally each worker role will connect to the topic listening for the messages appropriate for it’s type (separate for web, mobile and service).

public override void Run()
{
 Parallel.ForEach(SubscriptionClients, currentSubscrtiption =>
 {
    while (!IsStopped)
    {
        #region Receive messages
        try
        {
            // Receive the message
            var receivedMessage = currentSubscrtiption.Receive();
         
            if (receivedMessage != null)
            {
                var messageFrom = receivedMessage.Properties["MessageOrigin"].ToString();

                switch (messageFrom)
                {
                    case "Web":
                        //send it to web processing logic

                        break;
                    case "Mobile":
                        //send it to mobile processing logic

                        break;
                    case "Service":
                        //send it to service processing logic

                        break;
                    default:
                        break;
                }

                // Process the message
                Trace.WriteLine(Environment.NewLine + "--------------------------" + Environment.NewLine);
                Trace.WriteLine(string.Format("{0} message content: {1}", messageFrom, receivedMessage.GetBody<string>()));

                receivedMessage.Complete();
            }
        }
        catch (MessagingException e)
        {
            if (!e.IsTransient)
            {
                Trace.WriteLine(e.Message);
                throw;
            }

            Thread.Sleep(10000);
        }
        catch (OperationCanceledException e)
        {
            if (!IsStopped)
            {
                Trace.WriteLine(e.Message);
                throw;
            }
        }
        #endregion
    }
});
}

Finally we can simulate sending messages from the MVC application. We will use 3 different buttons to create and send messages.

 [HttpPost]
 public ActionResult SendWebMessage()
 {
    SendMessage("Web");

    return RedirectToAction("Index", "Home");
 }

 [HttpPost]
 public ActionResult SendMobileMessage()
 {
    SendMessage("Mobile");

    return RedirectToAction("Index", "Home");
 }

 [HttpPost]
 public ActionResult SendServiceMessage()
 {
    SendMessage("Service");

    return RedirectToAction("Index", "Home");
 }

See the image below:

ServiceBusTopic-subscriptions

Please note that when sending messages we have to assign value to message.Properties[“MessageOrigin”]. This will be used by the Service Bus Topic to route messages to appropriate subscriptions.

 void SendMessage(string type)
 {
    var connectionString = getLocalServiceBusConnectionString();

    var Client = TopicClient.CreateFromConnectionString(connectionString, "ServiceBusTestTopic");

    var message = new BrokeredMessage("test message");
    message.Properties["MessageOrigin"] = type;

    Client.Send(message);
 }

As usual, I have attached working project files for your tests 🙂

AzureServiceBus

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Social media Sentiment Analysis using C# API

Sentiment Analysis in Social media has become very important for many companies over the last few years as more and more people use this communication channel not only to exchange the messages, but also to express their opinions about the products and services they use or used in the past.

In order to start proper sentiment analysis, we need to use correct Text Analytics and Text Mining techniques. The fastest way is to use external API to get the information we need and then import that data on the daily basis to our system database, where we can visualize and track the results.

In this article I will show you, how you can convert unstructured data e.g. tweets using text2data.org API, into structured information that you can use to track sentiment towards the brand, product or company.

First step is to register in the service and get the private key to be used in your API calls. Next, you need to download API SDK from here

The code below, will just create request object containing your information and post that to platform using json or xml format. The response object will have information you need: Detected Entities, Themes, Keywords, Citations, Slang words and abbreviations. For each of these you can get sentiment result as double, polarity and score (positive, negative or neutral). Received information can be formatted the way you want. Below is complete sample (except for private key):

  static void Main(string[] args)
   {
        var inputText = "I am not negative about the LG brand.";
        var privateKey = "-------------"; //add your private key here (you can find it in the admin panel once you sign-up)
        var secret = ""; //this should be set-up in admin panel as well.

       var doc = new Document()
       {
          DocumentText = inputText,
          IsTwitterContent = false,
          PrivateKey = privateKey,
          Secret = secret
       };

       var docResult = API.GetDocumentAnalysisResult(doc); //execute request

       if (docResult.Status == (int)DocumentResultStatus.OK) //check status
       {
        //display document level score
        Console.WriteLine(string.Format("This document is: {0}{1} {2}", docResult.DocSentimentPolarity, docResult.DocSentimentResultString, docResult.DocSentimentValue.ToString("0.000")));

        if (docResult.Entities != null && docResult.Entities.Any())
        {
         Console.WriteLine(Environment.NewLine + "Entities:");
         foreach (var entity in docResult.Entities)
         {
           Console.WriteLine(string.Format("{0} ({1}) {2}{3} {4}", entity.Text, entity.KeywordType, entity.SentimentPolarity, entity.SentimentResult, entity.SentimentValue.ToString("0.0000")));
          }
       }
 
      if (docResult.Keywords != null && docResult.Keywords.Any())
      {
         Console.WriteLine(Environment.NewLine + "Keywords:");
         foreach (var keyword in docResult.Keywords)
          {
             Console.WriteLine(string.Format("{0} {1}{2} {3}", keyword.Text, keyword.SentimentPolarity, keyword.SentimentResult, keyword.SentimentValue.ToString("0.0000")));
            }
        }

        //display more information below if required 

      }
      else
      {
          Console.WriteLine(docResult.ErrorMessage);
      }

       Console.ReadLine();
  }

You can always validate results by checking the demo site (on text2data.org platform)

sentiment-analysis

Enjoy 🙂

Social-Media-Icons

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5.00 out of 5)
Loading...Loading...

Creating custom WCF message router – load balancer

The very common requirement is to route WCF messages from the publicly exposed front-end service (perimeter network) to other services sitting within the local network. This is the good security model that lets us implement routing or load balancer if needed. The front-end service will also act as a firewall in this scenario. The routing may be performed based on the requested service method, content or custom algorithm.

The image below shows typical hight level service infrastructure within the organization.

wcf_router

Let’s start with defining our service that the client will use to send requests. The “Calculate” method will be used to perform time consuming operation that will require more CPU usage.

  [ServiceContract]
    public interface IMessageService
    {
        [OperationContract]
        string SendMessage(string value);

        [OperationContract]
        int Calculate(string value); 
    }

  public class MessageService : IMessageService
  {
        public string SendMessage(string value)
        {
            return string.Format("You have sent: {0}", value);
        }

        public int Calculate(string value)
        {
            //do calculation
            return 999;
        }
    }

Next, we need to define IRouter interface. In our example we will use two methods: RouteMessage, that will process and route the messages and AddAvailableEndPoints, that will be used to assign available endpoints for the requests to be routed to. Please note that our router will not be using service contracts which is very useful as we don’t want to recompile router service each time our message service interface changes. You may also implement function to assign routing rules, in our example we will hard-code the rules for better clarity.

  [ServiceContract]
    public interface IRouter
    {
        [OperationContract(Action = "*", ReplyAction = "*")]
        Message RouteMessage(Message message);

        void AddAvailableEndPoints(List<EndpointAddress> addressList);        
    }

Let’s create our router class now. Please note that we are using static constructor as we will only use one instance of the service (creating and destroying channel factory is very costly operation).

 static IChannelFactory<IRequestChannel> factory;
 List<EndpointAddress> addressList;
 int routeCount;

  //create only one instance
  static Router()
  {
    try
    {
        var binding = new BasicHttpBinding();
        factory = binding.BuildChannelFactory<IRequestChannel>(binding);

        factory.Open();
    }
    catch (Exception e)
    {
        Console.WriteLine("Exception: {0}", e.Message);
    }
  }

Now we need to implement the actual routing algorithm. In our case, when message arrives we will check the action method that proxy object will use to execute on the target service. We have hard-coded the method “Calculate” to be routed to service2, the other request will be routed equally among available services. You can also use XmlDocument class to parse message content if needed.
Of course in the production environment you may want to pass in routing rules as a object and examine that when processing arrived message prior routing it to appropriate service.

 public Message RouteMessage(Message message)
 {
    IRequestChannel channel = null;
    Console.WriteLine("Action {0}", message.Headers.Action);
    try
    {
        //Route based on custom conditions
        if (message.Headers.Action.ToLower().EndsWith("calculate")) //or use custom routing table stored in other location
        {
            //use second endpoint as it has more resources for time consuming operations
            channel = factory.CreateChannel(this.addressList.Skip(1).First());
            Console.WriteLine("Routed to: {0}\n", this.addressList.Skip(1).First().Uri);
        }
        else
        {
            //or
            #region Route other requests equally
            //we assume only 2 endpoints for this example 
            if (routeCount % 2 == 0)
            {
                channel = factory.CreateChannel(this.addressList.First());
                Console.WriteLine("Routed to: {0}\n", this.addressList.First().Uri);
            }
            else
            {
                channel = factory.CreateChannel(this.addressList.Skip(1).First());
                Console.WriteLine("Routed to: {0}\n", this.addressList.Skip(1).First().Uri);
            }
            //reset route counter
            if (routeCount > 10000) { routeCount = 0; } else { routeCount++; }
            #endregion           
        }

        //remove context as the message will be redirected
        message.Properties.Remove("ContextMessageProperty");

        channel.Open();

        var reply = channel.Request(message);

        channel.Close();

        return reply;
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
        return null;
    }
}

At this time, we can create client application that will call our front-end service(router) multiple times for our test.

 static void Main(string[] args)
 {
    Console.Write("Press enter to send multiple messages");
    Console.ReadLine();

    var baseFrontOfficeAddress = new Uri("https://localhost:81/FrontOffice");

    try
    {
        var binding = new BasicHttpBinding();
        var endpoint = new EndpointAddress(baseFrontOfficeAddress);

        var factory = new ChannelFactory<IMessageService>(binding, endpoint);
        var channel = factory.CreateChannel();

        //simulate multiple requests
        for (var i = 0; i < 10; i++)
        {
            var reply = channel.SendMessage("test message");
            Console.WriteLine(reply);

            var replyCalulated = channel.Calculate("test message");
            Console.WriteLine("Calculated value: " + replyCalulated);
        }

        Console.ReadLine();
        factory.Close();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

In order to receive client requests we need to start our services. In our example we will host router and available services within the same application. Normally we would deploy router service within DMZ zone and target services on the separate computers withing our local network. For security reasons, only router service would be exposed to public network.

 static void Main(string[] args)
 {
    //execute this first using cmd (win7)
    //netsh http add urlacl url=https://+:81/FrontOffice user=Domain(or PC name)\UserName
    //netsh http add urlacl url=https://+:81/service1 user=Domain(or PC name)\UserName
    //netsh http add urlacl url=https://+:81/service2 user=Domain(or PC name)\UserName

    //front office endpoint - all clients will call this address before requests will be routed
    var baseFrontOfficeAddress = new Uri("https://localhost:81/FrontOffice");

    #region Target service endpoints that requests can be routed to (can be more than 2)
    var baseEndPointAddress1 = new Uri("https://localhost:81/service1");
    var baseEndPointAddress2 = new Uri("https://localhost:81/service2");

    var endPoint1 = new EndpointAddress(baseEndPointAddress1);
    var endPoint2 = new EndpointAddress(baseEndPointAddress2); 
    #endregion

    #region These services should normally be deployed to different servers within the organization
    //start service 1
    var hostService1 = new ServiceHost(typeof(MessageService1), baseEndPointAddress1);
    hostService1.AddServiceEndpoint(typeof(IMessageService), new BasicHttpBinding(), "");
    hostService1.Open();
    Console.WriteLine("MessageService1 service running");

    //start service 2
    var hostService2 = new ServiceHost(typeof(MessageService2), baseEndPointAddress2);
    hostService2.AddServiceEndpoint(typeof(IMessageService), new BasicHttpBinding(), "");
    hostService2.Open();
    Console.WriteLine("MessageService2 service running");        
    #endregion

    #region Start router service
    var router = new Router();

    //add available service enpoints
    router.AddAvailableEndPoints(new List<EndpointAddress>() { endPoint1, endPoint2 });

    var routerHost = new ServiceHost(router);
    routerHost.AddServiceEndpoint(typeof(IRouter), new BasicHttpBinding(), baseFrontOfficeAddress);
    routerHost.Open();

    Console.WriteLine("Router service running");           
    #endregion           
    
    Console.WriteLine("---Run client to send messages---");  
    Console.ReadLine(); 
}

The results of our test is shown below. In this example we chose to route all requests for “Caluculate” method to “service2” (as it located on faster machine within the network), the other requests are routed equally to service1 or service2.

routed_messages

Above way of routing messages is very flexible as it is implemented on the very low level. The other way of routing messages is to use RoutingService class. In this approach you would define the routing table, endpoints and filters in your configuration file. Based on these settings you would add service behavior that will use RoutingService class to route messages.

I have attached project files below for your tests. Please execute below commands before testing it:

netsh http add urlacl url=https://+:81/FrontOffice user=Domain(or PC name)\UserName”
netsh http add urlacl url=https://+:81/service1 user=Domain(or PC name)\UserName
netsh http add urlacl url=https://+:81/service2 user=Domain(or PC name)\UserName

WCF_Router

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Integrating automated Selenium tests with TeamCity

Automated tests are crucial these days to achieve high quality software products as they are being prone to errors especially during agile process where changes can occur at any stage causing our solution to stop functioning properly. Apart from standard testing using TDD, Mocking etc. there is always a need to perform interface tests simulating an user using our website. The ideal solution in that case are automated Selenium tests.

 

Selenium tests give us possibility to create user case scenarios that we can test either on our dev applications or live websites deployed to the clients. This will give us a chance to test all cases after the build will happen either on demand or when we deploy live version of our product.

Selenium tests can be created by the developer or business analyst or testers as this not requires programming skills. In order to start creating tests we need to download Selenium IDE plug-in for Firefox: https://docs.seleniumhq.org/download/

Creating tests is similar to recording macros in Excel, all we need to do is to record our actions that we want to test, image below shows the example:

 

selenium-ide

Please note that you need to install Firefox version 21.0 the newer version is not working with selenium (Firefox bug).

After our test is created, we need to export it to .cs C# file as pictured below:

selenium-export

The next step is to create small library project application. We will use this project to compile our test files exported from the Selenium IDE. After we have the project created, we simply need to copy our exported .cs file to our project and compile dll.

selenium-project-files

If we want to integrate Selenium tests with the TeamCity server, we can do it by including previously created project into our development project files. After that, we can configure TeamCity build step to run our Selenium tests on every triggered build. To configure build step, we need to choose NUnit runner type (or other test framework of your choice)

buildstep

If we have configured everything correctly, the test will run on every application change. Of course Selenium tests must be run at the end, when project is compiled and deployed on the server (if this is our local dev environment).

When tests finish, we can check the status in TeamCity admin panel or configure email notification or use TeamCity Tray Notifier to track the results.

test-results

The final thing that needs to be set-up is the Selenium server. You can run the Selenium server locally by turning it on when needed and then running NUnit application to check the test locally. In our case we want the Selenium to be running on the server as a Windows Service.

After downloading Selenium RC https://docs.seleniumhq.org/projects/remote-control/ we unpack the files to following location C:\Selenium\selenium-server-1.0.3 You not necessarily need all that files as it also includes web drivers for other languages. Anyway, our folder structure should look similar to following:

selenium-files

We also need to download nssm-2.16 (“Non-Sucking Service Manager”), we will use it to install Windows Service running our Selenium server. When we run run-server-manually.bat, the Selenium server will be launched, it will stop after you close console window – this will help when testing on your local machine.
The script looks like this:

 java -jar selenium-server-standalone-2.33.0.jar -port 4444

I have also created 2 scripts: install-windows-service.bat and remove-windows-service.bat that install and remove Windows Service on the target server. The logic is as follows:

//install Windows Service
C:\Selenium\selenium-server-1.0.3\nssm-2.16\win64\nssm.exe install Selenium-Server "C:\Program Files (x86)\Java\jre7\bin\java.exe" "-jar C:\Selenium\selenium-server-1.0.3\selenium-server-standalone-2.33.0.jar"

//Uninstall Windows Service
C:\Selenium\selenium-server-1.0.3\nssm-2.16\win64\nssm.exe remove Selenium-Server

After installing Windows Service, please make sure it is running by going to Control panel > Administrative Tools > Services. You also need to remember to check java path as it differs for x64 systems.
That’s it. I have included configuration files that may help you. Hope you will be successful with your configuration 🙂

Selenium-VisualStudioApp

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5.00 out of 5)
Loading...Loading...

Auto creating installers using WIX (Open source) and CI server

Creating installer packages has become more complicated recently as the Microsoft decided to not to support installation projects (.vdproj) in VS 2012 upwards. On the top of that there is additional difficulty if we want to auto-create installers on the server that hasn’t got commercial Visual Studio installed – we just cannot do it 🙁

The perfect solution for that is to use WIX installer (Open source). WIX enables you to build installation packages from xml files without relying on Visual Studio. The only component required is to install WIX toolset on the build server (wixtoolset.org)

The main WIX file is the .wixproj file that builds the required package either from inside Visual Studio or from command line – without VS. In that project file we can find some commands that use WIX heat, candle and light modules to create required msi files. See sample below:

Harvesting module will create dynamic list of all files that need to be included into installer. This list is gathered after our website app is published to the local folder:

  <Exec Command="&quot;$(WixPath)heat.exe&quot; dir $(PublishF) -dr INSTALLLOCATION -ke -srd -cg MyWebWebComponents -var var.publishDir -gg -out WebSiteContent.wxs" ContinueOnError="false" WorkingDirectory="." />

After harvesting, we can finally build installer. Please note that we can pass in custom parameters when triggering candle module, the parameters must be preceded with the letter “d” e.g. -dMyParam. In our sample we will use this to pass in command line arguments that will define our installer’s name, version, website root etc.

 <Exec Command="&quot;$(WixPath)candle.exe&quot; -ext WixIISExtension -ext WixUtilExtension -ext WiXNetFxExtension -dProductName=$(ProductName) -dWebFolderName=$(WebFolderName) -dProductVersion=$(ProductVersion) -dUpgradeCode=$(UpgradeCode) -dProductCode=$(ProductCode) -dpublishDir=$(PublishF) -dMyWebResourceDir=. @(WixCode, ' ')" ContinueOnError="false" WorkingDirectory="." />

 <Exec Command="&quot;$(WixPath)light.exe&quot; -ext WixUIExtension -ext WixIISExtension -ext WixUtilExtension -ext WiXNetFxExtension -out $(MsiOut) @(WixObject, ' ')" ContinueOnError="false" WorkingDirectory="." />

For the purpose of this article we will create very simple installer without configuring IIS settings, deploying sql scripts etc. All installer’s basic configuration can be found in UIDialogs.wxs, MyWebUI.wxs, IisConfiguration.wxs – you may want to adjust it for your needs. The file Product.wxs is the main entry point where you can define installation folders, actions, validations etc.

Please note that adding UpgradeCode and MajorUpgrade configuration elements will result in any older installed version to be automatically un-installed prior the installation. Also, any custom params you want to use inside your installer configuration must be used like this $(var.MyParamName).

 <Product Id="$(var.ProductCode)" 
			 Name="$(var.ProductName)" 
			 Language="1033" 
			 Version="$(var.ProductVersion)" 
			 Manufacturer="MyCompany" 
			 UpgradeCode="$(var.UpgradeCode)" >
 <MajorUpgrade DowngradeErrorMessage="A newer version of Product Name Removed is already installed."/> 
 
 .....

After defining our WIX configuration files, we can finally build our installer. We can do it by using batch or Power Shell script, or use Visual Studio to do it. It is because the .wixproj file it’s just normal ms-build file.

When using CI server we can simply run power-shell installation process by passing in our version number (build number, revision etc.) and other parameters we need.

This is how we can run it with PS:
WIX_installer-powershell

We can still use Visual Studio to do the same, as presented below:
WIX_installer-visualstudio

I have attached working sample below. Please don’t forget to install WIX toolset before running it. Also please check if all paths to WIX and msbuild folders are the same on your server (or use environment variables instead).

Enjoy!
MyWebsite

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5.00 out of 5)
Loading...Loading...

Multiple object bindings on MVC view

Multiple object bindings in MVC are the common requirement when working with more advanced UI. An example of that can be creating order form with multiple order items being added dynamically by the user.

In such a scenario we can create binding for our “Order” model as normal and create partial view with the structure of our “OrderItem” class separately. By doing this, the user will be able to dynamically preload order items using ajax request, appending created html to div container.

When submitting the form, order items will be automatically bound to the nested property list, provided the html control ids and names will follow the convention: name of the list property, index id and property name; id=’OrderItems_ID__Quantity’

For example:

   <input type="number" id='OrderItems_@(Model.OrderItemId)__Quantity' value="@Model.Quantity" name='OrderItems[@Model.OrderItemId].Quantity'/>
 

MVC-order

Lets create our order and order items classes first

   public class Order
    {
        public int OrderId { get; set; }
        [Required]
        [Display(Name = "Order Person")]
        public string OrderPerson { get; set; }
        [Required]
        [Display(Name = "Delivery Address")]
        public string DeliveryAddress { get; set; }
        public string OrderComments { get; set; }
        public DateTime OrderDate { get; set; }
        public double TotalNet { get; set; }
        public double TotalVAT { get; set; }
        public double TotalAmount { get; set; }
        [Required]
        public List<OrderItem> OrderItems { get; set; }
    }

	public class OrderItem
    {
        public int OrderItemId { get; set; }
        [Required]
        public string ItemName { get; set; }
        [Required]
        public int Quantity { get; set; }
        [Required]
        public double? UnitPrice { get; set; }
        public double VATRate { get; set; }
        public double TotalValue { get; set; }
    }
 

Our Order form will look the standard way, plus extra element for dynamically added elements:

  <div class="form-group">
    <b>@Html.ActionLink("Add item", "LoadBlankItemRow", null, new { id = "addOrderItem", @class = "btn btn-warning" })</b>
     <div style="padding-top: 10px;">
         <p class="itemColumn">Quantity</p>
         <p class="itemColumn">Name</p>
         <p class="itemColumn">Unit Price</p>
         <p class="itemColumn">VAT Rate</p>
         <p class="itemColumn">Net Value</p>
         <p class="itemColumn"></p>
       </div>
     <div id="editorRows">
        @foreach (var itemModel in Model.OrderItems)
         {
            @Html.Partial("_OrderItem", itemModel)
         }
       </div>
   </div>
 

When user clicks the Add item link, the controller action is being invoked returning html structure reflecting the list item model.

   [HttpGet]
   public virtual PartialViewResult LoadBlankItemRow(int id)
   {
     var orderItem = new OrderItem { OrderItemId = id, Quantity = 1 };

     return PartialView("_OrderItem", orderItem);
   }
 

We will use jquery to get the current numbers of items already inserted and define the current index. This will be used to create appropriate control names required for nested model bindings:

   $("#addOrderItem").click(function (e) {
        var itemIndex = $("#editorRows input.iHidden").length;
        $.get("@Url.Action("LoadBlankItemRow", "Order")/" + itemIndex, function (data) {
            $("#editorRows").append(data);
        });
        return false;
    });
 

Each dynamically inserted row will have JavaScript logic to remove the current row and recalculate total values:

    $("a.deleteRow").click(function () {
        $(this).parents("#itemRow").remove();
        calculateTotals();
        return false;
    });

    $('#OrderItems_@(Model.OrderItemId)__Quantity').blur(function () {
        updateTotalValue(@Model.OrderItemId);
    });
    $('#OrderItems_@(Model.OrderItemId)__UnitPrice').blur(function () {
        updateTotalValue(@Model.OrderItemId);
    });
    $('#OrderItems_@(Model.OrderItemId)__VATRate').change(function () {
        updateTotalValue(@Model.OrderItemId);
    });

Finally, scripts included on parent model form will calculate and update controls located outside the partial view, providing Total amount for the entire order.

   function updateTotalValue(id) {
        var qnt = $('#OrderItems_' + id + '__Quantity').val();
        var uprc = $('#OrderItems_' + id + '__UnitPrice').val();

        var vat = parseFloat($('#OrderItems_' + id + '__VATRate').val());
        if (vat == 0) { val = (qnt * uprc); } else { val = (qnt * uprc) * (1 + vat / 100); }
        if (qnt == 0 || uprc == 0) { val = 0; }

        $('#OrderItems_' + id + '__TotalValue').val(val.toFixed(2));
        calculateTotals();
    }

    function calculateTotals() {
        var totalNet = 0.0; var totalVat = 0.0;

        $("#editorRows").children().each(function (index) {
            var qnt = $('#OrderItems_' + index + '__Quantity').val();
            var uprc = $('#OrderItems_' + index + '__UnitPrice').val();
            var vat = parseFloat($('#OrderItems_' + index + '__VATRate').val());
            if (qnt != null && qnt != 'NaN') {
                totalNet += qnt * uprc;
                if (vat != 0) {
                    totalVat += parseFloat((qnt * uprc) * (vat / 100));
                }
            }
        });

        $('#TotalNet').val(totalNet.toFixed(2));
        $('#TotalVAT').val(totalVat.toFixed(2));
        $('#TotalAmount').val((totalNet + totalVat).toFixed(2));
    }

I have included working project below. Enjoy!

MVC OrderForm

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5.00 out of 5)
Loading...Loading...

Umbraco custom model with pager

Umbraco is the open source content management system allowing for quick content creation and easy customization as it uses ASP.NET MVC pattern to render page components.

The power of Umbraco is the flexibility and a host of features it provides out of the box. It is also written in C# which makes it especially popular amongst the developers.

Creating custom views with Umbraco is very easy, as we can set-up our own routing methods by inheriting from RenderMvcController and overriding action methods accepting and returning RenderModel object:

public class MyPageController : RenderMvcController
{

public override ActionResult Index(RenderModel model)
{
//your logic here

return base.Index(model);
}
}

However, if we want to accept and return our own custom model, the hack is required.
Suppose we want to return custom model containing pager information. We can do that by extending RenderModel class as follows:

public class MyCustomModel : RenderModel
{
public MyCustomModel() : this(new UmbracoHelper(UmbracoContext.Current).TypedContent(UmbracoContext.Current.PageId)) { }
public MyCustomModel(IPublishedContent content, CultureInfo culture) : base(content, culture) { }
public MyCustomModel(IPublishedContent content) : base(content) { }

public string Title { get; set; }
public List<IPublishedContent> ModelItems { get; set; }
public Pager Pager { get; set; }
}

Our pager class will look as this:

 public class Pager
 {
 public int TotalPages { get; set; }
 public int CurrentPage { get; set; }
 public int PageSize { get; set; }
 public int NumberOfRows { get; set; }
 }
 

Next, our controller action will have following logic:

public override ActionResult Index(RenderModel model)
{
var myCustomModel = new MyCustomModel();

var modelItems = model.Content.Children.OrderByDescending(x => x.CreateDate);
//pager logic
const int PAGE_SIZE = 6;
var currentPage = 1;
int.TryParse(Request.QueryString["page"], out currentPage);

myCustomModel.Pager = new Pager()
{
PageSize = PAGE_SIZE,
TotalPages = (int)Math.Ceiling((double)modelItems.Count() / (double)PAGE_SIZE),
CurrentPage = currentPage
};

myCustomModel.ModelItems = currentPage == 0 ? modelItems.Take(PAGE_SIZE).ToList() : modelItems.Skip((currentPage - 1) * PAGE_SIZE).Take(PAGE_SIZE).ToList();

if (myCustomModel.Pager.CurrentPage > myCustomModel.Pager.TotalPages) { myCustomModel.Pager.CurrentPage = myCustomModel.Pager.TotalPages; }
else if (myCustomModel.Pager.CurrentPage < 1) { myCustomModel.Pager.CurrentPage = 1; }

return base.Index(myCustomModel);
}

As we return custom model from the action, we also need to define this in our custom view as shown below:

@inherits Umbraco.Web.Mvc.UmbracoViewPage<MyCustomModel>

<div class="row">
<div class="col-md-6">
@foreach (var item in Model.ModelItems.Take(Model.Pager.NumberOfRows))
{
//display your item data here
}
</div>
</div>

@Html.Partial("_Pager", Model.Pager) //embed pager view

Finally, the pager partial view should look as follows:

@model Pager

@{
var IsBreak = false;
}

<div class="container">
@if (Model.TotalPages > 1)
{
<ul class="pager">
@if (Model.CurrentPage > 1)
{
<li><a href="?page=@(Model.CurrentPage - 1)">Previous</a></li>
}
@for (int p = 1; p < Model.TotalPages + 1; p++)
{
var linkClass = (p == Model.CurrentPage) ? "disabled" : "active";
if (p == Model.CurrentPage)
{
<li class="@Html.Raw(linkClass)"><a href="?page=@p">@p</a></li>
IsBreak = false;
}
else
{
if (p == 1
|| p == Model.CurrentPage - 1
|| p == Model.CurrentPage + 1
|| p == Model.TotalPages - 1
|| p == Model.TotalPages)
{
<li class="@Html.Raw(linkClass)"><a href="?page=@p">@p</a></li>
IsBreak = false;
}
else
{
if (IsBreak)
{
continue;
}
else
{
<li><a href="#">...</a></li>
IsBreak = true;
}
}
}
}

@if (Model.CurrentPage < Model.TotalPages)
{
<li><a href="?page=@(Model.CurrentPage + 1)">Next</a></li>
}
</ul>
}
</div>

The above is fully working example – of course you need to do some html adjustments depending on your requirements. Enjoy!

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Displaying dynamic javascript charts using MVC

When creating business solutions there is often a need to display dynamic JavaScript charts using specific entity data that can be easily extended throughout the system. Properly designed system should always provide clear separation between business entities and UI layer. When using JavaScript this separation can be easily neglected.
In this article I will show you how to use entity oriented approach when mixing C# and JavaScript code.

In order to do that we will create JavaScript api that will be used to call web controllers such as clientController, productController, userController etc. Each of these controllers will provide us entity related data that can be displayed on the web pages throughout the system. The data will be sent using json protocol along with the target chart configuration we want.

jschart_line

In order to display above charts within our application, we need to simply create div tag that will serve as a placeholder for our chart, we also need to assign attributes and “chartButton” class name to the element that will trigger chart display. The attributes will contain entity name e.g. data-entity=”client”, chart placeholder e.g. targetdiv=”chart1Div” and optionally data parameters e.g. params=”clientId=?&fullInfo=?”

  <input type="button" value="show client chart" class="chartButton" data-entity="client" data-params="clientId=@ViewBag.ClientId&fullInfo=true" data-targetdiv="chart1Div" />
  <input type="button" value="show product chart" class="chartButton" data-entity="product" data-targetdiv="chart1Div" />
  <input type="button" value="show user chart" class="chartButton" data-entity="user" data-targetdiv="chart1Div" />
  <div id="chart1Div"></div>

We also need to add following script references to our _Layout.cshtml file. In our case we will use HighCharts, hence reference to highcharts.js script (we are not limited only to this control, you can convert it to use other JavaScript chart controls as well if needed). The file chartAPI.js will contain our main logic that connects Web Api controllers and JavaScript code.

   <script src="https://code.highcharts.com/highcharts.js" type="text/javascript"></script>
   <script src="@Url.Content("~/Scripts/chartAPI.js")" type="text/javascript"></script>

Let’s start implementation from creating Web Api entity controllers and chart data dto object that will transfer the chart data and it’s configuration.

The ChartData class will contain chart series and chartConfig object – it can be extended at any time depending on our requirements.

   public class ChartData
   {
        public ChartConfig ChartConfig { get; set; }
        public List<Series> Series { get; set; }
    }

    public class Series
    {
        public string Name { get; set; }
        public string Color { get; set; }
        public string DashStyle { get; set; }      
        public List<PointData> Points { get; set; }
    }

    public class PointData
    {
        public DateTime Date { get; set; }
        public double Value { get; set; }
    }

    public class ChartConfig
    {
        public ChartConfig()
        {
            tooltipPointFormat = "";
        }

        public string chartTitle { get; set; }
        public string defaultSeriesType { get; set; }
        public int width { get; set; }
        public int height { get; set; }
        public string tooltipPointFormat { get; set; }
        public bool pie_allowPointSelect { get; set; }
        public bool pie_dataLabelsEnabled { get; set; }
        public bool pie_showInLegend { get; set; }   
    }

Having above classes created, we can now use it to fill it with dummy data inside our entity controller. We can add as many series as we like, we may also configure desired chart’s width, height, series colors etc. Please note that controller’s method will be mapped based on defined html params (see above html configuration).

 public string Get(int clientId, bool fullInfo = true)
 { 
    var data = new ChartData();

    //configure chart
    data.ChartConfig = new ChartConfig()
    {
        chartTitle = "Clients",
        defaultSeriesType = "line",
        width = 800,
        height = 400,
    };

    #region add series
    data.Series = new List<Series>();

    data.Series.Add(new Series()
    {
        Name = "Series1",
        Color = ColorTranslator.ToHtml(Color.Navy),
        DashStyle = "ShortDash",
        Points = new List<PointData>()
    });

    data.Series.Add(new Series()
    {
        Name = "Series2",
        Color = ColorTranslator.ToHtml(Color.Red),
        Points = new List<PointData>()
    });

    data.Series.Add(new Series()
    {
        Name = "Series3",
        Points = new List<PointData>()
    });

    #endregion

    #region add dummy data
    var random = new Random();
    foreach (var serie in data.Series)
    {
        for (var i = 0; i < 50; i++)
        {
            serie.Points.Add(new PointData()
            {
                Date = DateTime.Now.AddDays(-i),
                Value = random.Next(10, 100)
            });
        }
    }
    #endregion

    var json = JsonHelper.ToJSON<ChartData>(data);

    return json;
}

Our chartAPI.js file will contain main JavaScript logic encapsulating data retrieval and constructing the chart object. The function getEntityData simply calls appropriate entity controller using $.getJSON function. When data is retrieved, the renderChart function is used to create chart object, fill it with data and configure according to obtained configuration.

 //this function is used by UI to trigger chart display
$(document).ready(function () {
    //process data on button click
    $(".chartButton").click(function (sender) {

        //read button attributes
        var entity = $(this).attr("data-entity");
        var targetDiv = $(this).attr("data-targetDiv");
        var params = $(this).attr("data-params");

        //call encapsulated function
        getEntityData(entity, params, function (data, status, response) {
            if (status === "success") {
                renderChart(data, targetDiv);
            }
            else if (status === "error") {
                //process the error here if needed

                alert('An error occurred: ' + response);
            }
            else if (status === "complete") {
                //attach your load completed events here if needed

                //alert('Data loaded!');
            }
        });
    });
});

//Gets data for the entity
//This function is encapsulated 
function getEntityData(entityName, params, callbackFn) {
    if (typeof callbackFn != 'function') {
        alert('Incorrect callback function attached!'); return;
    }

    //make sure correct path is set
    var root = location.protocol + "//" + location.host;

    $.getJSON(root + '/API/' + entityName + "?" + params, null)
    .success(function (data, status, response) {
        //create object from json
        var resultObject = JSON.parse(data);
        callbackFn(resultObject, status, response);
    })
    .error(function (e, status, response) {
        callbackFn(e, status, response);
    })
    .complete(function (e, status, response) {
        callbackFn(e, "complete", response);
    });
}

//Renders chart
function renderChart(data, renderTo) {
    var chart1 = new Highcharts.Chart({
        chart: {
            renderTo: renderTo,
            defaultSeriesType: data.ChartConfig.defaultSeriesType,
            width: data.ChartConfig.width,
            height: data.ChartConfig.height,
        },
        title: {
            text: data.ChartConfig.chartTitle,
        },
        tooltip: {
         pointFormat: data.ChartConfig.tooltipPointFormat,
        },
        plotOptions: {
                pie: {
                    allowPointSelect: data.ChartConfig.pie_allowPointSelect,
                    cursor: 'pointer',
                    dataLabels: {
                        enabled: data.ChartConfig.pie_dataLabelsEnabled,
                    },
                    showInLegend: data.ChartConfig.pie_showInLegend,
                },
            },
        xAxis: {
            title: {
                text: 'date'
            },
            type: 'datetime',
        },
        yAxis: {
            title: {
                text: 'value'
            }
        },
    });

    $.each(data.Series, function (index, item) {
        //add series
        chart1.addSeries({
            name: item.name,
            dashStyle: item.DashStyle,
            color: item.Color,
            data: []
        }, false);

        //add data points
        $.each(item.Points, function (index, pointData) {
            chart1.series[chart1.series.length - 1].addPoint([
                Date.parse(new Date(parseInt(pointData.Date.substr(6)))),//format date
                pointData.Value,
            ], false);
        });
    });

    chart1.redraw();
}

It is more than certain that you will have to adjust above solution to your specific requirements. At the same time it should give you a good starting point to implement your own solution based on this example.

I have attached project files below for your convenience.

jsChartMVC

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 4.00 out of 5)
Loading...Loading...

Entity validation engine using c# expression rules

Validating entity data is the common task when building business applications. Very often happens that we want to separate the rules from the app code to be able to quickly change it without affecting the application. In this article we will build simple validation engine that will be evaluating rules stored as linq expressions.

In our example we will hardcode the rules in separate .cs files (based on entity type). You may also want to store the linq expressions as a string in database and also compile the code dynamically from the string and then execute the rules based on the output dll file.

validation-engine

In our case we will hard code the rule in .css files. The following example shows the rule definitions for Client entity.

  public class ClientValidationRules : BaseValidationRuleSet<Client>
   {
        public ClientValidationRules(Client arg)
        {
            RuleList.Add(new ValidationRule<Client>(
                //set the rule
                a => a.Name.Trim().Any() && !a.Name.Any(b => char.IsLower(b))
                ////
                , arg)
                {
                    //set the result
                    Message = "Name cannot be all in uppercase!",
                    ResultTypeIfFailed = ValidationResultType.ERROR,
                    SuggestionString = arg.Name.ToLower()
                });

            RuleList.Add(new ValidationRule<Client>(
                //set the rule
               a => a.DateOfBirth.AddYears(18) > DateTime.Now
                ////
               , arg)
            {
                //set the result
                Message = "You must be at lest 18 years old to register!",
                ResultTypeIfFailed = ValidationResultType.ERROR,
                SuggestionString = string.Format("Wait {0} days", DateTime.Now.Subtract(arg.DateOfBirth).TotalDays.ToString("0"))
            });
        }
    }
}

You can see that this way allows us to easily define rule’s logic based on the entity data. We can also define suggestion message for each rule separately. By setting ValidationResultType as a rule result we can for example allow to save data if this is only a warning or prevent saving the data by the user if this is an error.

The implementation is as follows. Let’s start with generic ValidationRule class containing the rule data and delegate to run.

 namespace RuleEngine
 {
    public class ValidationRule<T> where T : class
    {
        public string Message { get; set; }
        public string SuggestionString { get; set; }
        
        public ValidationResultType ResultTypeIfFailed { get; set; }

        internal Func<T, bool> RuleDelegate { get; set; }
        internal T ObjectTovalidate { get; set; }

        public ValidationRule(Func<T, bool> rule, T arg)
        {
            RuleDelegate = rule;
            ObjectTovalidate = arg;
        }

        public bool RunRuleDelegate()
        {
            return RuleDelegate(ObjectTovalidate);
        }
    }
 }

It’s base class will have the “Run” method implementation.

 namespace RuleEngine
 {
    public abstract class BaseValidationRuleSet<T> where T : class
    {
        public List<ValidationRule<T>> RuleList { get; internal set; }

        /// <summary>
        /// Initiates rule list
        /// </summary>
        public BaseValidationRuleSet()
        {
            RuleList = new List<ValidationRule<T>>();
        }

        /// <summary>
        /// Run all added rules
        /// </summary>
        /// <returns></returns>
        public ValidationResult Run()
        {
            foreach (var rule in this.RuleList)
            {
                var result = rule.RunRuleDelegate();

                if (result)
                {
                    return new ValidationResult() { IsValid = false, ResultType = rule.ResultTypeIfFailed, Message = rule.Message, SuggestionString = rule.SuggestionString };
                }
            }

            return new ValidationResult() { IsValid = true, ResultType = ValidationResultType.OK };
        }
    }
}

Let’s define ValidationResult class that will be returned after running the rule delegate.

 namespace RuleEngine
 {
    public class ValidationResult
    {
        public bool IsValid { get; set; }
        public string SuggestionString { get; set; }
        public string Message { get; set; }
        public ValidationResultType ResultType { get; set; }
    }
}

The ValidationEngine class will have the entry point for different entity validation methods. You may also want to create generic function to pass the validating object to. You also can preload all rules at runtime and evaluate dynamically using AppDomain.CurrentDomain.GetAssemblies() method.

  public class ValidationEngine: ValidationEngineBase
  {
      public ValidationResult RunProjectRules(Project project)
      {
          return new ProjectValidationRules(project).Run();
      }
      public ValidationResult RunClientRules(Client client)
      {
          return new ClientValidationRules(client).Run();
      }
   }

In order to test our solution we can use console app. We also need to load some fake data into our entities.

     static void Main(string[] args)
        {
            var validationEngine = new ValidationEngine();

            #region load entities
            var client = new Client()
                  {
                      Name = "CLIENT NUMBER1a",
                      DateOfBirth = DateTime.Now.AddYears(-10)
                  };

            var project = new Project()
            {
                Name = "PROJECT NUMBER1",
                StartDate = DateTime.Now.AddDays(1),
                EndDate = DateTime.Now.AddDays(1)
            }; 
            #endregion


            #region validate client
            Console.WriteLine("Starting client validation");

            var result = validationEngine.RunClientRules(client);

            if (!result.IsValid)
            {
                Console.WriteLine(result.Message);
                Console.WriteLine(string.Format("Suggestion: {0}", result.SuggestionString));
            }
            else
            {
                Console.WriteLine("OK");
            }
            #endregion

            Console.WriteLine("--------------------");

            #region validate project
            Console.WriteLine("Starting project validation");

            result = validationEngine.RunProjectRules(project);

            if (!result.IsValid)
            {
                Console.WriteLine(result.Message);
                Console.WriteLine(string.Format("Suggestion: {0}", result.SuggestionString));
            }
            else
            {
                Console.WriteLine("OK");
            }
            #endregion

            Console.ReadLine();
        }

I have included project files bellow for your tests.


#Article update#

We can also create simple business rule engine in very similar way, here is the implementation:

 namespace RuleEngine
 {
    /// <summary>
    /// To be extended if needed
    /// </summary>
    public abstract class BusinessRuleEngineBase
    {
        
    }

    /// <summary>
    /// Runs set of rules for different entities
    /// </summary>
    public class BusinessRuleEngine : BusinessRuleEngineBase
    {
        /// <summary>
        /// Run rules for ScheduledFlight 
        /// </summary>
        /// <param name="scheduledFlight"></param>
        /// <returns></returns>
        public bool RunBusinessRulesFor(ScheduledFlight scheduledFlight)
        {
            return new FlightBusinessRules(scheduledFlight).Run();
        }
    }
 }

Business rule implementation containing evaluating and executing delegate

/// <summary>
/// Runs delegate on the specified business rule set
/// </summary>
/// <typeparam name="T"></typeparam>
public class BusinessRule<T> where T : class
{
    public string RuleName { get; set; }
    internal Func<T, bool> RuleDelegate { get; set; }
    internal Func<T, bool> ExecuteRuleDelegate { get; set; }
    internal T ObjectToValidate { get; set; }
    internal T ObjectToApply { get; set; }

    public BusinessRule(Func<T, bool> rule, Func<T, bool> ruleApply, T arg)
    {
        RuleDelegate = rule;
        ExecuteRuleDelegate = ruleApply;
        ObjectToValidate = arg;
        ObjectToApply = arg;
    }

    /// <summary>
    /// Determines if business rule should be applied
    /// </summary>
    /// <returns></returns>
    public bool ApplyRuleDelegate()
    {
        return RuleDelegate(ObjectToValidate);
    }

    /// <summary>
    /// Applies business rule on the current entity
    /// </summary>
    /// <returns></returns>
    public bool ApplyBusinessDelegate()
    {
        return ExecuteRuleDelegate(ObjectToApply);
    }
    
}

BaseBusinessRuleSet base class

/// <summary>
/// Base class for business rule sets
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BaseBusinessRuleSet<T> where T : class
{
    public List<BusinessRule<T>> RuleList { get; internal set; }

    /// <summary>
    /// Initiates business rule list
    /// </summary>
    public BaseBusinessRuleSet()
    {
        RuleList = new List<BusinessRule<T>>();
    }

    /// <summary>
    /// Run all added business rules
    /// </summary>
    /// <returns></returns>
    public bool Run()
    {
        foreach (var rule in this.RuleList)
        {
            var result = rule.ApplyRuleDelegate();

            //if rule is true, run business logic
            if (result)
            {
                rule.ApplyBusinessDelegate();

                return true;
            }
        }

        return false;
    }
}

And finally the business rule set-up class.

public class FlightBusinessRules : BaseBusinessRuleSet<ScheduledFlight>
{
    /// <summary>
    /// Setup business rules for ScheduledFlight
    /// </summary>
    /// <param name="arg"></param>
    public FlightBusinessRules(ScheduledFlight arg)
    {

        RuleList.Add(new BusinessRule<ScheduledFlight>(
            //set the rule
                a => (double)arg.Passengers.Count(p => p.Type == PassengerType.AirlineEmployee) / arg.Passengers.Count > arg.FlightRoute.MinimumTakeOffPercentage
            ,
            //execute rule
                 e =>
                 {
                     //lower the base price till 100
                     if (arg.FlightRoute.BasePrice >= 100)
                     {
                         arg.FlightRoute.BasePrice -= 10;
                     }

                     //add more logic here...

                     return true;
                 },
                arg) { RuleName = "ApplyDiscountFor_AirlineEmployee" });


        RuleList.Add(new BusinessRule<ScheduledFlight>(
            //set the rule
              a => a.FlightRoute.BasePrice == 0
              ,
            //execute rule
               e =>
               {
                   //enforce minimal base price
                   arg.FlightRoute.BasePrice = 100;

                   return true;
               },
              arg) { RuleName = "EnsureMinimalBasePrice" });

    }
}

RuleEngine

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...