avanderlinde

Getting Started with Developing a .NET Learn Web Services Client

Blog Post created by avanderlinde on Oct 25, 2016

Overview

The goal of this blog is to provide a kind of walk-through of the steps needed to build the Blackboard Learn .NET web services library and to construct a very basic and simple application (client) through which to pull and display user information to the console.

 

Provided will be a few base classes which can be used as a template to expand this walk-through into a shareable code-base for more advanced and complex web services clients. These files can be found on GitHub here.

 

What's Needed

  1. Microsoft Visual Studio 2012 or later (earlier versions may be compatible with this walk-through and the build process; I used Visual Studio 2015 Community Edition)
  2. Microsoft Web Services Enhancements (WSE) 2.0 SP3 or greater Download Web Services Enhancements (WSE) 3.0 for Microsoft .NET from Official Microsoft Download Center

 

Resources/Links

Tutorial: Build The C# SOAP Sample Library

GitHub - allenvanderlinde/wsclient: Base classes to accompany .NET Learn Web Services Client walk-through.

 

Building the Library

To begin we'll need to download the prepackaged web services library ZIP file which can be pulled from any Learn environment via the System Admin panel.

 

System Admin > Building Blocks module > Building Blocks > Proxy Tools > Download Sample Tools

 

You'll find within the ZIP several folders including "dotnetclient". From this folder we'll run through the process of building the Learn web services library we'll be using in our basic tool's solution. You will also notice a folder entitled "javaclient". We'll be focusing on .NET for the entirety of this walk-through, however there are several articles and discussions which also go over building the library for Java developers.

 

Following the original tutorial Tutorial: Build The C# SOAP Sample Library if you find that not all .cs files are generated, run sampleGenClient.cmd several times to make sure the necessary sources are pulled from the WSDLs as needed. You'll want to make certain you have the following in your automatically generated BbWsClient/gen folder:

 

AnnouncementWS.cs

CalendarWS.cs

ContentWS.cs

ContextWS.cs

CourseMembershipWS.cs

CourseWS.cs

GradebookWS.cs

NotificationDistributorOperationsWS.cs

UserWS.cs

UtilWS.cs

 

Note: The original tutorial also mentions how to modify the above web services source files to allow your client to run on load-balanced environments. For testing purposes on a single application server, excluding the load-balanced code will be fine.

 

When each source file is pulled that you'd like, go ahead and build the Visual Studio solution found in the BbWsClient folder. This will produce a .DLL that will be included in the actual source code of our tool.

 

 

Web Services Requests At-A-Glance

Essentially the goal of our tool for the purposes of this walk-through is to reach out remotely to a Learn environment, send a request for a single record or more of user information, store it, and display it in a meaningful way. We'll also be calling read-only methods this time around. A more technical, but still basic breakdown of this process could be seen as follows:

 

  1. Client establishes connection to remote environment.
  2. Client requests to log in.
  3. Tool registration or remote-user account existence is checked before allowing access.
  4. Entitlements are requested and stored via tool registration or pulled from level of permissions when logging in as a user.
    • If previously registered, tool entitlements are included within raw SOAP requests.
  5. The actual request is sent.
  6. The Learn server responds with the requested data through populating certain VOs (e.g., UserVO[] for users, CourseVO[] for courses) which are stored in memory on the client PC.
  7. The client sends request to log out.
  8. The established connection is safely broken with successful log-out.

 

We'll be using the same methods found in the web services base classes you can find here. This walk-through will go over just the bare-minimum needed to make a successful request and leave it up to you to integrate any additional features.

 

 

Saying Hello...

To begin, we want to 1) establish a connection to the remote Learn environment, 2) identify ourselves, and 3) register. This walk-through focuses on the web services client being a proxy tool as opposed to impersonating an admin-level user through which to make our user information request(s).

 

It is possible to produce a proxy tool record which will be visible in your System Admin panel should you simply try to connect to a web service. Doing so is not recommended unless it's a first step in testing whether your tool can get out a connection request in the first place.

 

Note: Simply hitting the server as a proxy tool without proper registration will deny any later connections entitlements since they must specifically be requested during the registration process.

 

Our first step is to write a method that reaches out to the server, establishes a connection, and generates a session. It is entirely up to you how to handle variables for your own tool, and it is never recommended to pass plaintext passwords into an application. Learn's web services handles the password nicely over-the-wire since without an SSL-encrypted connection, the server will never allow requests to get through from the client (no trust relationship will be established).

 

To make this step quick-and-easy for us for the walk-through, we'll be assuming that we've taken the host, a password for the tool, the word "register", and the global password required as configured in "Manage Global Properties" in your Proxy Tool System Admin area, from the command-line.

 

>wsclient https://mybbenv.blackboard.com toolPassword register globalPassword

 

Learn's web services calls handle a great deal of the grunt work in the background, so for us, registering our tool is fairly straightforward.

 

1. Connecting.

Notes:

a. WS is a WebserviceWrapper object.

b. vendor and tool are some arbitrary strings.

c. lifetime has a value of 1 hour.

 

public static bool connectAndInitialize(string _host)
{
    host = _host;
    try
    {
        // Instiantiate the primary web services wrapper
        WS = new WebserviceWrapper(host, vendor, tool, lifetime);
    }
    catch(Exception e)
    {
        Console.WriteLine("Error occurred: {0}", e.Message);

        return false;
    }

    // Initialize the primary web services object
    WS.initialize_v1();

    if(WS.debugInfo().Equals("NO session"))
    {
        Console.WriteLine("Error: Unable to generate session.");

        return false;
    }

    sessionId = WS.debugInfo();
    Console.WriteLine("Session generated: {0}", sessionId);

    return true;
}

 

What we're doing here is building our own WebserviceWrapper object with our host, some vendor and tool name for the web services client, and an expected lifetime of 1 hour. initialize_v1() does all the magic for us handling the actual connection. Our goal is to have the services give us back a session ID string. Checking that that string doesn't equal "NO session" means we've made a connection.

 

Referring to Program.cs of the wsclient project, we use a static class to run certain web services tasks with, so we could call the above with:

 

bool success = WebServices.connectAndInitialize(host);

 

Our host address is the only thing passed in for example's sake. Our tool and vendor strings and our lifetime are constant identifiers in the WebServices class in this project for convenience.

 

At this point, with no failure, we've established a connection to the environment's web services and made the system aware that we'll be trying to pull data soon. Our next step is to register, which will allow us to specify which entitlements we'll be using for the tool so that we can pull the data we're looking for.

 

2. Registering.

Notes:

a. We're assuming we've already checked that the third argument string to wsclient is "register".

b. "This is my web services tool." is an arbitrary description of the tool we're registering (required for registration).

c. sharedSecret is the password gathered from the command line we want to use for the tool.

b. WebServices.logout() is also used to safely break the connection and check for any issues regardless whether our tool is actually logged in.

 

try
{
    if(!WebServices.register("This is my web services tool.", args[3], sharedSecret))
    {
        Console.WriteLine("\nUnable to register tool in host {0}.\n\tMore info: {1}\n\t*Global password may be invalid.", host, WebServices.getWS().getLastError());

        return -1;
    }
    else
    {
        Console.WriteLine("\nRegistered tool successfully!");
        WebServices.logout();

        return 0;
    }
}

 

Here we're assuming that the string "register" was passed into wsclient as the third argument, and we pass the global registration password into the WebServices.register() method as args[3]. The only reason for this is to specify to our tool that we wish to register a new proxy tool connection. Future runs of wsclient will involve just the host, tool password, and user ID of the user we'd like basic information on. Again, it's up to you to determine how you'd like your tool to receive input for logging in and requesting data.

 

Note: Once a proxy tool is registered, it will need to be set to "Available" in the GUI after it appears in the Proxy Tools list in your System Admin > Building Blocks module > Building Blocks > Proxy Tools page. This is also a good opportunity to make sure the requested entitlements were requested successfully as they'll appear as a list on the same "Edit" page for the proxy tool.

 

We're going to take a closer look at what WebServices.register() actually does as that's where our direct Blackboard web services calls takes place for this walk-through.

 

Notes:

a. toolRegResult is a Learn RegisterToolResultVO object that stores information about our registration previously declared as a private member of the WebServices class.

b. initialSecret is the "global registration password" found in System Admin > Building Blocks module > Building Blocks > Proxy Tools > Manage Global Properties.

c. sharedSecret is the password that will be used for the tool.

d. entitlements is the string array of specific Learn identifiers which represent the actions we want to perform on the data we're trying to pull. (Details follow example.)

 

public static bool register(string desc, string initialSecret, string sharedSecret)
{
    toolRegResult = WS.registerTool(desc, initialSecret, sharedSecret, entitlements, null);

    if(toolRegResult.status)
    {
        return true;
    }
    else
        return false;
}

 

Here we simply call the WebserviceWrapper object's registerTool() method which takes a set of its own arguments. Since we're using a proxy tool as the medium through which to pull our user data, we're passing in null for the final parameter of registerTool().

 

Afterwards we check that our RegisterToolResultVO object has a status of true to confirm that our tool was registered without issue.

 

Note: Don't forget to make your registered tool "available" in the GUI on the Proxy Tools page.

 

3. Entitlements.

In order to fully register a tool certain entitlements, or privileges, must be requested alongside the registration. These are identifiers which Learn recognizes as permissible actions when pulling data from its web services. Certain web services calls, whether reading data or writing data, will fail should the necessary entitlement not be a part of the requesting tool's registration. They are fairly self-explanatory, so I'll simply list what this walk-through uses below. A fuller list can be found in the comments at Proxy Tools

 

{
    "Context.WS:emulateUser", "Context.WS:logout", "Context.WS:getMemberships",
    "Context.WS:getMyMemberships", "Util.WS:checkEntitlement", "User.WS:getServerVersion",
    "User.WS:initializeUserWS","User.WS:saveUser", "User.WS:getUser",
    "User.WS:deleteUser", "User.WS:saveObserverAssociation", "User.WS:getObservee",
    "User.WS:deleteAddressBookEntry", "User.WS:getAddressBookEntry", "User.WS:saveAddressBookEntry"
}

 

Note: Make sure that your Util.WS web service is available and that you initialize it in future requests so that data sources can be exposed.

 

 

Pulling Data

Now that our tool is registered and our entitlements have been granted, we can actually request a user's account data. The process is very straightforward and almost identical for all "read" methods with Learn's web services.

 

1. Build a new filter.

2. Define filter properties.

3. Specify the "thing" you're looking for based upon filter type.

4. Build the necessary VO to hold response data.

5. Do something with your data.

 

With a bit more pseudocode, we'll go through these steps and then discuss any details.

 

Notes:

a. RequestType is a custom enumeration which has identical values for constants as those found in your web services documentation.

b. GET_USER_BY_NAME_WITH_AVAILABILITY indicates to pull a user or users by their username (as opposed to PK1).

 

            uf = new UserFilter();
            uf.filterType = (int)RequestType.GET_USER_BY_NAME_WITH_AVAILABILITY;
            uf.filterTypeSpecified = true;

            /* Since we're only looking for 1 user's information,
             * we instantiate index 0 of the string[] for the filter. */
            try
            {
                uf.name = new string[] { userId };
            }
            catch(NullReferenceException)
            {
                return false;
            }

            /* Build the UserVO[] with information about
             * the requested user. */
            try
            {
                users = WebServices.getUserWS().getUser(uf);
                if(users == null)   // Most likely services aren't available for the tool
                    throw new NullReferenceException();
            }
            catch(NullReferenceException)
            {
                return false;
            }

            return true;

 

Ignoring basic exception handling, what were doing is using our UserFilter object to store criteria we want to pull data on. The member filterType is vital as that lets Learn known how we're requesting the data (e.g., in this case, by username).

 

The UserFilter class also has a member name and member id (among others). name is a string array we populate with usernames we want account information on. id is also a string array but refers to the user's PK1 and is in the format "_XXXXXX_1".

 

Recollecting that WebServices is a static utility class for this walk-through, we see that it's really just getting a handle to the UserWrapper object member. We pass in our filter which defines the "things" we want data on and have Learn's web services build our UserVO[] with the request's results (e.g., users).

 

 

Look at this!

I hope this walk-through has been a useful resource for you in your Learn web services journey and hopefully has provided you with some additional insight into how the system works.

 

At this point we've written a tool which connects to Learn's web services, registers itself, and pulls some user data. From here we can manipulate that data any way we'd like by referring to an index in the UserVO array and specifying the member we're interested in (i.e., users[0].userBatchUid).

 

When we know what we're looking for, we could format the strings as needed to even write a feed file....

 

But I'll leave that to you.

Outcomes