

Last date modified: July 07 2025
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:
Estimated completion time - 2-3 hours
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
2
3
4
5
6
7
8
9
10
11
12
13
14
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");
}
}
1
2
3
4
5
6
7
8
9
public override FieldCollection RequiredFields
{
get
{
var retVal = new FieldCollection();
retVal.Add(new kCura.EventHandler.Field(Constants.CategoryNameGUID));
return retVal;
}
}
1
[kCura.EventHandler.CustomAttributes.Description("Validate Article Category Event handler")]
1
[System.Runtime.InteropServices.Guid("a4c0eefd-0113-4737-8f2c-4de6b8309c7d")]
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:
In the next section, you run your event handler.
After adding your event handler assembly to Relativity, you can now remotely debug it.
Debugging an event handler is slightly different than debugging a Kepler service. Be sure to review the following debugging steps. For information about Kepler services, see Lesson 3 - Create a RESTful API.
Use the following steps to remotely debug your 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 3 - Create a RESTful API for this purpose.
Use the following steps to add validation logic:
1
2
3
4
5
6
7
8
9
10
11
12
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
1
2
3
4
public async Task<bool> EnsureCategoryExistsInWiki(IWikipediaService wikiService, string category)
{
return false;
}
EnsureCategoryWithThatNameExists
1
2
3
4
public async Task<bool> EnsureCategoryWithThatNameExists(string name, IObjectManager objectManager, IEHHelper helper)
{
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
1
2
3
4
5
6
ILog Logger;
public ValidateArticleCategoryEventHandler()
{
this.Logger = Log.Logger;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
}
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
2
3
4
5
public async Task<bool> EnsureCategoryExistsInWiki(IWikipediaService wikiService, string category)
{
var foundCategories = await wikiService.GetCategoriesByPrefixAsync(category);
return foundCategories.Exists(s => s.Title == category);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
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
2
3
4
5
6
7
8
9
10
11
12
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;
1
2
3
4
5
6
7
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" };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[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());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[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);
}
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.
On this page
Why was this not helpful?
Check one that applies.
Thank you for your feedback.
Want to tell us more?
Great!
Additional Resources |
|||
DevHelp Community | GitHub | Release Notes | NuGet |