Click or drag to resize
Web services example

[This is preliminary documentation and is subject to change.]

This page will walk trough a running example of how to use SOAP web services in TOPICA.

This topic contains the following sections.

Producer / consumer

As described in Web Services:

  • A "web service producer" is an application / system, that exposes some web services (that some external system may call).

  • A "web service consumer" is an application / system, that calls some web services defined in an external system.

The preceding chapters (Built-in webservices, Auto generated webservices, Custom webservices) decribe the API exposed by TOPICA - this API is what makes it possible to use TOPICA as "web service producer".

A fully working demo system must contain both a "producer" and a "consumer". From the preceding chapters it follows, that TOPICA is a "web service producer" out of the box - and that you can create your own additional web services, if the standard API is not enough.

Implementing a web service consumer (e.g. to call web services) requires programming - but this is easy in .NET. TOPICA has several options to add custom .NET code (.ASPX files) - e.g. pre-processing, post-processing, custom webforms. Therefore, TOPICA may also be a "web service consumer".

Calling web services from .NET code

This is easy - especially when the web service you want to call exposes a WSDL file. Luckily, all web services implemented in .NET (including those exposed by TOPICA) expose WSDL "files" (dynamically). The WSDL file allows clients (= consumers) to discover methods, the structure of parameters, return values etc. which makes it possible for .NET to auto-generate "proxy" code to make life easier for programmers calling web services.

To call a web service from .NET, add a web reference. To use the old .NET 2.0 style, you must click "Add Service Reference...", click "Advanced", click "Add Web Reference...") - this will show the below window:

TODO: image...

Enter URL of the TOPICA web service, you want to add reference to - example: http://localhost/TOPICA/Webservices/LoginService.asmx.

The result is a window showing a list of operations (= web methods) in the referenced service. The web reference name is per default the name of the server - in the above example "localhost". You may choose to rename it to something more meaningful - e.g.. "TOPICALoginService". Then click the button "Add Reference".

Visual Studio will auto-generate "proxy" code. This proxy code exposes the web service as .NET classes - so that you do not have to work with the underlying XML your self. If you did not change the name previously, the auto-generated class will be named after the server (e.g. "localhost"). In that case, you should now rename the class to something more meaningful - e.g.. "TOPICALoginService".

It is possible to call web services without using "proxies" - this is not covered in this example (may be added later).

Calling web services from TOPICA

The generation of auto generated "proxy" code means, that you cannot directly call a web service from an .ASPX - e.g. one that implements post-processing. This is because the auto generated code must be compiled somehow.

This is how to do it:

  • Create a class library project in Visual Studio.

  • In this class library project, add a web reference (as described above).

  • You may export the classes, methods, etc. defined by the webservice - or you may choose to completely "wrap" the functionality of the web service in your .DLL (hide web service functionality, and export only your own "wrapper").

  • At this point (before you try to use the .DLL from TOPICA), it would be useful to test your .DLL from e.g. a console application - or using a unit test framework (e.g. the one that is built into Visual Studio). Both options are used in the example described below.

  • Build the class library project - this will result in a .NET assembly (a .DLL-file).

  • Copy this .DLL-file to a location, where it can be reached from your custom .ASPX-file (e.g. post-process). This location may be the same folder as the custom .ASPX-file - or the framework's bin folder. Add Imports declaration (in .ASPX-file) or using (in C# code behind, e.g. .ASPX.CS-file). Classes and methods in the .DLL-fil may now be used in your .ASPX or .ASPX.CS-file.

    Note Note

    Even if you do it right, experience shows, that Visual Studio does not always "detect" the presence of the .DLL immediately. This might be due to some caching. Try a couple of times - after a while the problem will disappear.

TOPICASoapWebServiceCaller solution

The solution TOPICASoapWebServiceCaller is built to demonstrate how to use TOPICA as a "web service producer". The solution contains the following projects:

  • TOPICASoapWebServiceCaller: source code for the .DLL, that "wraps" calls to TOPICA web services. It defines a class Controller, that uses TOPICA's built-in web services to log in and create users.

    For details on this, see below...

  • TOPICASoapWebServiceCallerCommand: a simple console application, that prompts for details of users to create, and then calls TOPICASoapWebServiceCaller to create the users in TOPICA.

    TOPICASoapWebServiceCallerCommand acts as an external system, that consumes web services exposed (produced) by TOPICA.

  • TOPICASoapWebServiceTest: a unit test project, that calls TOPICASoapWebServiceCaller to create users, read them back, and asserts that the objects read equal the objects written.

    TOPICASoapWebServiceTest acts as an external system, that consumes web services exposed (produced) by TOPICA.

TOPICASoapWebServiceCaller project details

After adding a web reference to the project, Visual Studio automatically creates an app.config file in the project, that among other things contains the full URLs of the each web service referenced. But even if this app.config is deployed along with the built .DLL, .NET does not use it for reading configuration values. .NET reads configuration values from the .config file(s) belonging to the process, that the .DLL runs in. So - forget about this app.config file.

For web application running under Internet Information Server, configuration settings are read from the Web.config file in the web home directory. In TOPICA there is a mechanism, that supplies these values with values read from the current configuration's Application.config and Server.config files.

The web service you want to call typically will depend on the environment. In development scenario you will want to call a test web service - and in production you will want to call a production web service. Such environment specific settings must be set up in TOPICA 's Server.config file.

Therefore, the Controller class takes 2 parameters: topicaURL and config. Note that topicaURL only specifies the URL to the TOPICA framework - not the entire path to the actual web service(s). This is because the .DLL may "wrap" calls to more than one web method (in this case: 2 calls: one to log in, and one to create employee - but this could be extended to call dozens of different web services . each with its own URL). The web services called must point to the SAME web home (and configuration) - if you for example logged in to one TOPICA configuration and receive a session guid, this session guid would not be valid in any other TOPICA configuration.

Therfore only the "main URL" identifying the TOPICA web home (= the framework folder) is a parameter - and the full URLs are computed (and set on the proxy service classes BEFORE making the actual calls) inside the .DLL. In this way you can switch from from one environment (web home url + configuration) to another by changing only the 2 parameters passed to the Controller class's constructor:

C#
public class Controller
{
    private string topicaURL;
    private string config;
    private Guid sessionGuid;

    public Controller(string topicaURL, string config)
    {
        this.topicaURL = topicaURL;
        this.config = config;
    }

    //.......
}

Note that the Controller class stores sessionGuid - so that it does not have to be passed as parameters to methods doing actual work.

Two web service references have been added to this project. The two proxy classes have been renamed to TOPICALoginService and TOPICAEmployeeService, so that the class names reflect the purpose of the web services (.ASMX files), they refer to.

Code snippet: log in:

C#
public bool LoginAsEmployee(string domain, string username, string passwordPlainText)
{
    var loginService = new TOPICALoginService.LoginService();
    if (!String.IsNullOrEmpty(this.topicaURL))
        loginService.Url = this.topicaURL + "/Webservices/LoginService.asmx";
    var logonInfo = loginService.LogInPlainPassword(this.config, domain, username, passwordPlainText, false);
    if (logonInfo.Status == TOPICALoginService.LogOnStatus.Ok)
    {
        this.sessionGuid = logonInfo.SessionGuid;
        return true;
    }
    else
    {
        this.sessionGuid = Guid.Empty;
        return false;
    }
}

Code snippet: Get reference to employee service, cached in Controller class:

C#
private TOPICAEmployeeService.EmployeeService employeeService;

public TOPICAEmployeeService.EmployeeService EmployeeService
{
    get
    {
        if (this.employeeService == null)
        {
            this.employeeService = new TOPICAEmployeeService.EmployeeService();
            if (!String.IsNullOrEmpty(this.topicaURL))
                employeeService.Url = this.topicaURL + "/Webservices/EmployeeService.asmx";
        }
        return this.employeeService;
    }
}

Code snippet: Call web sevice to create or update employee/user:

C#
public void CreateOrUpdateEmployee(string firstname, string lastname, string orgUnitType, string orgUnitCode, string domain, string username, string passwordPlainText)
{
    this.AssertLoggedIn();
    var employee = new TOPICAEmployeeService.Employee();
    employee.FirstName = firstname;
    employee.LastName = lastname;
    employee.OrgUnitType = orgUnitType;
    employee.OrgUnitCode = orgUnitCode;
    employee.Domain = domain;
    employee.Username = username;
    employee.Password = passwordPlainText;
    TOPICAEmployeeService.Profile[] profiles = {};
    TOPICAEmployeeService.OrgUnit[] orgUnits = {};
    this.EmployeeService.CreateOrUpdateEmployee(this.config, this.sessionGuid, employee, profiles, orgUnits);
}

This .DLL only "wraps" built-in web services. It may be easily be extended to wrap custom, configuration-specific web services. Or maybe better: an additional .DLL could be created for each configuration. Demonstration of this may follow in a later version.

Consumer example

As promised above, TOPICA may be used as both "web service producer" (as demonstrated above) and "web service consumer".

To act as a "web service consumer", some code must be added to a custom .ASPX-file. In this demonstration we create a post-process that can create employee users in some other TOPICA configuration.

In this demonstation we use 2 TOPICA configurations:

  • DemoSoapProducer is the simplest possible configuration: no forms, no custom .ASPX'es, no reports, no nothing. Since creating users is a standard functionality in TOPICA, and a web service with suitable methods exist, this web service may be used "out of the box". The TOPICA user interface contains functionality so search employees. This can be used to teste that employees are in fact created with the correct properties. So we do not need any forms or other configuration to test the system.

  • DemoSoapConsumer is slightly more complex:

    • DemoSoapConsumer contains one configured form (ConsumerForm.xml). This form contains fields for employee/user details (first name, last name, domain, user name, password).

    • DemoSoapConsumer contains a post-process (ConsumerForm.aspx) defined on the form (so the post-process runs every time the form is saved).

    • The above described .DLL TOPICASoapWebServiceCaller.dll must be placed along side the post-process .ASPX - or in the framework's bin-folder.

    • Server.config should contain these settings, that are used by the post-process (ConsumerForm.aspx):

      Key

      Value

      DemoSoapProducer.URL

      The URL of the TOPICA web home, in which to call web services. Example: "http://localhost/TOPICA".

      DemoSoapProducer.Config

      The config to use inside of the TOPICA web home. In this case: "DemoSoapProducer".

      DemoSoapProducer.LoginDomain

      The domain to log in with.

      DemoSoapProducer.LoginUsername

      The user name to log in with.

      DemoSoapProducer.LoginPassword

      The password to log in with.

    • Each time the form is saved, the post-process runs. The post-process extracts the field values from the saved record, gets the above configuration values, and uses the Controller class exposed by TOPICASoapWebServiceCaller.dll to create employees/users in the referenced TOPICA configuration.

    • To test the system, log in to the DemoSoapProducer configuration, search all employees, and verify that users entered in the configured form in DemoSoapConsumer are actually created.

Code snippet from ConsumerForm.aspx:

C#
<%@ Page Language="C#" AutoEventWireup="true" Inherits="TopicaPostProcessRedirect" %>

<%@ Import Namespace="CSC.SC.Enterprise.Utilities" %>
<%@ Import Namespace="CSC.SC.Enterprise.DataAccess" %>
<%@ Import Namespace="CSC.SC.TOPICA4.Library" %>
<%@ Import Namespace="CSC.SC.TOPICA4.DynamicData" %>
<%@ Import Namespace="CSC.SC.TOPICA4.Controllers" %>
<%@ Import Namespace="TOPICASoapWebserviceCaller" %>

protected void Page_Load()
{
    try
    {
        // error checks / debug output omitted...
        var row = base.ObjectContext.Record.DataRow;
        var firstName = DataRowUtil.GetString(row, "FirstName");
        var lastName = DataRowUtil.GetString(row, "LastName");
        var domain = DataRowUtil.GetString(row, "Domain");
        var username = DataRowUtil.GetString(row, "Username");
        var password = DataRowUtil.GetString(row, "Password");
        var url = base.ConfigurationString("DemoSoapProducer.Url", "http://localhost/TOPICA");
        var config = base.ConfigurationString("DemoSoapProducer.Config", "DemoSoapProducer");
        var loginDomain = base.ConfigurationString("DemoSoapProducer.LoginDomain", "");
        var loginUsername = base.ConfigurationString("DemoSoapProducer.LoginUsername", "Administrator");
        var loginPassword = base.ConfigurationString("DemoSoapProducer.LoginPassword", "Administrator");
        var controller = new TOPICASoapWebserviceCaller.Controller(url, config);
        if (!controller.LoginAsEmployee(loginDomain, loginUsername, loginPassword))
            base.ShowDebug("Could not log in");
        else
        {
            controller.CreateOrUpdateEmployee(firstName, lastName, "SYSTEM", "", domain, username, password);
            base.ShowDebug("Employee created or updated");
            controller.Logout();
        }
        base.Return(this.PanelContinue, this.HyperLinkContinue);
    }
    catch (Exception ex)
    {
        this.PanelException.Visible = true;
        this.LabelException.Text = ex.Message;
        if (ex.InnerException != null)
            this.LabelInnerException.Text = ex.InnerException.Message;
        return;
    }
}

To repeat some key points from post-processing: The post-process inherits from TopicaPostProcessRedirect (as all post-processes should). This makes it possible to use:

  • The calls to base.ConfigurationString() get values from the TOPICA .config files - in this case values must be defined i Server.config, because the values are environment dependent. This makes it possible to change which TOPICA configuration to hit by updating a few settings in Server.config.

  • When the .config file setting DynamicForm.PostProcess.Debug is set to true, base.ShowDebug(string) writes the parameter to the Response object, and the system makes a pause before resuming normal flow to give the user a chance to see the messages. This makes it possible for the post-process developer to display debug information from the post-process. The actual version of (ConsumerForm.aspx contains a lot of such debug information - omitted in the snippet above for brevity.

  • The call base.Return(Panel, HyperLink) implements resuming of normal flow of control after post-process execution.