# Client Matter — Execution flow
This page describes the step-by-step execution of ClientMatterInformationHandler.Execute(), the orchestrator that runs every time a Client Matter file is processed. Steps are numbered in execution order.
# 1. Input validation
If no CreateUpdateClientMatter object is present, execution stops immediately with a failure result.
# 2. Existing-matter lookup
When a MatterCode is present, the handler queries the MattersList to check whether this matter has been provisioned before. The query returns the matter's stored URL, site URL, and the ClientMatterDesign that was used to create it.
The result drives two decisions:
- Design detection — if a design is stored in the MattersList entry (in the
ClientMatterStructurecolumn), it is applied to the current request before any other design resolution runs. This guarantees that updates always use the same structural design as the original provisioning. - New vs. existing distinction — see step 4 below.
If MatterCodeIsNotUnique is enabled in the configuration, matter codes are not globally unique — the same matter code can exist under different clients. In that case, the handler first queries the client list to obtain the client's list item ID, then uses the combination of that client ID and the matter code to find the correct matter entry in the MattersList.
# 3. MonitorDirectory routing
MonitorDirectory signals that the current service instance should not process this request locally — instead, it should forward the JSON file to a different service instance (one watching that directory) that owns the correct configuration for this matter.
Routing triggers under two conditions:
No MatterCode: If MatterCode is empty and MonitorDirectory is set, the handler clears the MonitorDirectory field, saves the JSON to that directory, and returns success. Since no code is present, the receiving instance handles code assignment and provisioning.
This path only applies to JSON requests; Excel rows always have a MatterCode.
MatterCode present: After the existing-matter lookup (step 2), if MonitorDirectory is set, the handler evaluates the matter's stored URL:
| MattersList URL state | Action |
|---|---|
Not found (null) | Calls ParseMatterStatus; if matter is Closed, stops with success — no point forwarding a closed matter that was never provisioned. Otherwise routes. |
| Empty string (no URL yet) | Routes to MonitorDirectory. |
Starts with http (already provisioned) | Routes to MonitorDirectory — the receiving instance handles the update in the correct environment. |
| Non-HTTP local path (intermediate state) | Routing is skipped. Execution continues locally on the current service instance. |
When routing, the MonitorDirectory field is cleared from the request before saving, so the receiving instance processes it as a normal local request.
Duplicate-file avoidance: Before writing the ClientMatter_<ClientCode>_<MatterCode>.json file into the target MonitorDirectory, the handler checks whether an identical file already exists and skips the write if so (the routing step still reports success, just without creating a new file). The check looks in three places, in order, stopping at the first match:
- The target filename itself, if present.
- The same filename with an
active.prefix — a copy currently picked up for processing by the file-watcher on the receiving instance (see theactive.convention in the file handlers overview). - The most recently modified file matching
<name>_*<extension>in the directory'sCompletedsubfolder — a previously processed copy of the same request.
If a match is found and its content is identical to the new file's content, the write is skipped entirely. This avoids queuing redundant updates when the same matter change is submitted more than once in quick succession.
# 4. New vs. existing matter
The stored URL from the MattersList lookup (step 2) determines the processing path:
| URL state | Meaning | Behaviour |
|---|---|---|
null (not found) | New matter | Full provisioning path; config switching allowed |
| Empty string | In MattersList but no URL yet | Treated as new |
Starts with http | Already provisioned | Update path; original SharepointCfg is kept |
| Other (local path) | Intermediate state | Local processing continues with current config |
Update path: when a matter already has a provisioned URL, the SharepointCfg is locked to the one originally used (the one whose SharePoint environment contains the matter). Config switching (step 5) is skipped. This ensures updates always run against the original environment.
# 5. Configuration switching
If the request specifies a SharepointCfg name different from the current configuration, and the matter is new (not yet provisioned), the handler loads and clones that configuration and uses it for the rest of execution. This allows a single service instance to provision matters across multiple SharePoint environments based on per-matter input.
For existing (already-provisioned) matters, configuration switching is skipped — the original config is always kept so updates land in the correct environment.
# 6. Auto-numbering
If ClientCodeStartNumber is configured and no ClientCode was provided, the handler calls AutoNumber.GetNextID to assign the next available client code. The same applies to MatterCode when MatterCodeStartNumber is configured.
When either code is auto-assigned and the request was loaded from a JSON file, the file is overwritten with the assigned codes so the source file reflects what was actually used.
After this step, if both ClientCode and MatterCode are still empty, execution stops with a failure result.
See Auto-numbering for a full description of format strings, counter storage, and reset behaviour.
# 7. Dynamic ClientMatterStructure and URL resolution
Two values are resolved through the GetDynamicNameValue pipeline, which evaluates dynamic placeholders (tokens from the matter's properties) against the configuration:
ClientMatterStructure— resolves the matter structure/design name. If the resolved value matches a knownClientMatterDesignenum, it overrides the design for this execution.ClientMatterServerRelativeUrl— resolves the root server-relative URL under which the matter site or library will be created.
Both values can be static strings or dynamic expressions referencing matter properties such as client code, matter type, or area of law.
After dynamic resolution, explicit overrides from the request are applied:
- If
ClientMatterDesignis set on the request, it overrides the configuration's design value. - If
ServerRelativeUrlis set on the request, it overrides the resolved server-relative URL.
# 8. Matter status parsing
ParseMatterStatus reads the matter's status value from its properties and maps it to either Open or Closed using the configuration below. If the feature is not configured, the status remains unset and all operations proceed unconditionally.
| Configuration property | Description |
|---|---|
SettingsCfg.Provisioning.MatterStatusColumnName | Name of the column in the MattersList that holds the status value. Leave empty to disable status-based behaviour entirely. |
SettingsCfg.Provisioning.MatterStatusOpenedValues | Array of column values that mean the matter is open (e.g. ["Opened", "Geopend"]). |
SettingsCfg.Provisioning.MatterStatusClosedValues | Array of column values that mean the matter is closed (e.g. ["Closed", "Gesloten"]). At least one value must be configured here for status parsing to activate. |
The status value is read from the matter's properties using MatterStatusColumnName. It is matched case-insensitively against MatterStatusClosedValues first, then MatterStatusOpenedValues. If no match is found the status remains unset.
The resolved status is used in two places:
- Step 3 (MonitorDirectory routing): if a matter is not in the MattersList and is
Closed, execution stops immediately — there is no point routing a matter that was already closed before it was ever provisioned. - Design handler execution (step 10): when a matter is
Closed, design handlers setSkipIfNotExists = trueon the matter list and site/library operations. This means a closed matter can only update structures that already exist — it cannot create new sites, libraries, or list items.
# 9. Design handler selection
The handler selects an IClientMatterDesignHandler based on ClientMatterDesign:
| Design | SharePoint structure |
|---|---|
MatterSiteCollection | Matter gets its own top-level site collection |
MatterSite | Matter is a sub-site within an existing site collection |
MatterDocLib | Matter is a document library within an existing site |
ClientSiteCollection_MatterSite | Client has its own site collection; matter is a sub-site within it |
ClientSiteCollection_MatterDocLib | Client has its own site collection; matter is a document library within it |
ClientSite_MatterSite | Client has a site (not a full site collection); matter is a sub-site within it |
ClientSite_MatterDocLib | Client has a site; matter is a document library within it |
MatterOffice365Group | Matter is a Microsoft 365 Group/Team (SharePoint Online only) |
If no handler is found for the configured design, an exception is thrown.
# Round-robin URL selection
When SharepointRootServerRelativeUrl is configured as a semicolon-separated list of multiple URLs, the handler distributes matters across those URL prefixes using a round-robin algorithm (GetSharepointRootServerRelativeUrl). The algorithm counts existing sites under each URL and picks the one with the fewest matters, refreshing its counts when they become stale. Access is serialised per configuration to prevent two concurrent requests from selecting the same target URL.
# 10. Design handler execution
The selected design handler runs and returns an ActionResult. The handler flags (ClientCreated, MatterCreated, ClientListItemCreated, MatterListItemCreated) are propagated back to the caller after execution.
# 11. Post-success steps
The following steps run only when the design handler reports success.
# My Matters list
The matter's entry in the My Matters list is renamed if the matter name changed. The previous name is read from the MatterName column of the existing matter list item (falling back to Title with the code prefix stripped if MatterName is empty). The previous title is then stripped of any client name prefix before the rename to avoid double-prefixing (e.g. Acme - Contract review → Contract review).
See Matter list title columns for how MatterName, Title, and NormalizedTitle relate and how the title format is configured.
# Extranet integration
If an extranet is configured, ICreateUpdateExtranet builds a duplicate provisioning request targeting the extranet environment (adjusting codes, names, design, and URL), dispatches it to the correct service instance, and writes the assigned extranet matter code back into the original matter's list entry.
See Extranet integration for the full description of both extranet modes, column mapping, external dispatch, and all configuration properties.
# Client and matter URL write-back
The absolute URLs of the matter site and document library (URL, SiteURL, Epona365URL) are written back into the matter's properties so subsequent steps and list updates can use the final SharePoint addresses.
For designs with a client site or site collection, the client SiteURL is also written back.
# Master monitor directory (optional)
If SettingsCfg.Provisioning.MatterListMonitorDirectory is configured, the handler writes two JSON files to that directory:
ClientList_<ClientCode>.json— client properties for the master environment's client listMatterList_<ClientCode>_<MatterCode>.json— matter properties for the master environment's matter list
This allows a secondary service instance (watching the master directory) to keep its own client/matter lists in sync after the matter is provisioned in the primary environment.
# Update default values (optional)
If SettingsCfg.Provisioning.UpdateDefaultValuesOnDocumentsMonitorDirectory is configured, the handler writes an AssignDefaultValuesInDocLib_<ClientCode>_<MatterCode>.json file to that directory. This triggers the Update Default Values job to apply the matter's metadata as default column values on all existing documents in the matter's document libraries.
If a file with identical content already exists at that path, the write is skipped to avoid unnecessary reprocessing.
# 12. Finally: post-handlers and lock release
Regardless of success or failure, the finally block runs:
- Post-handlers — all registered
IClientMatterPostHandlerimplementations are invoked in sequence. Each receives the connection, the action result, and theCreateUpdateClientMatterobject. Errors in individual post-handlers are caught and logged but do not affect the overall result. - Lock release — concurrency locks held for the client code and the client/matter code pair are released, allowing any queued requests for the same matter to proceed.
# Client and matter identifiers
Each client and matter has two separate identifiers that serve different purposes.
ClientCode / MatterCode are the functional codes used throughout provisioning: SharePoint list lookups, site URL generation, filenames, log output, and concurrency lock keys.
ClientID / MatterID are external system identifiers supplied by the originating system (case management, ERP, etc.). They are stored in the external ID column of the client list and matter list respectively.
Which column that is depends on when the list was set up. Newer lists have a column called ExternalID; lists set up before it existed hold the value in a column called ClientID or MatterID. Provisioning looks for ExternalID first and falls back to ClientID (client list) or MatterID (matter list), matching on either the internal name or the display name, so both layouts work without configuration. If a list has none of them, every step below that depends on the external ID is skipped and matching happens on Code alone.
# Lookup priority
When CreateOrUpdateItem runs against a SharePoint list:
ClientID/MatterIDsupplied and the list has an external ID column — the item is looked up by that column. TheCodecolumn is still written but is not used for matching.- That lookup found nothing — the item is looked up by
Code(ClientCode/MatterCode) instead. When exactly one item matches and it has noExternalIDyet, that item is updated in place and theExternalIDis assigned to it. See Adopting records that predate the external ID. - That lookup also found nothing and a secondary match column is configured — the item is looked up by that column instead, with the same adoption rules as step 2. Switched off unless configured. See Records provisioned under a different code.
- No
ClientID/MatterIDsupplied, or the list has no external ID column — the item is looked up byCode(ClientCode/MatterCode). Codeis empty — the external ID is used as the code value as a fallback.
This means a matter is found by its external system ID even if its code changes over time — as long as the list has an external ID column and the ID is supplied on each request.
# Adopting records that predate the external ID
Step 2 covers the case where a source system starts supplying an external ID that it never supplied before. Client and matter records created earlier have an empty ExternalID, so the lookup in step 1 finds nothing. Without step 2 every one of those records would get a second list item carrying the same Code, which in turn makes the provisioning treat the document library as new. Step 2 recognises the existing record by its code instead and assigns the external ID to it, so the next run matches on ExternalID as normal.
Adoption is deliberately narrow. The existing item is only adopted when:
- exactly one item has that code — when several do, the run continues as before (a new item is added) and a warning naming the list, code and external ID is written to the log;
- that item has no
ExternalID— an item that already carries a different external ID belongs to another record and is never taken over; - the code identifies a single item — with
MatterCodeIsNotUniquethe same matter code exists for several clients, so on the matter list the client must be known before a code lookup means anything; - the configuration provisions no extranet — adoption is off for both lists there, see below.
# Records provisioned under a different code
Step 2 only helps when the existing record carries the same code as the source system now sends. It does not when the records were provisioned under an older numbering scheme and the source system numbers them differently — then neither the ExternalID nor the Code matches, and a second record is added even though the first one is plainly the same client or matter.
For that case the client and matter lists each take an optional secondary match column, in Global Settings → Provisioning:
| Setting | Applies to |
|---|---|
| Client Secondary Match Column | the client list |
| Matter Secondary Match Column | the matter list |
Set it to a column on that list which already holds the code the source system sends — often a column populated during the migration to the new system, for example a Client Matter Number column holding the new matter number while Code still holds the old one. The column may be named by its display name or its internal name.
When both the ExternalID and the Code lookup find nothing, the incoming code is matched against this column. If exactly one item matches and it has no ExternalID yet, that item is adopted: the ExternalID is assigned and the Code is brought in line with the source system, so from the next run onwards the record is found on ExternalID like any other. The record keeps its existing site collection and document library — nothing is moved or recreated.
Leave both settings empty, which is the default, and nothing changes.
The secondary column is only consulted when the source system supplies a ClientID/MatterID — it is part of steps 1 to 3, not of step 4. An integration that matches on Code alone never reaches it, because there is no external ID to assign.
Every guard from step 2 applies unchanged — exactly one match, an empty ExternalID, the client scope under MatterCodeIsNotUnique, and the extranet carve-out below. A configured column that does not exist on the list is reported as a warning and the lookup is skipped.
Matching on a mapped column is weaker than matching on the code, which is why this is opt-in. Only use a column that holds the source system's code for exactly the record it belongs to; verify it across the whole list before switching it on.
# Adoption and the extranet
A configuration with Extranet.ExternalMonitorDirectory set adds its own entries to the matter list, under their own matter code but carrying the matter id of the matter they shadow. Adopting there would let an extranet entry take over the matter it belongs to.
Adoption is therefore switched off for both lists of such a configuration, which keeps its previous behaviour throughout: a second item is added when the ExternalID lookup finds nothing, on the client list as well as the matter list. The shadow entries only ever land in the matter list, so adopting on the client list would have been safe on its own terms — it is switched off regardless, so that an extranet configuration is not asked to rely on a safety argument covering only one of its two lists. An extranet environment that needs the duplicate fix should be looked at on its own.
# What does not run for an adopted record
Because the item is updated rather than created:
- the confirmation email for a new matter is not sent (
SettingsCfg.Email.ClientMatter.NewMatter), which is correct — the matter already existed; - no My Matters entry is added; the entry for that matter already exists, and on the previous behaviour the entry was added for the second, duplicate item;
- creation-only document library work (template import, content types, views) is not repeated against the existing library;
- for a client-only record (no matter code) and for a locked matter, provisioning no longer stops after the client list. Both used to end there when the client entry had just been created; an adopted client entry counts as found rather than created, so the record continues into client site provisioning and, for a locked matter, on to the matter list. From that point each follows exactly the path a record whose client already existed follows today.
The confirmation email post handler is the one that behaves differently, for the reason in the first bullet above. Every other post handler only records the created flags and runs for an adopted record exactly as before.
For a record whose code also changed, adoption cannot help — nothing links the old and the new code. Use __Code__ on the Client List or Matter List handler to rename the code first.
# Related links
- Client Matter handler — columns, format reference, and filename pattern
- Matter List handler — create or update matter list entries without full provisioning
- Client List handler — create or update client list entries without full provisioning