Lesson 3 - Validate object changes

Relativity Dynamic Objects (RDOs) are objects you define. You can set properties for them, manage data links to other objects, and incorporate additional features including event handlers and object rules.

Event handlers manipulate or validate data that users see or modify through the Relativity UI.

In this lesson, you will learn how to complete these tasks:

  • Create a Relativity Dynamic Object (RDO) to categorize objects.
  • Implement a Pre Save event handler using the Relativity Visual Studio templates.
  • Use an event handler to apply unique business logic to documents and RDOs.
  • Attach event handlers to RDOs and work with GUIDs.
  • Add validation logic to an event handler that uses the MediaWiki API.
  • Query data by making calls to Object Manager API from the event handler.
  • Write testable, scalable and maintainable code for event handlers

Estimated completion time - 2-3 hours

Step 1 - Implement a Pre Save event handler

You can use the Relativity templates for Visual Studio to implement event handlers and other entities. For more information, see Visual Studio templates

You can find general information about event handlers on Best practices for event handlers.

In this lesson, you use the event handler template to implement a Pre Save event handler, which validates the Name field on an Article Category object when it is created and updated. A Pre Save event handler performs this validation before the data is saved to the database.

Use the following steps to implement the event handler:

  1. Download the Relativity templates from Visual Studio Marketplace.
  2. Install the templates in Visual Studio, if you haven't already done so.
  3. In Visual Studio, open the HelloWikipedia solution that you created in Lesson 2 - Create a RESTful API.
    You will be using the IWikipediaService class in the solution.
  4. Use the Pre Save event handler template to create a new project called ValidateArticleCategoryEventHandler in the HelloWikipedia solution.

    Add a new project dialog

  5. Enter ValidateArticleCategoryEventHandler in the Project name field.

    Configure your new project dialog

  6. Rename the PreSaveEventHandler.cs file in the project to ValidateArticleCategoryEventHandler.cs.
  7. If Visual Studio doesn't prompt you to update the class name, then rename the PreSaveEventHandler class name to ValidateArticleCategoryEventHandler in the PreSaveEventHandler.cs file.

    Renamed class example

  8. Create a file called Constants.cs in the ValidateArticleCategoryEventHandler project.
  9. Add the GUIDs for ArticleCategory object and the Name field for this object from the application created in Lesson 1 - Build an application without any code by completing these steps:
    • Log in to your Relativity instance.
    • Navigate to the details views of the Hello Wikipedia application.
    • Click Show Component GUIDs in the Application console.
    • Note the GUIDs for the Article Category and the Article Category – Name.

      Application Components list view

  10. Add the following code for the Constants class to the Constants.cs file.This class provides values used in the implementation of the other classes.

    Note: The GUIDs in this sample code differ from the GUIDs generated by your Relativity instance. Update the GUIDs in the code with those from your instance.

    Copy
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace ValidateArticleCategoryEventHandler
    {
        public static class Constants
        {
            public static readonly Guid CategoryNameGUID = new Guid("16D8A362-2923-45B7-8444-7339C57B3AF0");
            public static readonly Guid ArticleCategoryGUID = new Guid("6B20F149-1B17-4E9C-8403-439E98E8BFD2");
        }
    }
  11. Override the RequiredFields property in the ValidateArticleCategoryEventHandler class by adding a new Field object called Name.
    Copy
    public override FieldCollection RequiredFields
    {
        get
        {
            var retVal = new FieldCollection();
            retVal.Add(new kCura.EventHandler.Field(Constants.CategoryNameGUID));
            return retVal;
        }
    }
  12. Set the CustomAttributes.Description attribute to Validate Article Category Event handler.

    Copy
    [kCura.EventHandler.CustomAttributes.Description("Validate Article Category Event handler")]
  13. Create a new GUID to identify the event handler.

    Use the GUID generator in Visual Studio.

    Create GUID dialog

    Verify that your code is like the following sample:

    Copy
    [System.Runtime.InteropServices.Guid("a4c0eefd-0113-4737-8f2c-4de6b8309c7d")]
  14. Build your event handler project by clicking Build > Build Solution.

Step 2 - Upload the event handler assembly to Relativity

To use the event handler, you must upload it to Relativity and associate it with an application.

Use the following steps to upload your event handler assembly:

  1. Locate My First Workspace created in Lesson 1 - Build an application without any code.
  2. Navigate to the Resource File tab and click New Resource File.
  3. In the Resource File field, click Browse to select the ValidateArticleCategoryEventHandler.dll from the directory where you built the project. See Step 1 - Implement a Pre Save event handler.
  4. In the Application field, click ellipsis button to select Hello Wikipedia, which is the application created in Lesson 1 - Build an application without any code.
    This action links your event handler assembly to the Hello Wikipedia application.
  5. Click Save.
  6. Upload the ValidateArticleCategoryEventHandler.pdb by repeating steps 4-6.
    Uploading the .pdb file to facilitates remote debugging the event handler.

    Application & Scripts tab

  7. In your workspace, locate the Object Type tab.
  8. Navigate to the details view of the Article Category object type.
  9. To attach the event handler to your new object type, click New on the Event Handlers associative list.
  10. Select your event handler in the dialog box.

    Select Event Handler dialog

In the next section, you run your event handler.

Step 3 - Add validation logic to an event handler

You can update your event handler to validate whether a newly created or updated article category is valid in Wikipedia. Use the GetCategoriesByPrefixAsync() method that was implemented in Lesson 2 - Create a RESTful API for this purpose.

Use the following steps to add validation logic:

  1. Add the following project references to the ValidateArticleCategoryEventHandler project by right-clicking on References > Add Reference >Projects tab:
    • WikipediaKepler.Interfaces

    Reference Manager

  2. Verify that the references display in the Solution Explorer:

    Solution Explorer

  3. Add a new class named ValidateArticleCategoryEventHandlerJob to the ValidateArticleCategoryEventHandler project and make sure that it's public.

    New class in Visual Studio

  4. Update the ValidateArticleCategoryEventHandlerJob class by following statements:
    Copy
    using kCura.EventHandler; 
    using Relativity.API; 
    using Relativity.Kepler.Logging; 
    using Relativity.Services.Objects; 
    using Relativity.Services.Objects.DataContracts; 
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Net; 
    using System.Text; 
    using System.Threading.Tasks; 
    using WikipediaKepler.Interfaces.WikipediaManagement.v1;
  5. Update the ValidateArticleCategoryEventHandlerJob class by adding the following properties and the constructor:
    Copy
    ILog logger;
    IWikipediaService wikiService;
    IObjectManager objectManager;
    IEHHelper helper;
    Artifact activeArtifact;

    public ValidateArticleCategoryEventHandlerJob(Artifact activeArtifact, ILog logger, IWikipediaService wikiService, IObjectManager objectManager, IEHHelper helper)
    {
        this.logger = logger;
        this.wikiService = wikiService;
        this.objectManager = objectManager;
        this.helper = helper;
        this.activeArtifact = activeArtifact;
    }
  6. Update the ValidateArticleCategoryEventHandlerJob class by adding the EnsureCategoryExistsInWiki() and ExecuteAsync() methods
    • EnsureCategoryExistsInWiki()
      Copy
      public async Task<bool> EnsureCategoryExistsInWiki(IWikipediaService wikiService, string category) 

          return false
      }
    • EnsureCategoryWithThatNameExists

      Copy
      public async Task<bool> EnsureCategoryWithThatNameExists(name, objectManager, helper) 

          return false
      }
    • ExecuteAsync()
      Copy
      public async Task<Response> ExecuteAsync()
      {
          ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

          kCura.EventHandler.Response retVal = new kCura.EventHandler.Response();
          retVal.Success = true;
          retVal.Message = string.Empty;

          var name = activeArtifact.Fields[Constants.CategoryNameGUID.ToString()].Value.Value.ToString();

          if (!await EnsureCategoryExistsInWiki(wikiService, name))
          {
              retVal.Success = false;
              retVal.Message = $"There is no such category in wiki: {name}";
              return retVal;
          }

          if (await EnsureCategoryWithThatNameExists(name, objectManager, helper))
          {
              retVal.Success = false;
              retVal.Message = $"There is an existing category with the same name: {name}. Try picking a different name.";
              return retVal;
          }

          return retVal;
      }
  7. Update references in ValidateArticleCategoryEventHandler class as follows:
    Copy
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Threading.Tasks;
    using kCura.EventHandler;
    using kCura.EventHandler.CustomAttributes;
    using Relativity.API;
    using Relativity.Kepler.Logging;
    using Relativity.Services.Objects;
    using Relativity.Services.Objects.DataContracts;
    using WikipediaKepler.Interfaces.WikipediaManagement.v1;
  8. Add the following property and constructor to the ValidateArticleCategoryEventHandler class.
    Copy
    ILog Logger;

    public ValidateArticleCategoryEventHandler()
    {
        this.Logger = Log.Logger;
    }
  9. Add the following code to modify Execute() method in ValidateArticleCategoryEventHandler class:
    Copy
    public override Response Execute()
    {
        var retVal = new Response();

        try
        {
            var serviceManager = Helper.GetServicesManager();
            Logger.LogVerbose($"Start '{nameof(Execute)}' method");
            using (var objectManager = serviceManager.CreateProxy<IObjectManager>(ExecutionIdentity.System))
            using (var wikiService = serviceManager.CreateProxy<IWikipediaService>(ExecutionIdentity.System))
            {
                var job = new ValidateArticleCategoryEventHandlerJob(this.ActiveArtifact, this.Logger, wikiService, objectManager, this.Helper);
                retVal = Task.Run(async () => await job.ExecuteAsync()).ConfigureAwait(false).GetAwaiter().GetResult();
            }

            Logger.LogDebug($"{nameof(retVal)}: {retVal}", null, retVal);
        }
        catch (Exception ex)
        {
            retVal.Success = false;
            retVal.Message = $"{ex}";
            Logger.LogError($"An error occured in '{nameof(Execute)}' method", ex, ex);
        }
        finally
        {
            Logger.LogVerbose($"End '{nameof(Execute)}' method");
        }

        return retVal;
    }
  10. Try out the validation by creating a new Article Category with the name SciFi movie.
    The red error message prevents you from saving the Article Category. Try editing one of the existing categories and notice the same error message.

    Validation message

Step 4 - Make service calls from the event handler

You can make calls to Relativity APIs from your event handler. For example, you can use the Object Manager API to query for duplicate article categories being added to the same workspace. You can also use the services exposed on this API to facilitate working with Document objects and RDOs. For more information, see Object Manager (.NET).

Use the following steps to make service calls from the event handler:

  1. To detect whether the Article Category exists in Wikipedia, call the Kepler service created in Lesson 2 - Create a RESTful API.

    Update the EnsureCategoryExistsInWiki() method in the ValidateArticleCategoryEventHandlerJob class as follows:

    Copy
    public async Task<bool> EnsureCategoryExistsInWiki(IWikipediaService wikiService, string category) 

         var foundCategories = await wikiService.GetCategoriesByPrefixAsync(category); 
         return foundCategories.Exists(s => s.Title == category); 
    }
  2. To detect duplicate article categories, update the EnsureCategoryWithThatNameExists() method in the ValidateArticleCategoryEventHandlerJob class as follows:
    Copy
    public async Task<bool> EnsureCategoryWithThatNameExists(string categoryName, IObjectManager objectManager, IEHHelper helper)
    {
        var currentWorkspaceArtifactID = helper.GetActiveCaseID();
        var queryRequest = new QueryRequest
        {
            ObjectType = new ObjectTypeRef { Guid = Constants.ArticleCategoryGUID },
            Fields = new List<FieldRef> { new FieldRef { Guid = Constants.CategoryNameGUID, Name = "Name" } },
            Condition = $"('Name' == '{categoryName}')",
        };

        var categoryObjects = await objectManager.QuerySlimAsync(currentWorkspaceArtifactID, queryRequest, 1, 1000);

        //Identify duplicates for all records except for the current record
        bool doesDuplicateExists = categoryObjects.Objects.Count > 0
           && categoryObjects.Objects.All(x => x.ArtifactID != activeArtifact.ArtifactID);
        return doesDuplicateExists;
    }
  3. Click Build > Build Solution in Visual Studio to verify that the code is building successfully.
  4. Upload the updated .dll and .pdb files for the event handler to test the validation code used when creating or editing an Article Category.
    Use the instructions in Step 2 - Upload the event handler assembly to Relativity.
  5. Create a new Article Category with name SciFi movie after .dll and .pdb files for the event handler are updated.

    This update should throw a validation exception because SciFi movie isn't a valid category in Wikipedia.

    Validation error for article category

Step 5 - Write unit tests

In this section, you write unit tests to verify that your code is working properly and to prevent breaking this logic in the future.

Use the following steps to write unit tests:

  1. Open the HelloWikipedia solution in Visual Studio.
  2. Use the Unit Test Project (.NET Framework) template to create a new project called ValidateArticleCategoryEventHandler.Tests.

    Unit Test Project template

  3. Enter ValidateArticleCategoryEventHandler.Tests in the Project name field.

    Configure your new project dialog

  4. Add the following project references to the ValidateArticleCategoryEventHandlerJobTests project by right-clicking on References > Add Reference >Projects tab:
    • WikipediaKepler.Interfaces
    • ValidateArticleCategoryEventHandler
  5. From the NuGet Package Manager in Visual Studio, uninstall the MSTest.TestAdapter and MSTest.TestFramework NuGet packages from the ValidateArticleCategoryEventHandler.Tests project.
  6. From the NuGet Package Manager in Visual Studio, install the following NuGet packaged in your test project:
    • NUnit 3.12.0
    • Moq 4.14.5 - use this package to mock the objects in your project.
    • Relativity.EventHandler 17.0.2
  7. Rename the UnitTest1.cs file in the project to ValidateArticleCategoryEventHandlerJobTests.cs.
  8. Rename the UnitTest1 class in the ValidateArticleCategoryEventHandlerJobTests.cs file to ValidateArticleCategoryEventHandlerJobTests.

    Rename test code class

  9. Add the following code to update the using statements in the ValidateArticleCategoryEventHandlerJobTests.cs file.
    Copy
    using Moq; 
    using NUnit.Framework; 
    using Relativity.API; 
    using Relativity.Kepler.Logging; 
    using Relativity.Services.Objects; 
    using Relativity.Services.Objects.DataContracts; 
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Threading.Tasks; 
    using WikipediaKepler.Interfaces.WikipediaManagement.v1; 
    using WikipediaKepler.Interfaces.WikipediaManagement.v1.Models;
  10. Update the class attribute from [TestClass] to [TestFixture].
  11. Delete the TestMethod1() method from the ValidateArticleCategoryEventHandlerJobTests class.

    TextFixture code

  12. Add the following properties to the ValidateArticleCategoryEventHandlerJobTests.cs class:
    Copy
    IWikipediaService wikiService; 
    ILog logger; 
    IEHHelper helper; 
    kCura.EventHandler.Artifact activeArtifact;

    List<string> wikiCategories = new List<string> { "Category 1", "Category 2", "Category 4" }; 
    List<string> dbCategories = new List<string> { "Category 1", "Category 2", "Category 3" };
  13. Add the SetUp() method to the new class as follows:
    Copy
    [SetUp]

    public void SetUp()
    {
      var wiki = new Mock < IWikipediaService > ();
      wiki.Setup(s =>s.GetCategoriesByPrefixAsync(It.IsAny < string > ())).Returns(Task.FromResult(
      new List < CategoryResponseModel > (wikiCategories.Select(s =>new CategoryResponseModel {
        Title = s
      }))));

      wikiService = wiki.Object;
      logger = new Mock < ILog > ().Object;
      helper = new Mock < IEHHelper > ().Object;
      activeArtifact = new kCura.EventHandler.Artifact(0, null, 0, "", true, new kCura.EventHandler.FieldCollection());
    }
  14. Add the ValidateArticleCategory_TestExecuteLogic() method and using the TestCase attribute, add different test cases.
    Copy
    [Test]

    [Description("Testing Execute logic.")]
    [TestCase("Category 1", false, Description = "Testing category that exists in db AND in wiki")]
    [TestCase("Category 3", false, Description = "Testing category that exists in db AND NOT in wiki")]
    [TestCase("Category 4", true, Description = "Testing category that exists in wiki AND NOT in db")]
    [TestCase("Not existed category", false, Description = "Testing not existed category")]

    public async Task ValidateArticleCategory_TestExecuteLogic(string categoryName, bool expected)
    {
      var dbObjects = new List < RelativityObjectSlim > (
      dbCategories
      .Where(s =>s == categoryName)
      .Select(s =>new RelativityObjectSlim {
        Values = new List < object > {
          s
        }
      }));

      var objectManager = new Mock < IObjectManager > ();
      objectManager.Setup(s =>s.QuerySlimAsync(It.IsAny < int > (), It.IsAny < QueryRequest > (), It.IsAny < int > (), It.IsAny < int > ())).Returns(Task.FromResult(
      new QueryResultSlim {
        Objects = dbObjects
      }));

      var activeArtifact = new kCura.EventHandler.Artifact(1, null, 0, "", true, new kCura.EventHandler.FieldCollection());
      activeArtifact.Fields.Add(new kCura.EventHandler.Field(0, "Name", "Name", 0, null, 0, false, false, new kCura.EventHandler.FieldValue(categoryName),
      new List < Guid > {
        Constants.CategoryNameGUID
      }));

      var job = new ValidateArticleCategoryEventHandlerJob(activeArtifact, logger, wikiService, objectManager.Object, new Mock < IEHHelper > ().Object);

      // Act  
      var result = await job.ExecuteAsync();

      // Assert  
      Assert.AreEqual(result.Success, expected);
    }
  15. Build the test project.
  16. In Visual Studio, click Test > Run All Tests.
  17. Verify that all the tests passed.

After confirming that the validation logic works as expected, you can export the application. It contains the event handler attached to the Article Category object type, and it runs validation logic when a user saves an Article Category object.