Immediate Answer
You asked: "Is there a simple way to determine what pipelines run for an API call?"
In a nutshell, the only way to know for sure what an API controller is doing is to decompile the controller and take a peek. So, if that's not considered "simple", then the answer to your question is probably no.
That being said, I'd like to offer up some information specifically on the Contact Search functionality that you are asking about, and provide you with a path for adding to the results that you are wanting to extend.
I'll start with an explanation of how Contact Search works. I will also add that a lot of the API controllers have similar methodologies.
Understanding how Contact Search Works
The Contact API Controller, when Search() is executed, performs a Find(parameters)
method of the configured ContactSearchProvider
.
This is defined in the Sitecore.ExperienceProfile.config as such:
<providers>
<!-- Provides contact search functionality. -->
<contactSearchProvider type="Sitecore.Cintel.ContactSearchProvider, Sitecore.Cintel" singleInstance="true" />
</providers>
The ContactSearchProvider provides the brains behind how the search is performed and returns a ResultSet
list of results based off of the interface IContactSearchResult
.
Once the Search is complete, the Contact Controller then passes the results off of to a Result Transformer, that takes the results and converts them into a JSON response that is then passed through the API controller and back to the client browser.
The Result Transformer that is used in the Experience Profile is called ContactSearchResultTransformer
and is defined in the config file Sitecore.ExperienceProfile.Client.config
<contactSearchResultTransformer>
<add key="default" type="Sitecore.Cintel.Client.Transformers.Contact.ContactSearchResultTransformer, Sitecore.Cintel.Client" />
</contactSearchResultTransformer>
A look at the out of the box result transformer reveals an example on how to extend the JSON results.
public object Transform(ResultSet<List<IContactSearchResult>> resultSet)
{
Assert.ArgumentNotNull((object) resultSet, "resultSet");
if (resultSet.Data.Dataset.Count == 0)
return (object) resultSet;
List<IContactSearchResult> source = resultSet.Data.Dataset.FirstOrDefault<KeyValuePair<string, List<IContactSearchResult>>>().Value;
if (source == null)
return (object) resultSet;
DateTime nowTime = DateTime.UtcNow;
List<ContactSearchResultEx> list = source.Select<IContactSearchResult, ContactSearchResultEx>((Func<IContactSearchResult, ContactSearchResultEx>) (r => this.ExtendResult(r, nowTime))).ToList<ContactSearchResultEx>();
source.Clear();
source.AddRange((IEnumerable<IContactSearchResult>) list);
return (object) resultSet;
}
private ContactSearchResultEx ExtendResult(IContactSearchResult result, DateTime nowTime)
{
ContactSearchResultEx contactSearchResultEx = new ContactSearchResultEx(result);
contactSearchResultEx.FormattedLatestVisitStartDateTime = this.timeConverter.FormatDateTime(result.LatestVisitStartDateTime);
contactSearchResultEx.Recency = this.timeConverter.GetRecency(result.LatestVisitStartDateTime, nowTime);
contactSearchResultEx.LatestVisitLocationDisplayName = this.textConverter.GetLocation(result.LatestVisitLocationCityDisplayName, result.LatestVisitLocationRegionDisplayName, result.LatestVisitLocationCountryDisplayName);
contactSearchResultEx.EmailAddressExt = this.textConverter.GetEmail((object) result.PreferredEmail);
string duration = this.timeConverter.GetDuration(result.LatestVisitStartDateTime, result.LatestVisitEndDateTime);
contactSearchResultEx.LatestDuration = duration;
string[] strArray = new string[3]
{
result.FirstName,
result.MiddleName,
result.Surname
};
string fullName = this.textConverter.GetFullName((ContactIdentificationLevel) result.IdentificationLevel, strArray);
contactSearchResultEx.FullName = fullName;
return contactSearchResultEx;
}
The magic here is that Transform() returns an object
and not a concrete class.
Notice the method ExtendResult()
returns a ContactSearchResultEx
object. A look at this class shows that it is adding 6 different, additional JSON values to the search result.
[JsonProperty("formattedLatestVisitStartDateTime")]
public string FormattedLatestVisitStartDateTime { get; set; }
[JsonProperty("recency")]
public string Recency { get; set; }
[JsonProperty("latestVisitLocationDisplayName")]
public string LatestVisitLocationDisplayName { get; set; }
[JsonProperty("emailAddressExt")]
public string EmailAddressExt { get; set; }
[JsonProperty("fullName")]
public string FullName { get; set; }
[JsonProperty("latestDuration")]
public string LatestDuration { get; set; }
Extending the Contact Search Results Further
With a basic understanding of how the Contact Search works currently, to add additional fields, I would do the following:
Start with creating a new ContactSearchResultExtended class that inherits from ContactSearchResultEx
, adding the appropriate JSON properties to represent the new data that you want to return.
Create a custom ContactSearchResultTransformer
class that mimics the same functionality as the out of the box class, but adds in whatever logic is needed in order to add the additional data to the result set in your own ExtendResult method. Keep in mind that you pass in the result which contains Contact ID so that if you need to perform operations on the Contact ID in this method, you can. I would also preserve whatever logic is needed to satisfy the original extended results as well.
Create the appropriate patch configs to apply the config changes needed to utilize the new custom <contactSearchResultTransformer>
.
A Pipeline Approach (Theoretically)
Sadly, as shown here, there's really not a pipeline utilized to adjust, extend, or affect the result list. It's all in the configured Result Transformer. However, I love looking at unpipelined logic and seeing where I might be able to apply a pipeline.
In this case, the Transform()
method of the Result Transformer seems like a potential candidate for adding a pipline. Specifically, I can see a pipeline process that allows for the addition of additional JSON values as part of the embedded function:
List<ContactSearchResultEx> list = source.Select<IContactSearchResult, ContactSearchResultEx>((Func<IContactSearchResult, ContactSearchResultEx>) (r => this.ExtendResult(r, nowTime))).ToList<ContactSearchResultEx>();
Then each processor would be responsible for adding values to the result. Obviously, I haven't actually tried this, nor do I think it's actually needed since you can easily just create your own transform results.
However... it's a possible thought.