Introduction to PixEdit Server’s web service interface

  • 545 Views
  • Last Post 28 June 2018
Anders posted this 28 June 2018

Introduction

In this article we will show how to connect to the PixEdit Server Core and configure a job ticket to demonstrate document processing and optimization.

PixEdit Server and PixEdit Converter Server hosts a web service interface to allow clients to connect and submit documents for processing and conversion. The interface provides a set of methods to allow clients to create, submit and monitor document processing jobs. Document processing could for instance be OCR, de skew, crop, resize, barcode separation or a simple file format conversion.

In fact, the web service interface is used by the file pickup service ‘PixEdit Input Connector’ and the ‘PixEdit Server Manager’ embedded in the product’s installation.

 

Before you start

The article requires basic knowledge of programming in C# with Microsoft Visual Studio. Here, we will use Visual Studio 2017, but you should be able to follow along with other versions also. Still, we recommend using VS 2013 or newer.

 You should also have the newest version of PixEdit® Server installed in your development environment.

Set up your application

Open up Visual Studio and choose File, New, Project. In the left pane of the New Project dialog, browse to Visual C#, Windows and select it. Select Windows Forms Application in the right pane, choose a name and location for your project and click OK. (In this article, we will call our application MyPixEditServerClientApp.) 

Visual Studio

Visual Studio prepares your application and eventually, a basic, empty form called Form1 comes up. We rename the form to PSClientApp and set its title to PixEdit Server Client Application.

Visual Studio

 

Connect to PixEdit Server

First make sure PixEdit Server is running, preferable on your development computer.

PixEdit Server Core running service

To access the web service interface, we will need to know its endpoint/URL address. The PixEdit server Core writes this to the Windows Event Viewer when it starts. Like this:

Listening for messages at:

net.tcp://tsws19:8080/PixEditWebService (NetTcpBinding)

net.tcp://localhost:8080/PixEditWebService/mex (MetadataExchangeTcpBinding)

http://tsws19:8180/PixEditWebService (WSHttpBinding)

PixEdit Server provides two possible bindings. Net.tcp which is a duplex binding and will work inside your intranet. The http binding is simplex and will work for server/clients outside your intranet. In this tutorial we will go with the net.tcp binding which also allow us to implement some nice callback features creating a very tight and responsive client application.

Important

For Visual studio to detect the PixEdit Web service interface you will need to add a line in PixEditService.exe.config file. The file is located in the installation folder: ‘C:\Program Files\Techsoft\PixEdit Server\Core’

Open the file for editing and locate the following line of code:

<endpoint address="" binding="netTcpBinding" bindingConfiguration = "NetTcpBinding_IPixEditWebService" bindingNamespace="PixEditWebServiceLibrary" contract="PixEditWebServiceLibrary.IPixEditWebService"/>

Just underneath this line, add the following:

<endpoint address="net.tcp://localhost:8080/PixEditWebService/mex" binding="mexTcpBinding" contract="IMetadataExchange"/>

And then restart the ‘PixEdit Server Core’ service to apply the config change.

Now head back to Visual studio. In the solution explorer right click and select ‘Add Service reference’

 Visual Studio add service reference

In this dialog paste the endpoint address and press the ‘Go’ button to discover the PixEditWebService

Visual Studio add service reference2

Then rename the Namespace to ‘PixEditWebServiceReference’ and press ok to add the service reference to the project.

Adding some code to get things up and running

With the web service up and running its time to add some code to our sample client. In the solution explorer right click the form1.cs and select view code. 

First, we need to add a reference to the web service

using MyPixEditServerClientApp.PixEditWebServiceReference;

Since we are connecting using the net.tcp binding we also need to implement the callback features in the web service. We need to derive from the IPixEditWebServiceCallback and set the callback attribute, like this:

[CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Reentrant)]
public partial class PSClientApp : Form, IPixEditWebServiceCallback

Now above the form load handler add an instance of our PixEdit web Service a GUID to hold a user id and job status subscriber id.

PixEditWebServiceClient _server;
Guid _userID;
Guid _jobStatusSubscriberID;

(Continued in next post..)

  • Liked by
  • Andreas
Order By: Standard | Newest | Votes
Anders posted this 28 June 2018

In the form load handler add the following code to connect to the service and validate a user. PixEdit Server has its own user system to identify which processing jobs each user has submitted for processing. The user id is needed for about every method in this tutorial but we will not get into any details of the user management besides that. The ‘admin’ user is a default user created by the core service on the initial startup.

We also need to make sure we subscribe to server callbacks.

private void PSClientApp_Load(object sender, EventArgs e)
{
    InstanceContext context = new InstanceContext(this);
        _pixClient = new PixEditWebServiceClient(context, "NetTcpBinding_IPixEditWebService", "net.tcp://localhost:8080/PixEditWebService");
        _clientUserID = _pixClient.ValidateUser("admin", "admin");
    if (_server != null)
            _jobStatusSubscriberID = _server.SubscribeNotifications(_userID);
}

 Implement the 3 callback functions the interface provide.

public void OnServerStatus(string status)
{
}

public void OnJobStatusChanged(string status)
{
}

public void OnJobProfilesChanged()
{
}

 

Ready to do some document processing

Our application should now compile and run okay but it doesn’t do very much besides from connecting to PixEdit Server and validating the default user.

To process documents, you will need to set up a job ticket. The ticket specifies the imaging and optimization operations for the PixEdit core to apply to the document.

In this sample we would like to apply the following operations:

 

Remove blank pages Remove Blank Pages Usually needed when scanning both front and backside of your paper documents

 

Remove Black Border Remove Black Borders Removes black borders produced by most high-end document scanners. This function also corrects for any skew that may arise during the scan process

 

Erase borders Erase Borders This will delete any remaining noise near the document edges

 

Resize to standard pages sizes Resize to standard page sizes This function will snap each page in the document to exact paper sizes, like for instance A4 and A3. This could be required if the processed document is being handled by other services in the system

 

Text recognition (OCR) Text recgontion (OCR) Recognize text to make our processed documents searchable

 

Save properties Save properties Specify saving properties like naming convention, compression and type of file format

 

There is no public specification for the job ticket xml. But the PixEdit Server Manager tool uses the file format when users design job profiles there. So, one can simply use the tool to investigate the different processing options and later have a look inside the Jobprofiles.xml located in the product’s working folder c:\DocServer

We add a button and 2 labels to the form

Name the button ‘btnCreateJob’ and one of the labels ‘labelStatus’. The other label we can leave with its default name, just set the display text to “Job status”. Now double-click the button and Visual Studio will add a button click event handler. Inside this handler we apply the following code:

(Continued in next post...) 

Anders posted this 28 June 2018

private void btnCreateJob_Click(object sender, EventArgs e)
{
    // This is the job ticket. It specifies the imaging/processing to apply to the document. 
    // Each command is specified with its parameters. The paramaters may of course be altered and extracted to variables for custom application behaviour.

    string documentFile = "ScannedDoc1.pdf";
    string xmlJobTicket = @"<?xml version=""1.0"" encoding=""utf-8""?>
    <ProcessSubmission xmlns:xsd =""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
        <Title>Convert job for " + documentFile + @"</Title>
        <Filenames>
            <string>" + documentFile + @"</string>
        </Filenames>
        <ProcessProfile>
            <commands>
            <ProcessBase xsi:type=""ConvertProperties"">
            <CmdName>Open/Convert Document</CmdName>
            <CmdInfo>Open/Convert document and prepare for further processing</CmdInfo>
            <TrackWordchanges>Ignore</TrackWordchanges>
            <Showmarkup>Allow</Showmarkup>
            <ExcelPageorientation>Landscape</ExcelPageorientation>
            <SaveOutlookAttachments>true</SaveOutlookAttachments>
            <ReplaceLinksWithText>false</ReplaceLinksWithText>
            <AutomaticMsOffTimeout>true</AutomaticMsOffTimeout>
            <OfficeAppTimeout>120</OfficeAppTimeout>
            <ThrowOutlookAttachmentError>true</ThrowOutlookAttachmentError>
            <ProgUsedHtmlconversion>Word</ProgUsedHtmlconversion>
            <ThrowZipDocumentError>true</ThrowZipDocumentError>
            <AutoCADuninitLayout>Ignore</AutoCADuninitLayout>
            <UseAcadCustomSettings>false</UseAcadCustomSettings>
            <CustomAcadsettings>
                <Papersize>ISO_A3</Papersize>
                <Orientation>Landscape</Orientation>
                <PlotArea>Extents</PlotArea>
                <Scale>s1_1</Scale>
                <FitToPaper>true</FitToPaper>
                <ScaleLineWeights>false</ScaleLineWeights>
                <CenterPlot>true</CenterPlot>
                <PlotLineWeights>true</PlotLineWeights>
                <PaperspaceLast>true</PaperspaceLast>
            </CustomAcadsettings>
            <PDFRenderingDpi>300</PDFRenderingDpi>
            <PDFRetainDpi>true</PDFRetainDpi>
            <PDFSmoothText>false</PDFSmoothText>
            <PDFTimeoutSeconds>1200</PDFTimeoutSeconds>
            <VerifyPDFA1b>false</VerifyPDFA1b>
            <VerifyPDFA2b>false</VerifyPDFA2b>
            <VerifyPDFA3b>false</VerifyPDFA3b>
            <ZipArchiveMergeContent>true</ZipArchiveMergeContent>
            <XPSDigitalRender>true</XPSDigitalRender>
            </ProcessBase>
            <ProcessBase xsi:type= ""RemoveBlankPages"" >
            <CmdName>Remove Blank Pages</CmdName>
            <CmdInfo>Remove blank pages.This can be useful when scanning in duplex mode</CmdInfo>
            <Graphics>0.06</Graphics>
            <LeftTopMargins>
                <Width>5</Width>
                <Height>5</Height>
            </LeftTopMargins>
            <RightBottomMargins>
                <Width>5</Width>
                <Height>5</Height>
            </RightBottomMargins>
            </ProcessBase>
            <ProcessBase xsi:type= ""RemoveBlackBorders"" >
            <CmdName>Remove Black Borders</CmdName>
            <CmdInfo>Removes black borders from a scanned document</CmdInfo>
            <Crop>true</Crop>
            <MaxDeskewAngle>4</MaxDeskewAngle>
            </ProcessBase>
            <ProcessBase xsi:type= ""EraseBorders"" >
            <CmdName>Erase Borders</CmdName>
            <CmdInfo>Erase Borders may be used to erase any remaining graphics noise near the document edges</CmdInfo>
            <LeftBorder>3</LeftBorder>
            <TopBorder>3</TopBorder>
            <RightBorder>3</RightBorder>
            <BottomBorder>3</BottomBorder>
            </ProcessBase>
            <ProcessBase xsi:type= ""ResizeToStandardPageSize"" >
            <CmdName>Resize to standard page sizes</CmdName>
            <CmdInfo>All document pages will be adapted to standard sizes to create a uniform document</CmdInfo>
            <MaximumDeviation>5</MaximumDeviation>
            <ResizeImage>false</ResizeImage>
            </ProcessBase>
            <ProcessBase xsi:type= ""OCR"" >
            <CmdName>Text Recognition(Ocr)</CmdName>
            <CmdInfo>Apply text recognition to make your documents searchable</CmdInfo>
            <LargeObjectsFilter>30</LargeObjectsFilter>
            <ExportOcrTextFile>false</ExportOcrTextFile>
            <ExportRemoveLinefeed>false</ExportRemoveLinefeed>
            <ExportPageSeparator>- Page { 0} -</ExportPageSeparator>
            <ExportOnly>false</ExportOnly>
            <OCRTimeout>0</OCRTimeout>
            </ProcessBase>
            <ProcessBase xsi:type=""SaveProperties"">
            <CmdName>Save to Output Directory</CmdName>
            <CmdInfo>Save the document in the output directory</CmdInfo>
            <FileFormat>PDF_A1b</FileFormat>
            <ImageQuality>85</ImageQuality>
            <AlternativeSavePath />
            <DocnameFormat>OrgFilename</DocnameFormat>
            <AddOriginalFileExtention>false</AddOriginalFileExtention>
            <Prefix />
            <Suffix />
            <IncrementStartNumber>0</IncrementStartNumber>
            <Digits>4</Digits>
            <Recompress>false</Recompress>
            <MovePdfAVerifiedFiles>true</MovePdfAVerifiedFiles>
            </ProcessBase>
        </commands>
        <ID>428cdbfa-8fc6-4ead-915d-fd43fd6518b0</ID>
        <Name>jobProfile</Name>
        <DocumentBackup>false</DocumentBackup>
        <Icon>BlackAndWhite.png</Icon>
        </ProcessProfile>
    </ProcessSubmission>";

    //Create processing job using our specified ticket
    Guid jobID = _server.CreateJob(_userID, xmlJobTicket);

    string jobDocFolder = _server.GetJobDocumentsFolder(_userID, jobID);

    // We have embedded a scanned test document in this project. Get it !
    string filePathname = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "..", "..", "Documents", documentFile);

    // Upload the document by using PixEdit Server chunk uploader.
    // This is a better way instead of copying the file to the job's working folder
    labelStatus.Text = "Uploading document...";
    StreamUploadDocumentToServer(jobID, filePathname);

    //Submit for processing. The Core service will add it to its queue and handle it when its ready.
    // When the job is submitted the core will invoke callbacks and usefull information can be retrieved on the OnJobStatusChanged handler
    _server.SubmitJob(_userID, jobID);
}

(Continued in the next post...)

Anders posted this 28 June 2018

At the beginning of the job ticket xml we put our filename, which is also embedded inside this solution. Then with the _userID and the job ticket xml string we call the ‘CreateJob’

Guid jobID = _server.CreateJob(_userID, xmlJobTicket);

With the returned jobID call GetJobDocumentsFolder

string jobDocFolder = _server.GetJobDocumentsFolder(_userID, jobID);

GetJobDocumentsFolder will return the internal path where we upload the document to process. In this sample we have implemented a chunk uploader to stream the file to the documents folder, but you may also use a simple file copy operation. In that case PixEdit Server’s working folder must be shared with the Windows user executing this client sample. With the document in place we can now submit the document for processing

_server.SubmitJob(_userID, jobID);

Collecting the result file(s) and purging the job

As soon as our job is submitted for processing the server core will invoke our callback function ‘OnJobStatusChanged’ along with a status xml string. In this sample we will show how to decode the essentials in order to provide some basic progress info, and how to collect the finished document.

In the ‘OnJobStatusChanged’ handler we load the status xml in a .Net XMLDocument. Then we use the methods there to search for "DocProcessStatus/Status", "DocProcessStatus/JobID” and "DocProcessStatus/ OutputFilenames"

The status is written to our simple ‘labelStatus’ and when its delivered, we collect the OutputFilenames and copy them to our client folder provided in this sample. When the result file(s) is copied we purge the job

_server.PurgeJob(_userID, jobID);

This will set the jobID in “to be purged” state and the server core will delete it in about 30 seconds.

public void OnJobStatusChanged(string status)
{
    // Simple decoding of the status xml sent from the server.
    // We will only fetch the job status here to provide a very simple progress and to know when the document is finished and ready for our client to collect

    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(status);

    string jobstatus = "";
    // Get status from XML
    try
    {
        jobstatus = xmlDoc.SelectSingleNode("DocProcessStatus/Status").InnerText;
    }
    catch (Exception)
    {
    }
    // Updates progress label
    BeginInvoke(new Action(delegate
    {
        labelStatus.Text = jobstatus;
    }));

    // Job finished get document(s)
    if (jobstatus == "Delivered")
    {
        // Get Job ID
        Guid jobID = new Guid(xmlDoc.SelectSingleNode("DocProcessStatus/JobID").InnerText);

        // Copy document(s) to our client folder
        string jobFolder = _server.GetJobDocumentsFolder(_userID, jobID);

        // Get output filenames. In case of a document separation job there will multiple output filenames. 
        // Make sure we are able to collect them all
        XmlNodeList outputfileNodes = xmlDoc.SelectSingleNode("DocProcessStatus/OutputFilenames").ChildNodes;
        foreach (XmlNode outputfileNode in outputfileNodes)
        {
            // Copy each file from the internal output to client specified folder
            if (!string.IsNullOrEmpty(outputfileNode.InnerText))
            {
                string srcFilePathname = Path.Combine(jobFolder, outputfileNode.InnerText);
                string dstFilePathanme = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "..", "..", "Processed Docs", outputfileNode.InnerText);
                File.Copy(srcFilePathname, dstFilePathanme);
            }
        }
        // Purge Job and remove it from PixEdit Server internal folders
        _server.PurgeJob(_userID, jobID);
    }
}

To keep this tutorial as simple as possible we have not implemented any exception handlers. But in a production environment this will of course be necessary. The entire sample project with comments is available from GitHub

PixEdit Server WCF client sample

If you have questions or you need help with your project please feel free to contact us.

 

Close