LtiLibrary is now a NuGet Package

To make it even easier to add IMS LTI support to your learning app, I wrapped LtiLibrary into its own NuGet Package. To add LtiLibrary to your application, run the following command in the Package Manager Console:

PM> Install-Package LtiLibrary

There is also an LtiLibrary.Sample package which adds sample MVC and Web Forms to an empty ASP.NET Web Application.

PM> Install-Package LtiLibrary.Sample

Using the LtiLibrary.Sample Package

Want to poke around a simple sample application? Try this:

  1. In Visual Studio start a New Project.
  2. Select the ASP.NET Web Application template:
    New Project dialog in Visual Student 2013
  3. Select the Empty template and add folders and core references for Web Forms, MVC, and Web API:
    New ASP.NET Web Application dialog in Visual Student 2013
  4. After the project is created, enter the following command in the Package Manager Console to install LtiLibrary.Sample:
    Package Manager Console in Visual Studio 2013
  5. Run the project and click on the Launch link to launch a sample tool.
    LtiLibrary.Sample MVC home page

LtiLibrary.Sample has MVC, Web Forms, and Web API sample code that implements LTI 1.0, 1.1, and draft 1.2 services. Here is a table of contents for the sample files:

  • LTI 1.0 Tool Consumer and Tool Provider
    • MVC
      • Tool Consumer
        • Controllers\ConsumerController.cs
        • Views\Consumer\Launch.cshtml
      • Tool Provider
        • Controllers\ProviderController.cs
        • Views\Provider\Tool.cshtml
    • Web Forms
      • Tool Consumer
        • WebForms\Launch.ashx
      • Tool Provider
        • WebForms\Tool.aspx
  • LTI 1.1 Outcomes Service
    • Web API and MVC
      • Tool Consumer
        • Controllers\OutcomesApiController.cs
      • Tool Provider
        • Views\Provider\Outcomes.cshtml
  • LTI 1.2 Tool Consumer Profile
    • Web API
      • Tool Consumer
        • Controllers\ToolConsumerProfileApiController.cs

If you are looking for a more robust example of a Tool Consumer and Tool Provider, check out the LtiSamples code in CodePlex.

Let me know if you find this useful.

Posted in Uncategorized | Tagged , , , , , | 7 Comments

Quickly add LTI consumer outcomes support to your ASP.NET MVC web site

The IMS LTI™ outcomes service allows consumers (such as Learning Management Systems) and providers (such as an online test) to send score information back and forth. Teachers love this! No more manually entering grades into the gradebook.

In this post, I show how to use my .NET LtiLibrary to add consumer outcomes support. My previous post, Quickly add LTI consumer functionality to your ASP.NET MVC web site, describes how to add the LtiLibrary project to your solution.

Add WebApi support to your web site

Use the Package Manager to add WebApi support to your web site project,

image

Then add a WebApiConfig class to the App_Start folder. If you already have one, be sure to enable the XmlSerializer (LTI outcomes use XML formatting),

using System.Web.Http;

namespace WebApplication1
{
   public static class WebApiConfig
   {
      public static void Register(HttpConfiguration config)
      {
         // Use XmlSerializer instead of DataContractSerializer
         config.Formatters.XmlFormatter.UseXmlSerializer = true;

         config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
            );
      }
   }
}

And finally, be sure to call WebApiConfig.Register when the web site starts,

using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace WebApplication1
{
   public class MvcApplication : System.Web.HttpApplication
   {
      protected void Application_Start()
      {
         // This needs to appear before the non-api routes
         WebApiConfig.Register(GlobalConfiguration.Configuration);

         AreaRegistration.RegisterAllAreas();

         FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
         RouteConfig.RegisterRoutes(RouteTable.Routes);
         BundleConfig.RegisterBundles(BundleTable.Bundles);
      }
   }
}

Implement an OutcomesController

LtiLibrary contains an abstract OutcomesControllerBase with 4 methods that you need to override:

  • GetConsumerSecrets – Return the list of secrets for the given OAuth consumer key. These will be used to validate the outcomes service call from the provider.
  • DeleteResult – Delete the specified score.
  • ReadResult – Read the specified score.
  • ReplaceResult – Save the specific score.

Here is a simple example that works with the IMS LTI™ test tool,

using System.Collections.Generic;

namespace WebApplication1.Controllers
{
   public class OutcomesController : LtiLibrary.Consumer.OutcomesControllerBase
   {
      private static double? Score;

      protected override bool DeleteResult(LtiLibrary.Consumer.Result result)
      {
         Score = null;
         return true;
      }

      protected override IList<string> GetConsumerSecrets(string consumerKey)
      {
         return consumerKey.Equals("12345") ? new List<string> { "secret" } : new List<string>();
      }

      protected override bool ReadResult(LtiLibrary.Consumer.Result result)
      {
         result.DoubleValue = Score;
         return true;
      }

      protected override bool ReplaceResult(LtiLibrary.Consumer.Result result)
      {
         Score = result.DoubleValue;
         return true;
      }
   }
}

Tell the provider that you support outcomes

The last step is to tell the provider that you support outcomes. You do this by adding two values to the LTI request: the endpoint URL of the outcomes service, and the result ID which you can think of as the grade cell ID in the grade book. For example,

public ActionResult Launch()
{
   var request = new LtiLibrary.Consumer.LtiOutboundRequest
      {
         ConsumerKey = "12345",
         ConsumerSecret = "secret",
         ContextId = "1",
         ResourceLinkId = "1",
         Url = "http://www.imsglobal.org/developers/LTI/test/v1p1/tool.php"
      };

   // Add outcomes support
   var urlHelper = new UrlHelper(this.HttpContext.Request.RequestContext);
   var relativeUrl = urlHelper.HttpRouteUrl("DefaultApi", new { controller = "outcomes" });
   request.LisOutcomeServiceUrl = new Uri(this.HttpContext.Request.Url, relativeUrl).AbsoluteUri;
   request.LisResultSourcedId = "1";

   return View(request.GetLtiRequestModel());
}

Launch

If you launch the IMS LTI™ test tool, you will see a new option to test outcomes,

image

What’s Next?

Check out the sample Consumer in LTI Samples to see a more robust use of LtiLibrary. If you are a publisher, the sample Provider shows how to use LtiLibrary to handle LTI launch requests and even send grades back to the consumer.

Posted in Uncategorized | Tagged , , | 4 Comments

Quickly add LTI consumer functionality to your ASP.NET MVC web site

NOTE: This post is obsolete. LtiLibrary is now on GitHub, along with several samples.

IMS LTI™ is a technology which helps one educational system interoperate with another. For example, a Learning Management System (LMS) can launch an educational tool with enough context that the tool knows if the user a student or teacher, which institution they belong to, and even which course section they are enrolled in.

In this post, I show how to use my .NET LtiLibrary to make a simple LTI request to launch the IMS LTI™ test tool.

Get LtiLibrary from CodePlex

LtiLibrary is a project within LTI Samples on CodePlex. LTI Samples has two complete sample web sites: a Consumer and a Provider. But for this example, you only need the LtiLibrary project.

You will notice that LtiLibrary depends on EntityFramework, HtmlAgilityPack, and OAuth.net. The easiest way to get EntityFramework and HtmlAgilityPack is with nuget. You will have to download and unzip OAuth.net the old fashioned way.

Make sure you can build the LtiLibrary project before you continue.

SNAGHTML1ae6907

Hook Up LtiLibrary to Your Web Site

First add a reference to the LtiLibrary project to your ASP.NET MVC web site project, then add a new Action called Launch to one of your controllers:


public ActionResult Launch()
{
  var request = new LtiLibrary.Consumer.LtiOutboundRequest
  {
    ConsumerKey = "12345",
    ConsumerSecret = "secret",
    ContextId = "1",
    ResourceLinkId = "1",
    Url = "http://www.imsglobal.org/developers/LTI/test/v1p1/tool.php"
  };
  return View(request.GetLtiRequestModel());
}

Now add the corresponding View (e.g. Launch.cshtml) to post the LTI launch request:

@model LtiLibrary.Consumer.LtiOutboundRequestViewModel

<form id="form" action="@Model.Action" method="post">
  @foreach (var name in Model.Fields.AllKeys)
  {
    <input type="hidden" name="@name" value="@Model.Fields[name]" />
  }
  <input type="hidden" name="oauth_signature" value="@Model.Signature" />
</form>

@section Scripts {
  <script>
    $(function () {
      $("#form").submit();
    });
  </script>
}

And finally, add a link or button that invokes the Action:

image

Launch

When you click on the link or button, you should see IMS LTI™ test tool appear and display details of your request:

image

What’s Next?

Check out the sample Consumer in LTI Samples to see a more robust use of LtiLibrary. If you are a publisher, the sample Provider shows how to use LtiLibrary to handle LTI launch requests and even send grades back to the consumer.

Posted in Uncategorized | Tagged , | 2 Comments

How to Launch a Lincoln Speech from Canvas with LTI

My sample provider site has two Lincoln speeches which I use to experiment with LTI consumers. I signed up for a trial Canvas account yesterday and found out that the trial includes LTI consumer functionality. So, of course I started to add one of the Lincoln speeches.

However, when I setup the external tool in Canvas, I noticed that my sample provider site made it unnecessarily difficult by asking for the tool_consumer_instance_guid (Canvas does not expose that value to the user). So I removed that requirement. Yeah for owning the code! Once that was gone, it was fairly straightforward.

Here are (the now simple) steps to launch a Lincoln speech from Canvas with LTI.

  1. Login to your Canvas site.
  2. Select one of the courses.
    image
  3. Select Settings from the navigation menu.
    image
  4. Select the External Tools tab on the Settings page.
    image
  5. Click on Add External Tool.
    image
  6. Fill in all the fields as shown (using whatever key and secret you want) and then save the tool settings.
    image
  7. In another window, login to http://provider.azurewebsites.net.
  8. Select Orders.
    image
  9. Click on Create New.
    image
  10. Give the order a name and then click on Register New Consumer.
    image
  11. Fill in all the consumer fields, using the same key and secret as you used in Canvas, and then click on Create.
    image
  12. Click on Create again to finish creating the order.
    image
  13. Return to the Canvas course and select Home from the navigation menu.
    image
  14. Add Content to one of the modules.
    image
  15. Choose External Tool.
    image
  16. Choose the External Tool that you created above and then enter the URL to one of the Lincoln speeches. For example, http://provider.azurewebsites.net/Tool/1.
    image
  17. Click on Add Item to save the new item.
  18. Click on the item to launch it.
    image

image

Posted in Uncategorized | Tagged , | Leave a comment

Turn Your LTI 1.0 Consumer into an LTI 1.1 Consumer in 30 Minutes (or Less!)

If you have taken a peak at the source code in CodePlex lately, you may have noticed a new library project called LtiLibrary. I’m slowly pulling the LTI specific code out of Consumer (and eventually Provider) to make it easier to explain the code, and to make it easier to reuse.

Today I worked on the OutcomesController. This is the ApiController that implements the consumer side of the LTI 1.1 Basic Outcomes Service which is used to receive scores (grades, outcomes) from the provider.

I chose to implement the controller as an ApiController because MVC handles all the serialization and deserialization for me once I add the schema (which you can download from the IMS website as part of the LTI 1.1 specification) and tell MVC to use the XmlFormatter,

image

When the controller was part of the Consumer project, I mixed Consumer-specific code such as accessing the consumer database, with LTI code such as handling each outcome request. Since the outcome should eventually be saved in the database, I decided to implement the service as an abstract class in LtiLibrary called OutcomesControllerBase.

OutcomesServiceBase implements the only endpoint (a POST to api/outcomes). The consumer then implements a concrete OutcomesController which inherits from OutcomesServiceBase and implements 4 events handlers:

  • OnReplaceResult – Raised when the provider wants to save or update a result
  • OnReadResult – Raised when the provider wants to read a result
  • OnDeleteResult – Raised when the provider wants to delete a result
  • OnGetConsumerSecrets – Raised when the base class is authenticating the request

Each handler is quite short and only has to deal with implementation specific details such as saving the score in the database. For example,

image

To recap…if you want to add support for Basic Outcomes to your .NET consumer, follow these steps:

  1. Add the LtiLibrary project to your solution and reference LtiLibrary in your project.
  2. Create an OutcomesController that inherits from OutcomesControllerBase and implement the 4 handlers.
  3. Enable XmlSerialization in WebApiConfig.

That will turn your LTI 1.0 consumer into an LTI 1.1 consumer.

Posted in Uncategorized | Tagged , , | Leave a comment

Deploy ASP.NET MVC application with Membership to Azure

This article describes how to deploy an ASP.NET MVC application with the default Membership database to Azure,

Deploy a Secure ASP.NET MVC application with OAuth, Membership and SQL Database – .NET Web Development and Tools Blog – Site Home – MSDN Blogs.

The author uses Microsoft SQL Server Management Studio to generate an SQL script, and runs the script during Publish.

Another option is to create your own Membership table and Entity Framework Code First Migrations.

  1. First create your own Users table. It must have at least two fields: UserId and UserName,
    image
  2. Add the table to your DbContext,
    image
  3. Modify _AppStart.cshtml to run Migrations and then connect WebSecurity to the new Users table,
    image
  4. Run the application to create the table and test Membership.
  5. Finally, Publish the application to Azure using the option to Execute Code First Migrations,
    SNAGHTML8d6cb9

Note that if you want to seed the Users table, you will need to do it in _AppStart.cshtml,
image

Happy coding!

Posted in Uncategorized | Tagged , , | Leave a comment

Customizing External Login Buttons in Sample Consumer

Ran across this…

Customizing External Login Buttons in ASP.NET MVC 4.

Now you see this…

http://consumer.azurewebsites.net/Account/Login

image

Posted in Uncategorized | Tagged | Leave a comment

I’ve Launched My inBloom LTI App, Now What?

In a previous post, I described how I made my Sample LTI Consumer website (Consumer) into an SLC–now inBloom—application:

The first step was to extend the OAuth 2.0 support in ASP.NET MVC 4.5 to work with inBloom.

When my sample Consumer is launched from the inBloom Sandbox, OAuth 2 and the Sandbox identity provider (SAML) are used to authenticate and authorize a user in my sample Consumer.

Once there, the user can import inBloom course sections and gradebook entries (each inBloom Section becomes a Consumer course and each GradebookEntry becomes an assignment). And new assignments are written back to inBloom as new gradebook entries.

So far, none of this uses LTI. This post describes how I make use of LTI after the Consumer is launched.

LTI 1.1 has three main tricks:

  1. Authentication – It uses 2-legged OAuth in each request so the provider can infer authentication based on evidence from the consumer
  2. Authorization – It includes context name/value pairs in each request so the provider can authorize the request and provide the appropriate services and content
  3. Outcomes – A well defined scheme exists so the Provider can send outcomes back to the Consumer

My sample Consumer (now a registered inBloom application) makes use of all 3 LTI tricks. First of all every assignment in my sample Consumer is a link. If the assignment includes LTI credentials (key and secret), then when the assignment is launched, it is launched with an LTI request which includes both Authentication and Authorization fields so the Provider can authenticate and authorize the request. The values that are sent to the Provider came from inBloom.

launch

If the Provider sends an outcome back to the Consumer using the LTI Outcomes service, the Consumer will send the outcome back to inBloom as a StudentGradebookEntry using the inBloom REST API.

grades

Why Not Use OAuth 2 and the inBloom API instead of LTI?

Because the two organizations are optimizing for different goals, their solutions are optimized to solve different problems. inBloom is optimizing their solution (the inBloom Data Store) to collect data (including assignment grades) that will improve personalized learning. IMS is optimizing their solution (LTI) to a single, simple scheme to launch tools and content, and collect outcomes. This is similar to internet access: inBloom is focusing on the infrastructure and IMS is focusing on the last mile problem.

Show Me Some Code

As you saw in a previous post, when the Consumer application is launched from the inBloom sandbox, the Consumer will request an accessToken from inBloom’s authorization service. Once the Consumer receives the accessToken, it pairs the inBloom user to a local account, creating one if necessary. This was all handled using the built in support for external logins that comes with the MVC 4 Internet Application template in Visual Studio.

Launching an assignment with LTI for an inBloom user is no different than launching an assignment with LTI for any other user in the Consumer website. See Building the LTI POST.

Sending the outcome (also known as grade or score) back to inBloom is new. Keep in mind that in the LTI model, the provider can send an outcome back to the consumer at any time…not necessarily immediately after completing the assignment. For example, there may be a manual grading process that takes days to complete. To keep track of where to send the outcome, my Sample LTI Provider (Provider) creates an Outcome record for each unique lis_result_sourcedid,

image

Later, when the Provider needs to send an outcome back to the consumer, it retrieves the Outcome record and POSTS the score,

image

When the Consumer receives the score, it records it locally,

image

And then attempts to forward the score to inBloom,

SNAGHTML805378d

See that highlighted line?

var token = GetLongLivedSessionToken(score.TenantId);

All of the inBloom API calls I described at the top of this post (import course sections, etc) use the accessToken of the current user. But scores can arrive from the provider at any time…even days after the user has logged off. We need an accessToken that is valid even if the user is long gone. That is what the inBloom long-lived session token resolves. It is a special accessToken that inBloom will create that does not expire (or it expires after a VERY long time).

Summary

inBloom is building the infrastructure to improve personalized learning. LTI solves the “last mile” problem of using learning tools in the classroom. Together, they make a great team.

Posted in Uncategorized | Tagged , , , | Leave a comment

Does LTI use OAuth?

How to Think About OAuth

I’ve been “following” Tim Bray for a very long time. Probably since Chris Sells organized an XML conference at the amazing Skamania Lodge.

He recently wrote down some of his thoughts about OAuth in his blog, ongoing by Tim Bray · How to Think About OAuth. About the same time I was asked by a coworker if LTI used OAuth and that made me think about what we mean by “uses OAuth”.

TL;DR

Yes, LTI uses 2-legged OAuth 1.0; but many people think of the 3-legged variety when they ask about OAuth, and they will probably have to do a little bit of hand coding to support LTI.

Tim makes the point that unlike some standards and specs, OAuth is “a framework not a protocol, it has irritating problems, and it’s really very useful.” He goes on to explain that, “The end-game of an OAuth2 invocation is an ‘Access Token’, just a string of characters.”

That is also the end-game for OAuth 1.0 and it is often called “3-legged OAuth”. But some OAuth users also proposed 2-legged OAuth to digitally sign each request from a Consumer to the Service Provider’s resource endpoint. No access token is created or needed. LTI uses a scheme very similar to the proposal. Unfortunately, the proposal was never adopted by the IETF.

What LTI Really Does

The purpose of each LTI request is to launch a tool or consume a service. LTI Tool Consumers use LTI requests to launch tools provided by LTI Tool Providers. LTI Tool Providers use LTI requests to consume services provided by LTI Tool Consumers (such as the Outcomes service). Because LTI requests go in both directions between the LTI Tool Consumer and LTI Tool Provider, they are both OAuth Consumers and OAuth Service Providers.

Before the first LTI request is sent, both parties agree on a consumer_key and consumer_secret. The consumer_key will be used by both parties to identify who they are talking to. The consumer_secret will be used to digitally sign every request going in either direction.

After both parties agree on the consumer_key and consumer_secret, the rest is very straightforward. For example, when an LTI Tool Consumer wants to launch a tool or resource provided by the LTI Tool Provider, the consumer performs these steps:

  1. Start forming an HTTP(S) request with basic LTI name/value pairs that mark this request as being an “LTI” request.
  2. Add more name/value pairs to the request that will help the provider authorize the request.
  3. Sign the request with a slight variation of the 2-legged OAuth 1.0 specification described here.
  4. POST the request to the provider.

The provider in turn will perform these steps:

  1. Receive the request.
  2. Verify the authenticity of the request using the 2-legged Auth 1.0 specification described here.
  3. Verify the authorization of the request using the name/value pairs in the request.
  4. Launch the tool or resource.

2-Legged OAuth with OAuth 2.0

Most .NET OAuth libraries that I come across do not support 2-legged OAuth 1.0. The one exception is OAuth.net, but it is no longer being updated.

Most modern OAuth libraries–including DotNetOpenAuth which is available on NuGet and used in several Visual Student project templates–have been improving their support for OAuth 2.0 not 1.0. Unfortunately, the draft OAuth 2.0 specification does not talk about 2-legged OAuth 2.0; so it is unlikely that modern OAuth libraries will include support for 2-legged OAuth that is compatible with LTI.

[This article does a great job explaining 2-legged OAuth 1.0 and some alternatives in OAuth 2.0.]

UPDATE:

* I just ran into a post from one of the authors of DotNetOpenAuth in which he argues that what LTI (and other so-called 2-legged OAuth folks) are using is really 0-legged OAuth (as in zero-legs) because it does not include any of 3 OAuth legs.

Maybe a better answer is no, LTI uses OAuth signatures, but that’s it.

Posted in Uncategorized | Tagged , | 2 Comments

Refining the Tenant Registration

In my last post I wrote about passing the Client ID from the SLC to the Sample LTI Consumer Website (consumer website). This is required because each SLC tenant will have a different Client ID for the consumer website and the SlcSandboxClient needs to know which tenant it is talking to.

After using this scheme for a while, a problem cropped up. Each time I unregistered and reregistered the consumer website, I got a new Client ID. That orphaned all the users that had been paired, even though they were the same users.

So I added a new SlcTenantId field to the Tenant class. And I replaced clientid with tenantid in the Url field when I register the consumer website as an application in the SLC.

The tenantid can be any unique (within the consumer website) string. But you may want to simply use the SLC Tenant ID. For sandbox accounts, it is the email address you used when you signed up.

Posted in Uncategorized | Tagged , | Leave a comment