Lesson 3 - Create a RESTful API

Relativity exposes many REST endpoints, which you easily use to make API calls from various coding languages and tools, such as cURL or Postman. You make REST calls over HTTP, which provides language-agnostic programming and a high level of flexibility.

In this lesson, you will learn how to:

  • Use the Kepler framework to build RESTful APIs.
  • Implement a RESTful API which integrates with Wikipedia's public APIs.
  • Retrieve data in the required format.

Estimated completion time - 2 hours

This page contains the following information:

Step 1 - Create an empty Kepler service

Begin by creating an empty Kepler service, which serves as the framework for the final service that you implement. (You will be using the Relativity Visual Studio templates that you installed in Lesson1, Step 1)

Use the following steps to create an empty Kepler service:

  1. Opening Visual Studio 2019.
  2. Click Create a new project.
    The Create a new project dialog appears.

    Create a new project button

  3. Select C# as the language for the project.
  4. Search for the Relativity Kepler Project Template.

    (Click to expand)

    Create a new project dialog

  5. Click Next.
    The Configure your new project dialog appears.

    (Click to expand)

    Configure your new project dialog

  6. Enter the following information in this dialog:
    • Project name - WikipediaKepler
    • Solution name - HelloWikipedia
    • Framework - .NET Framework 4.6.2
  7. Click Create.
    The Template Wizard appears.

    (Click to expand)

    Template wizard

  8. Enter the following information in the wizard:
    • Service Module - WikipediaManagement
    • Service Name - WikipediaService
  9. Click Create.
    The wizard generates the solution and required projects.
  10. Verify that solution contains the WikipediaKepler.Interfaces and WikipediaKepler.Services projects, as illustrated in the following screen shot.

    Solution Explorer

    The projects are used as follows:

    • WikipediaKepler.Interfaces - add your API definitions to this project.
    • WikipediaKepler.Services - add your implementation to this project.
  11. Ensure that the generated API is compliant with REST best practices by navigating to WikipediaKepler.Interfaces > WikipediaManagement > IWikipediaManagementModule.cs in Visual Studio.
  12. Update the RoutePrefix attribute to wikipedia-management.
    using Relativity.Kepler.Services;
     
    namespace WikipediaKepler.Interfaces.WikipediaManagement
    {
        /// <summary>
        /// WikipediaManagement Module Interface.
        /// </summary>
        [ServiceModule("WikipediaManagement Module")]
        [RoutePrefix("wikipedia-management", VersioningStrategy.Namespace)]
        public interface IWikipediaManagementModule
        {
        }
    }
  13. Navigate to WikipediaKepler.Interfaces > WikipediaManagement > v1 > IWikipediaService.cs in Visual Studio.
  14. Update the RoutePrefix attribute to wikipedia-service.
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Relativity.Kepler.Services;
    using WikipediaKepler.Interfaces.WikipediaManagement.v1.Models;
     
    namespace WikipediaKepler.Interfaces.WikipediaManagement.v1
    {
        /// <summary>
        /// MyService Service Interface.
        /// </summary>
        [WebService("WikipediaService Service")]
        [ServiceAudience(Audience.Public)]
        [RoutePrefix("wikipedia-service")]
        public interface IWikipediaService : IDisposable
        {
            /// <summary>
            /// Get workspace name.
            /// </summary>
            /// <param name="workspaceID">Workspace ArtifactID.</param>
            /// <returns><see cref="WikipediaServiceModel"/> with the name of the workspace.</returns>
            /// <remarks>
            /// Example REST request:
            ///   [GET] /Relativity.REST/api/WIkipediaManagement/v1/WikipediaService/workspace/1015024
            /// Example REST response:
            ///   {"Name":"Relativity Starter Template"}
            /// </remarks>
            [HttpGet]
            [Route("workspace/{workspaceID:int}")]
            Task<WikipediaServiceModel> GetWorkspaceNameAsync(int workspaceID);
     
            /// <summary>
            /// Query for a workspace by name
            /// </summary>
            /// <param name="queryString">Partial name of a workspace to query for.</param>
            /// <param name="limit">Limit the number of results via a query string parameter. (Default 10)</param>
            /// <returns>Collection of <see cref="WikipediaServiceModel"/> containing workspace names that match the query string.</returns>
            /// <remarks>
            /// Example REST request:
            ///   [POST] /Relativity.REST/api/WIkipediaManagement/v1/WikipediaService/workspace?limit=2
            ///   { "queryString":"a" }
            /// Example REST response:
            ///   [{"Name":"New Case Template"},{"Name":"Relativity Starter Template"}]
            /// </remarks>
            [HttpPost]
            [Route("workspace?{limit}")]
            Task<List<WikipediaServiceModel>> QueryWorkspaceByNameAsync(string queryString, int limit = 10);
        }
    }

    The final route is constructed based on the route prefixes that you just added:

    Relativity.REST/api/{{IWikipediaManagementModule route prefix}}/{{IWikipediaService namespace version}}/{{IWikipediaService route prefix}}/{{IWikipediaService method route}}
  15. Build the solution.
    When the build completes, you have a functional Kepler service that you can deploy.

Step 2 - Deploy to Relativity

After implementing a functional Kepler service, you can upload it to Relativity and associate it with an application so that users can interact with it.

Use the following steps to deploy your service to Relativity:

  1. Log in to your Relativity instance.
  2. Click RelativityOne icon to display your home page.
  3. In the Sidebar, click Other Tabs > Applications & Scripts > Resource Files.
    The Resource Files tab appears.

    (Click to expand)

    Resource Files option

  4. Click New Resource File.
    The Resource File Information dialog appears.

    (Click to expand)

    Resource File Information dialog

  5. Click Select to display the Select Library Application dialog.
  6. Select the Hello Wikipedia application that you created in Lesson 2 - Build an application without any code.
  7. Click Choose File to select your compiled WikipediaKepler.Interfaces.dll to add as a new resource file.
  8. Click Save and New.
  9. Repeat steps 5 - 7 to add the WikipediaKepler.Services.dll, WikipediaKepler.Interfaces.pdb, and WikipediaKepler.Services.pdb as a resource files.

    You have now uploaded the .dlls and pdbs, associated them with the Hello Wikipedia application, and deployed them to Relativity.

Step 3 - Test the deployed service

You can test your new service after you have deployed it to Relativity.

Use the following steps to make a REST call:

  1. Make a REST request to your newly deployed Kepler service using an HTTP client such as Postman. If you get a 404 error, wait a few minutes and try again.
    • Method - POST
    • URL - use the following:
      <host>/Relativity.REST/api/wikipedia-management/v1/wikipedia-service/workspace
      • In the sample URL, <host> refers to the Relativity instance's base URL. On a test VM, the <host> value may look something like https://p-dv-vm-abc0efg
    • Headers - set the headers as follows:
      • X-CSRF-Header - set to a dash (-).
      • Authorization - set to Basic <basic-authorization-token>.

        Basic authentication is a simple authentication scheme built into the HTTP protocol. The client sends HTTP requests with the Authorization header that contains the word Basic followed by a space and a base64-encoded string, such as username:password. For example, if you wanted to authorize as user demo with the password p@55w0rd, use base64 to encode the password. Next, update the Authorization header to Basic with encoded password as ZGVtbzpwQDU1dzByZA==. If you are using a Relativity Test VM, use the same credentials that you used to sign into the Relativity Instance on the Test VM.

      • Content type - application/json
    • Body - add the following JSON:
      {
      
          "queryString" : "My First Workspace"
      
      }
      
  2. Verify that you receive a successful response with the following payload:
    [
        {
            "Name": "My First Workspace"
        }
    ]
    

Step 4 - Remote debugging

After you have deployed the service in Relativity, you can use remote debugging to inspect the runtime functionality of the service.

Verify the remote debugger is running

Make sure that you have the Visual Studio 2019 Remote Debugger service running on the Relativity instance where you want to debug. In your DevVM, click the VS 2019 debugger in the taskbar to start run it.

(Click to expand)

Visual Studio 2019 Remote Debugger

Remotely debug your code

Use the following steps to remotely debug your code:

  1. Open the HelloWikipedia solution in Visual Studio.
  2. Navigate to Debug > Attach to Process.

    (Click to expand)

    Attach to Process option

  3. Enter a Connection Target and click Find.
    The Remote Connections dialog appears.
  4. Select a connection in the Auto Detected section.
    A prompt appears requesting your credentials.

    (Click to expand)

    Remote Connection dialog

  5. Enter the administrator credentials for your Relativity instance and click OK.
  6. Use the following steps to locate a process for attaching the debugger:
    • Log in to the machine where your Relativity instance is running.
    • Open the Task Manager.
    • Click the Details tab.
    • Right-click the Name column header to display the Select columns dialog.
    • Select the Command line checkbox.

      (Click to expand)

      Select columns dialog

  7. Locate the Relativity.Platform.Service.exe process with the name Hello Wikipedia.
    This process is hosting your service.
  8. Copy the PID of the matching process.

    (Click to expand)

    Details tab

  9. In Visual Studio, go to the Attach to Process window. See steps 2-5.
  10. Select the Show processes from all users checkbox.
  11. Enter the PID that you copied from your Relativity instance in the search box and attach to the matching process.
    After you attach to the process, you can add breakpoints to begin testing your service.

    (Click to expand)

    Attach to Process dialog

  12. Add a breakpoint to the code in the WikipediaService.cs file for the REST call made in Step 3 - Test the deployed service.

    Adding a breakpoint

  13. Repeat the REST call made in Step 3 - Test the deployed service.
    The breakpoint should be triggered in Visual Studio.

    Triggering a breakpoint

    Note: The REST call won't complete until you stop debugging or allow the process to continue.

Step 5 - Update the service

After confirming that your service is working and can be remotely debugged, you can start updating it.

Use the following steps to update the service:

  1. Add a default value that the QueryWorkspaceByNameAsync() method returns.
  2. Save the changes and rebuild the solution.
  3. Log in to your Relativity instance.
  4. Click RelativityOne icon to display your home page.
  5. In the Sidebar, click Other Tabs > Applications & Scripts > Resource Files.
    The Resource Files tab appears.

    (Click to expand)

    Resource Files option

  6. In the Name filter, type WikipediaKepler.
    You should see the two .dlls and two .pdbs files that you uploaded in Step 2 - Deploy to Relativity.

    (Click to expand)

    Resource File tab

  7. To update the implementation code, click WikipediaKepler.Services.dll.
    You only need to update these .dll and .pdb files because you didn't modify the interfaces.
  8. Click Edit.
    The Resource File Information dialog appears.
  9. Click Clear in the Resource File field.

    (Click to expand)

  10. Select your compiled WikipediaKepler.Services.dll to add as a new resource file.
  11. Click Save.
    The updated .dll is uploaded to Relativity and the service is redeployed.

    Note: If you want to remote debug this service, repeat steps 9-11 for the WikipediaKepler.Services.pdb file.

  12. After waiting a few minutes for the service to redeploy, repeat the REST call made in Step 3 - Test the deployed service.
    The same results should be returned along with the new default value.
    [
        {
            "Name": "NotARealWorkspace"
        },
        {
            "Name": "My First Workspace"
        }
    ]
    

Step 6 - Implement methods on an interface

In this step, you implement each of the methods on the IWikipediaService interface in the IWikipediaService.cs file.

Use the following steps to add methods to the IWikipediaService interface:

  1. In Visual Studio, locate the IWikipediaService.cs file.
  2. Remove the existing methods in your IWikipediaService interface and in the WikipediaService class.
  3. Add the GetCategoriesByPrefixAsync() method to the IWikipediaService interface.
    ...
    /// <summary>
    /// Returns a list of Categories in Wikipedia.
    /// </summary>
    /// <param name="prefix">Category prefix to limit results query of Categories in Wikipedia.</param>
    /// <returns><see cref="CategoryResponseModel"/> with the title of the category.</returns>
    /// <remarks>
    /// Example REST request:
    ///   [GET] /Relativity.REST/api/wikipedia-management/v1/wikipedia-service/categories?prefix=Star%20Wars
    /// Example REST response:
    ///   [{"Title":"Star Wars: The Rise of Skywalker"},{"Title":"Star Wars: A New Hope"}]
    /// </remarks>
    [HttpGet]
    [Route("categories?{prefix}")]
    Task<List<CategoryResponseModel>> GetCategoriesByPrefixAsync(string prefix);
    ...
  4. Add the CategoryResponseModel to the following file in your project: WikipediaKepler.Interfaces > WIkipediaManagement > v1 > Models > CategoryResponseModel.cs.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace WikipediaKepler.Interfaces.WikipediaManagement.v1.Models
    {
        /// <summary>
        /// CategoryResponseModel Data Model.
        /// </summary>
        public class CategoryResponseModel
        {
            /// <summary>
            /// Title property.
            /// </summary>
            public string Title { get; set; }
        }
    }
  5. Add an interface to later inject in our WikipediaService. Add the IRestService to the following file in your project: WikipediaKepler.Interfaces > WIkipediaManagement > v1 > IRestService.cs
    using System.Net.Http;
    using System.Threading.Tasks;
    
    namespace WikipediaKepler.Interfaces.WikipediaManagement.v1
    {
        public interface IRestService
        {
            Task<HttpResponseMessage> GetAsync(string requestUri);
        }
    }
  6. Add the implementation WikipediaRestService to the following file in your project: WikipediaKepler.Services > WIkipediaManagement > v1 > WikipediaRestService.cs
    using System;
    using System.Net.Http;
    using System.Threading.Tasks;
    using WikipediaKepler.Interfaces.WikipediaManagement.v1;
    
    namespace WikipediaKepler.Services.WikipediaManagement.v1
    {
        public class WikipediaRestService : IRestService
        {
            private static Lazy<HttpClient> _httpClient = new Lazy<HttpClient>(() => new HttpClient()
            {
                BaseAddress = new Uri("https://en.wikipedia.org/w/")
            });
    
            private HttpClient HttpClient => _httpClient.Value;
    
            public async Task<HttpResponseMessage> GetAsync(string requestUri)
            {
                return await HttpClient.GetAsync(requestUri);
            }
        }
    }
  7. Update the service constructor so that you can inject additional dependencies needed at runtime.
    In the WikipediaService() method, add the additional private fields and update the constructor as illustrated in the following code sample.

    With this update, you can avoid initializing other classes in the implementation, which increases the flexibility and reusability of the code. It also simplifies testing. The WikipediaService() method is in the WikipediaService.cs file.

    ...
    private IRestService _restService;
    private const int _CATEGORY_TITLE_INDEX = 9;
      
    // Note: IHelper and ILog are dependency injected automatically into the constructor every time the service is called.
    public WikipediaService(IHelper helper, ILog logger, IRestService restService)
    {
        // Note: Set the logging context to the current class.
        _logger = logger.ForContext<WikipediaService>();
        _helper = helper;
        _restService = restService;
    }
    ...
  8. Add a reference to System.Net.Http namespace to use the HttpClient class to both the WikipediaKepler.Interfaces and the WikipediaKepler.Services projects.
  9. Add the dependency injection code that injects IRestService at runtime. The classes extending IWindsorInstaller interface are injected and executed as part of the service deployment.

    Complete these steps:

    • Install the Castle.Windsor 3.3.0 NuGet package.
    • Add the WikipediaServiceInstaller class to the following file in your project: WikipediaKepler.Services > WIkipediaManagement > v1 > WikipediaServiceInstaller.cs.
    using Castle.MicroKernel.Registration;
    using Castle.MicroKernel.SubSystems.Configuration;
    using Castle.Windsor;
    using WikipediaKepler.Interfaces.WikipediaManagement.v1;
    
    namespace WikipediaKepler.Services.WikipediaManagement.v1
    {
        public class WikipediaServiceInstaller : IWindsorInstaller
        {
            public void Install(IWindsorContainer container, IConfigurationStore store)
            {
                container.Register(Component.For<IRestService>().ImplementedBy<WikipediaRestService>().LifestyleTransient());
            }
        }
    }
  10. Review API:Allcategories on the MediaWiki site Before implementing GetCategoriesByPrefixAsync() method. Notice the request call and response format for retrieving a list of categories that match a specific prefix.
  11. Install the Newtonsoft.Json 6.0.8 NuGet package to use JSON as the response format.
  12. Add the following classes as internal models to simplify deserialization in the future implementation. These models are required for the implementation of the GetCategoriesByPrefixAsync() method.

    Add the models to the following folder in your project: WikipediaKepler.Services > WikipediaManagement > v1 > Models.

  13. Implement the GetCategoriesByPrefixAsync() method in the WikipediaService.cs file.
    ...
    public async Task<List<CategoryResponseModel>> GetCategoriesByPrefixAsync(string prefix)
    {
        var categories = new List<CategoryResponseModel>();
        HttpResponseMessage response = await _restService.GetAsync($"api.php?action=query&generator=allcategories&gacprefix={prefix}&prop=info&format=json");
        string content = await response.Content.ReadAsStringAsync();
        WikipediaQueryResponse result = JsonConvert.DeserializeObject<WikipediaQueryResponse>(content);
        if (result.Query != null)
        {
            categories = result.Query.Pages.Values.Select(page => new CategoryResponseModel
            {
                Title = page.Title.Substring(_CATEGORY_TITLE_INDEX) // Substring to drop the 'Category:' prefix
            }
            ).ToList();
        }
        return categories;
    }
    ...
  14. Add the GetPagesForCategoryAsync() method to the IWikipediaService interface in the IWikipediaService.cs file.
    ...
    /// <summary>
    /// Get a list of pages under the provided Category in Wikipedia.
    /// </summary>
    /// <param name="categoryName">An existing Category in Wikipedia.</param>
    /// <param name="pageSize">Number of results in the page.</param>
    /// <param name="continueFrom">Identifier indicating where a paged result should be continued from. If '-', will start from the beginning.</param>
    /// <returns><see cref="CategoryResponseModel"/> with the title of the category.</returns>
    /// <remarks>
    /// Example REST request:
    ///   [GET] /Relativity.REST/api/wikipedia-management/v1/wikipedia-service/categories/Star%20Wars/pages?pageSize=10&continueFrom=page|123|456&pageSize=2
    /// Example REST response:
    ///   [{"Title":"Star Wars: The Rise of Skywalker"},{"Title":"Star Wars: A New Hope"}]
    /// </remarks>
    [HttpGet]
    [Route("categories/{categoryName}/pages?{pageSize}&{continueFrom}")]
    Task<Pageable<PageForCategoryResponseModel>> GetPagesForCategoryAsync(string categoryName, int pageSize = 10, string continueFrom = "-");
    ...
  15. Add models that are required to support paging for categories to the following folder in your project: WikipediaKepler.Interfaces > WIkipediaManagement > v1 > Models.
  16. Implement the GetPagesForCategoryAsync() method in the WikipediaService.cs file.
    It references the API:Categorymembers on the MediaWiki site.
    ...
    public async Task<Pageable<PageForCategoryResponseModel>> GetPagesForCategoryAsync(string categoryName, int pageSize = 10, string continueFrom = "-")
    {
        var pages = new List<PageForCategoryResponseModel>();
        continueFrom = continueFrom == null || continueFrom.Equals("-") ? string.Empty : continueFrom;
        HttpResponseMessage response = await _restService.GetAsync($"api.php?action=query&list=categorymembers&cmtitle=Category:{categoryName}&cmlimit={pageSize}&cmcontinue={continueFrom}&format=json");
        string content = await response.Content.ReadAsStringAsync();
        WikipediaQueryResponse result = JsonConvert.DeserializeObject<WikipediaQueryResponse>(content);
        if (result.Query != null)
        {
            pages = result.Query.CategoryMembers.Select(item => new PageForCategoryResponseModel { Title = item.Title }).ToList();
        }
        string next = result.Continue?.CmContinue ?? string.Empty;
                  
        return new Pageable<PageForCategoryResponseModel>{ Results = pages, Next = next};
    }
    ...
  17. Implement the GetPageByNameAsync() method on the IWikipediaService interface by adding it to the IWikipediaService.cs file.
    ...
    /// <summary>
    /// Returns an existing page in Wikipedia.
    /// </summary>
    /// <param name="pageName">Name of the page in Wikipedia.</param>
    /// <returns><see cref="PageResponseModel"/> with the Title, Url, and categories of the page.</returns>
    /// <remarks>
    /// Example REST request:
    ///   [GET] /Relativity.REST/api/wikipedia-management/v1/wikipedia-service/pages/Star%20Wars
    /// Example REST response:
    ///  {"Title":"Star Wars", "Url":"https://en.wikipedia.org/wiki/Star_Wars", "Categories":[{"Title":"Star Wars: The Rise of Skywalker"}]}
    /// </remarks>
    [HttpGet]
    [Route("pages/{pageName}")]
    Task<PageResponseModel> GetPageByNameAsync(string pageName);
    ...
  18. Implement the PageResponseModel class for use with the GetPageByNameAsync() method.
    This method requires a model used to return a standalone Page. Add the PageResponseModel.cs file to the WikipediaKepler.Interfaces > WIkipediaManagement > v1 > Models folder.
    using System.Collections.Generic;
    using WikipediaKepler.Interfaces.WikipediaManagement.v1.Models;
      
    namespace WikipediaKepler.Interfaces.WikipediaManagement.v1.Models
    {
        /// <summary>
        /// PageResponseModel Data Model.
        /// </summary>
        public class PageResponseModel
        {
            /// <summary>
            /// Title property.
            /// </summary>
            public string Title { get; set; }
      
            /// <summary>
            /// Url property.
            /// </summary>
            public string Url { get; set; }
      
            /// <summary>
            /// Categories property.
            /// </summary>
            public List<CategoryResponseModel> Categories { get; set; }
        }
    }
  19. Implement the GetPageByNameAsync() method in the WikipediaService.cs file.
    It references the API:Categories on the MediaWiki site.
    ...
    public async Task<PageResponseModel> GetPageByNameAsync(string pageName)
    {
        HttpResponseMessage response = await _restService.GetAsync($"api.php?action=query&format=json&titles={pageName}&prop=categories");
        string content = await response.Content.ReadAsStringAsync();
        WikipediaQueryResponse result = JsonConvert.DeserializeObject<WikipediaQueryResponse>(content);
        Page page = result.Query.Pages.First().Value;
        if (page.Categories == null)
        {
            string errorMsg = $"Unable to find a page with name {pageName}.";
            _logger.LogError(errorMsg);
            throw new NotFoundException(errorMsg);
        }
      
        List<CategoryResponseModel> categories = page.Categories.Select(item => new CategoryResponseModel { Title = item.Title.Substring(_CATEGORY_TITLE_INDEX) }).ToList();
        return new PageResponseModel
        {
            Title = pageName,
            Url = $"https://en.wikipedia.org/wiki/{Uri.EscapeUriString(pageName)}",
            Categories = categories
        };
    }
    ...
  20. Implement the GetPageTextAsync() method on the IWikipediaService interface by adding it to the IWikipediaService.cs file.
    ...
    /// <summary>
    /// Returns a UTF-8 encoded text stream of an existing page in Wikipedia
    /// </summary>
    /// <param name="pageName">Name of the page in Wikipedia.</param>
    /// <returns>A <see cref="IKeplerStream"/> containing a UTF-8 encoded text stream from the specified page.</returns>
    /// <remarks>
    /// Example REST request:
    ///   [GET] /Relativity.REST/api/wikipedia-management/v1/wikipedia-service/pages/Star%20Wars/text
    /// Example REST response:
    ///  Star Wars is an American epic space-opera media franchise created by George Lucas[...]
    /// </remarks>
    [HttpGet]
    [Route("pages/{pageName}/text")]
    Task<IKeplerStream> GetPageTextAsync(string pageName);
    ...
  21. Implement the GetPageTextAsync() method in the WikipediaService.cs file.
    It references the API:Get the contents of a page on the MediaWiki site.
    ...
    public async Task<IKeplerStream> GetPageTextAsync(string pageName)
    {
        HttpResponseMessage response = await _restService.GetAsync($"api.php?action=query&prop=extracts&titles={pageName}&format=json");
        string content = await response.Content.ReadAsStringAsync();
        WikipediaQueryResponse result = JsonConvert.DeserializeObject<WikipediaQueryResponse>(content);
        Page page = result.Query.Pages.First().Value;
        if (page.Extract == null)
        {
            throw new NotFoundException($"Unable to find a page with name {pageName}.");
        }
        var stream = new MemoryStream(Encoding.UTF8.GetBytes(page.Extract));
        return new KeplerStream(stream)
        {
            ContentType = "text/html",
            StatusCode = HttpStatusCode.OK
        };
    }
    ...
  22. Verify that IWikipediaService interface is like the following code.
  23. Verify that your final WikipediaService implementation class is like the following code.
  24. Try out the methods on your IWikipediaService interface.
    They communicate with the Wikipedia API to retrieve information. If you want to test them, upload your .dll and .pdb files again as in Step 2 - Deploy to Relativity.

Step 7 - Logging

During the implementation of the Wikipedia Service, the ILog logger was used to log any errors that occurred. These logs are available in the EDDSLogging database in your Relativity instance. To view these logs, you can connect to the SQL database of your Relativity instance using Microsoft SQL Server Management Studio. Next, you need to execute a query against the EDDSLogging database.

Use the following steps to view logs:

  1. Open Microsoft SQL Server Management Studio.
  2. Connect to the IP address of your instance.
    To obtain your SQL credentials, see in Additional information about DevVMs.

    Connect to SQL Server dialog

  3. Navigate to the databases on the instance now.

    They should look like the following screen shot:

    (Click to expand)

    Microsoft SQL Server Management Studio

  4. Make a request that causes logging to occur.
    For example, use the HTTP client and configuration from Step 1 - Create an empty Kepler service to make the following GET request:

    http://<your-host>/Relativity.REST/api/wikipedia-management/v1/wikipedia-service/pages/non-existent-page-name 
  5. Create a new query and execute it by pressing F5 or Execute in the menu.
    The following query retrieves the most recent 1000 logs sorted by descending timestamp.

    Note: Update the query with your application GUID.

    USE EDDSLogging;
    
    
    SELECT TOP (1000) [ID]
          ,[Message]
          ,[MessageTemplate]
          ,[Level]
          ,[TimeStamp]
          ,[Exception]
          ,[Properties]
      FROM [EDDSLogging].[eddsdbo].[RelativityLogs]
      WHERE [Properties].value('(/properties/property[@key = "Application"]/text())[1]', 'varchar(100)') = 'YOUR APP GUID HERE'
      ORDER BY [TimeStamp] DESC

Step 8 - Write a unit test

To shorten your development cycles, you can write unit tests that validate the functionality of a service. The following example includes only a single unit test, but you can write more unit tests to cover the functionality of your service.

Review these guidelines for writing unit tests:

  • Test a single scenario per test.
  • Mock any dependencies to ensure that the test is limited to only the behavior that you want to test.
  • Follow testing best practices for naming conventions.

Use the following steps to write a unit test:

  1. Open the HelloWikipedia solution in Visual Studio.
  2. Right-click on the solution and click Add > New Project.
    The Add a new project dialog appears.
  3. Select Unit Test Project (.NET Framework) and click Next.

    (Click to expand)

    Add a new project dialog

  4. Enter the following information in the Configure your new project dialog:
    • Project name - WikipediaKepler.Tests
    • Location - set this field to the same root as your other projects.
    • Framework - verify that this field is set to .NET Framework 4.6.2.
  5. Click Create.
  6. To set up the required dependencies, right-click on the WikipediaKepler.Tests project, and click Add > Reference > Projects.
    The Reference Manager dialog appears.

    (Click to expand)

    Reference Manager

  7. Select the checkboxes for the following projects:
    • WikipediaKepler.Interfaces
    • WikipediaKepler.Services
  8. Install the following NuGet packages:
    • NUnit
    • NUnit3TestAdapter
    • Moq
    • Newtonsoft.Json 6.0.8

      Note: Use Newtonsoft.Json 6.0.8 across all your projects for this tutorial. The matching versions prevent the tests from failing with IO exceptions.

    • Relativity.API.15.1.3
    • Relativity.Kepler.2.3.2
  9. Implement the WikipediaServiceTests class.
    As a best practice, mirror the directory structure of the class under test. Add it to the following file in your project: WikipediaKepler.Tests > WikipediaManagement > v1 > WikipediaServiceTests.cs.
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using Moq;
    using Moq.Protected;
    using Newtonsoft.Json.Linq;
    using NUnit.Framework;
    using Relativity.API;
    using Relativity.Kepler.Logging;
    using Relativity.Services.Exceptions;
    using WikipediaKepler.Interfaces.WikipediaManagement.v1;
    using WikipediaKepler.Interfaces.WikipediaManagement.v1.Models;
    using WikipediaKepler.Services.WikipediaManagement.v1;
    
    namespace WikipediaKepler.Tests.WikipediaManagement.v1
    {
        [TestFixture]
        public class WikipediaServiceTests
        {
            private Mock<IHelper> _helperMock;
            private Mock<ILog> _loggerMock;
            private Mock<IRestService> _restService;
    
            private const int _ONE_THOUSAND = 1000;
            private const string _WIDGETS = "Widgets";
            private readonly Random _rnd = new Random();
    
            [SetUp]
            public void SetUp()
            {
                _helperMock = new Mock<IHelper>();
                _loggerMock = new Mock<ILog>();
                _restService = new Mock<IRestService>();
            }
    
            [TearDown]
            public void TearDown()
            {
                _helperMock = null;
                _loggerMock = null;
                _restService = null;
            }
        }
    }
  10. Implement a unit test for the GetCategoriesByPrefixAsync() method.
    The following code illustrates a basic unit test for the GetCategoriesByPrefixAsync() method in the WikipediaServiceTests.cs file.
    ...
    private readonly Random _rnd = new Random();
      
    [Test]
    [Description("Verifies that when GetCategoriesByPrefixAsync is called a call is made to the Wikipedia API and matching categories are returned.")]
    public async Task GetCategoriesByPrefixAsync_MatchingPrefix_ReturnsMatchingCategories()
    {
    	string prefix = "pre";
    	string expectedCategory = $"{prefix}determined";
    	var responseJson = new JObject
    	{
    		["batchcomplete"] = "",
    		["query"] = new JObject
    		{
    			["pages"] = new JObject
    			{
    				["123456"] = new JObject
    				{
    					["pageid"] = _rnd.Next(0,_ONE_THOUSAND),
    					["ns"] = _rnd.Next(0,_ONE_THOUSAND),
    					["title"] = $"Category:{expectedCategory}"
    				}
    			}
    		}
    	};
    	var response = new HttpResponseMessage
    	{
    		StatusCode = HttpStatusCode.OK,
    		Content = new StringContent(responseJson.ToString())
    	};
    	_restService.Setup(_ => _.GetAsync(It.IsAny<string>())).ReturnsAsync(response);
    	var service = new WikipediaService(_helperMock.Object, _loggerMock.Object, _restService.Object);
    	List<CategoryResponseModel> actual = await service.GetCategoriesByPrefixAsync(prefix);
    	Assert.That(actual.Count, Is.EqualTo(1));
    	Assert.That(actual.First().Title, Is.EqualTo(expectedCategory));
    }
    ...
  11. Run the test and verify that it passes.
    Use this test as a model for adding coverage for edge cases, such as the behavior of your method when it receives an error from the Wikipedia API.
    To quickly run all the tests, open the Test Explorer window in Visual Studio and click Run All Tests, or in the toolbar, click Tests > Run All Tests.

Step 9 - Deploy and verify

After confirming that the interface successfully retrieves information from the Wikipedia API, and passes your local tests, you can add it to an application.

Use the following steps to add the interface to an application:

  1. Log in to your Relativity instance.
  2. Click RelativityOne icon to display your home page.
  3. In the Sidebar, click Other Tabs > Applications & Scripts > Resource Files.
    The Resource Files tab appears.

    (Click to expand)

    Resource Files option

  4. Click New Resource File.
    The Resource File Information dialog appears.
  5. In the Name filter, type WikipediaKepler.
    You should see the two .dlls and the 2 pds files that you uploaded in Step 2 - Deploy to Relativity.

    (Click to expand)

    Resource File tab

  6. Click WikipediaKepler.Services.dll to update the implementation code.
  7. Click Edit.
    The Resource File Information dialog appears.
  8. Click Clear in the Resource File field.

    (Click to expand)

  9. Click Browse button to select your compiled WikipediaKepler.Services.dll to add as a new resource file.
  10. Click Save.
    The updated .dll is uploaded to Relativity and the service is redeployed.
  11. Repeat steps 5 - 10 for the WikipediaKepler.Interfaces.dll, WikipediaKepler.Services.pdb, and the WikipediaKepler.Interfaces.pdb files.
  12. Use the following sample REST calls to see how your service functions in a Relativity environment after waiting a few minutes for the service to redeploy.
    Your results might not exactly match the JSON in the expected results for these examples, because they execute against the live Wikipedia API and may change.
  13. Try out other API calls with your Kepler service.