Marcos Vainer Loeff

Monitoring long running PI AF SDK tasks on a web application

Blog Post created by Marcos Vainer Loeff Employee on Apr 20, 2016

Introduction

 

There has been some questions about how to create an ASP.NET Web Application that monitors long running tasks for retrieving a large amount of data from the PI System here on PI Square. There are many different solutions for this topic. On this blog post, I will show you how to achieve this goal using ASP.NET Signal R. As I have already written about how to use PI AF SDK with ASP.NET Signal R, I will focus on the web app itself.

 

ASP.NET Signal R was the selected technology to monitor long running tasks because it enables the server to start a communication with the clients. By using AJAX HTTP, requests to the server get a response but there is no way for a server to send autonomous messages for its clients. Therefore, when using ASP.NET Signal R there is no need for the client to keep making HTTP requests frequently in order to get the current status of the background task.

 

Creating the project on Visual Studio

 

Before we start, the web application that will be developed on this blog post is available on GitHub. Click here to visit the GitHub repository and download the source code package if you want.

 

Let's start by creating an ASP.NET Web Application project in Visual Studio 2013 or 2015.

 

 

Please select the Empty template, but do not forget to add folders and core references for MVC and Web API.

 

 

 

Using NuGet Package Manager, install jQuery, Bootstrap and ASP.NET Signal R.

 

Create a HomeController.cs file under the Controller folder and Index.cshtml under Views\Home folder with the following content:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;


namespace AFSDKProgressBarSignalR.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

HomeController.cs

 


@{
    Layout = null;
}


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PI AF SDK Progress Bar Application with Signal R</title>
    <link href="~/Content/bootstrap.css" rel="stylesheet" />
    <link href="~/Content/Site.css" rel="stylesheet" />
</head>
<body>
    <div class="container body-content">
        <div class="progress">
            <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
                0%
            </div>
        </div>
        <div class="jumbotron">
            <h1>AF SDK Progress Bar Demo</h1>
            <p id="processing-tag"></p>
            <p class="lead">Click on the button below to transfer all values from the PI Data Archive! </p>
            <p><button id="btn-retrieve-data" class="btn btn-primary btn-lg">Retrieve PI Data!</button></p>
        </div>
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - OSIsoft</p>
        </footer>
    </div>
    <script src="~/Scripts/jquery-2.2.3.js"></script>
    <script src="~/Scripts/jquery.signalR-2.2.0.js"></script>
    <script src="~/Scripts/bootstrap.js"></script>
    <script src="/signalr/hubs" type="text/javascript"></script>
    <script src="~/Scripts/app.js"></script>
</body>
</html>



Index.cshtml

 

The HomeController is the only Controller needed to display the only page of the web site.

 

On the Index web page, we have added the progress bar provided by Bootstrap and a button to start this process. Note that several CSS and JavaScript libraries such as jQuery, Twitter Bootstrap 3 and Signal R needed to be referenced on this page.

 

When the user clicks on the button, the progress bar should move from 0% and reach 100% when the process has finished its execution. But which background task are we talking about?

 

The background task

 

For the purpose of this blog post, we will take all PI Points from a PI Data Archive and try to get 150 values from each PI Point if possible. Create a new class PISystemWrapper on the project root and add a method called RetrieveData() which is its main method. Finally, we will add some properties that are going to be available for thee main thread to have enough information to monitor the process. The three public properties are:

 

  • CurrentPIPointNumber --> number of PI Points whose data was already retrieved
  • CurrentPIPointName --> the name of the current PI Point being processed within the for loop
  • TotalPIPoints --> The number of all PI Points of the PI Data Archive

 

The complete code snippet of PISystemWrapper.cs is:

 

using OSIsoft.AF.Asset;
using OSIsoft.AF.PI;
using OSIsoft.AF.Time;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Web;


namespace AFSDKProgressBarSignalR.
{
    public class PISystemWrapper
    {
        public PISystemWrapper()
        {
            CurrentPIPointNumber = 0;
        }
        public int CurrentPIPointNumber { get; private set; }
        public string CurrentPIPointName { get; private set; }
        public int TotalPIPoints { get; private set; }
        public int RetrieveData()
        {
            PIServer piServer = new PIServers()["MARC-PI2014"];
            IEnumerable<PIPoint> piPoints = PIPoint.FindPIPoints(piServer, "*", true);
            TotalPIPoints = piPoints.Count();
            CurrentPIPointNumber = 0;
            int totalValues = 0;
            foreach (PIPoint piPoint in piPoints)
            {
                CurrentPIPointNumber++;
                CurrentPIPointName = piPoint.Name;
                Debug.WriteLine(piPoint.Name);
                AFValues values = piPoint.RecordedValuesByCount(new AFTime("*-1500d"), 150, true, OSIsoft.AF.Data.AFBoundaryType.Inside, string.Empty, true);
                totalValues = totalValues + values.Count;
            }
            return totalValues;
        }
    }
}

 

Note that there are more efficient ways to obtain 150 values from each PI Point which require more advanced techniques such as bulk calls. Nevertheless, the simplest implementation was selected as the purpose of this blog post is to teach you how to monitor a background task.

 

The PIHub

 

The next step is to create a hub called PIHub with a method called GetData() which can be called by any web client. PIHub derives from the Hub class from ASP.NET Signal R.

 

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;


namespace AFSDKProgressBarSignalR.Hubs
{
    [HubName("piHub")]
    public class PIHub : Hub
    {
        public void GetData()
        {
            PISystemWrapper piSystemWrapper = new PISystemWrapper();
            Task<int> retrieveDataTask = Task.Factory.StartNew<int>(piSystemWrapper.RetrieveData);
            int lastCurrentPIPointNumber = 0;
            while (retrieveDataTask.IsCompleted == false)
            {
                if (piSystemWrapper.CurrentPIPointNumber - lastCurrentPIPointNumber > 100)
                {
                    lastCurrentPIPointNumber = lastCurrentPIPointNumber + 100;
                    Clients.Caller.updateProgressBar(piSystemWrapper.CurrentPIPointName, piSystemWrapper.CurrentPIPointNumber, piSystemWrapper.TotalPIPoints);
                }
                System.Threading.Thread.Sleep(1000);
            }
            retrieveDataTask.Wait();
            Clients.Caller.alertFinalValue(retrieveDataTask.Result);
        }
    }
}

 

A new task will start to run the RetrieveData() method from piSystemWrapper class and the main thread will check every second the state of the background task. It might send the results only to the caller through the C# Clients.Caller.updateProgressBar and  Clients.Caller.alertFinalValue methods which will call the JavaScript $.connection.piHub.updateProgressBar and  $.connection.piHub.alertFinalValue functions of the caller. Those functions are written on the app.js, which is also referenced on the Index.cshtml. The updateProgressBar function manipulate the DOM using jQuery in order to change the value of the progress bar and to show the current PI Point name being processed by the background thread. The alertFinalValue function just display an alert. The content of app.js is shown below.

 

 

var progressBar = $('.progress-bar');
$(function () {
    var broadcaster = $.connection.piHub;


    broadcaster.client.updateProgressBar = function (piPointName, count, totalCount) {
        $("#processing-tag")[0].innerText = 'Processing:' + piPointName;
        value = Math.round(count * 100 / totalCount);
        message = count + '/' + totalCount;
        progressBar[0].innerText = message;
        progressBar.css('width', value + '%').attr('aria-valuenow', value);
    }


    broadcaster.client.alertFinalValue = function (totalValues) {
        alert(totalValues + " events retrieved from the PI System");
    }


    $.connection.hub.start().done(function () {
        $('#btn-retrieve-data')[0].onclick = function () {
            broadcaster.server.getData();
        }
    });
});

app.js

 

 

When the user clicks on the only button of the page, the broadcaster.server.getData() JavaScript function is called which then calls the C# GetData() method of PIHub class. Finally, we need to load the ASP.NET Signal R libraries on the pipeline through the app.MapHubs() method defined on the Startup class that should be located on the project root.

 

 

using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;


[assembly: OwinStartupAttribute(typeof(AFSDKProgressBarSignalR.Startup))]
namespace AFSDKProgressBarSignalR
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HubConfiguration configuration = new HubConfiguration();
            app.MapHubs(configuration); 
        }
    }
}

 

 

Time to test!

 

Below is a screenshot of the web app we have developed on this blog post. We can see that on the moment that this screenshot was taken, 19474 out of 39012 PI Points were already processed!

 

 

 

 

Conclusion

 

Hopefully, you will find it easier to develop web pages that need to show background tasks information running on the web server by referring to this blog post. I know there are other ways to implement similar behavior but this one should be considered before drawing conclusions about which one you should use.

 

Finally, if you have any questions, just post a comment!

CurrentPIPointNum

ber

Outcomes