Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions docs/using-the-sdk/search-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Let's start with making a simple search request by using one of the `Search` met
The returned search result rows are in form of a `Dictionary<string, object>` with the search result property name as key and the search result property value as value.

```csharp
// Let's search for all lists using a particular content type and
// Let's search for all lists using a particular content type and
// for the found rows return the listed "select" properties
SearchOptions searchOptions = new SearchOptions("contenttypeid:\"0x010100*\"")
{
Expand Down Expand Up @@ -46,12 +46,12 @@ SearchOptions searchOptions = new SearchOptions("contenttypeid:\"0x010100*\"")
{
TrimDuplicates = false,
SelectProperties = new System.Collections.Generic.List<string>() { "Path", "Url", "Title", "ListId" },
// Define the properties to use for sorting the results, sorting on DocId a best practice
// Define the properties to use for sorting the results, sorting on DocId a best practice
// to increase search query performance
SortProperties = new System.Collections.Generic.List<SortOption>()
{
new SortOption("DocId"),
new SortOption("ModifiedBy", SortDirection.Ascending)
SortProperties = new System.Collections.Generic.List<SortOption>()
{
new SortOption("DocId"),
new SortOption("ModifiedBy", SortDirection.Ascending)
},
};

Expand Down Expand Up @@ -83,16 +83,35 @@ foreach (var result in searchResult.Rows)
}

// Process the refiner results
foreach(var refiner in searchResult.Refinements)
foreach (var refiner in searchResult.Refinements)
{
foreach(var refinementResult in refiner.Value)
foreach (var refinementResult in refiner.Value)
{
// refinementResult.Value is a possible refinement value
// refinementResult.Count will provide the number of counts for the refinement value
}
}
```

## Refining search results using refinement filters

The following example shows how to create a search request by passing the first refinement option obtained from the previous example search result. Set the `RefinementFilters` property to a list of KQL queries, which can be built up using the `Token` from previous refinement results.

```csharp
// Retrieve the refinement
var refinementResults = searchResult.Refinements["ContentTypeId"];
var refinementToken = refinementResults[0].Token;

SearchOptions searchOptions = new SearchOptions("contentclass:STS_ListItem_DocumentLibrary")
{
RefinementFilters = new System.Collections.Generic.List<string>() { $"ContentTypeId:{refinementToken}" }
};

// Issue the search query
var searchResult = await context.Web.SearchAsync(searchOptions);

```

## Paging the search results

By default search results are returned in pages of 500, something you can change via the `RowsPerPage` attribute of the `SearchOptions` class. When a search query returns you'll be informed of the total amount of search results there are via the `TotalRows` and `TotalRowsIncludingDuplicates` properties of the `ISearchResult` response. Using these properties in concert with the `StartRow` property of the `SearchOptions` class you retrieve all the search result pages. Below sample shows how this can be done.
Expand All @@ -110,18 +129,18 @@ while (paging)
StartRow = startRow,
TrimDuplicates = false,
SelectProperties = new System.Collections.Generic.List<string>() { "Path", "Url", "Title", "ListId" },
SortProperties = new System.Collections.Generic.List<SortOption>()
{
new SortOption("DocId"),
new SortOption("ModifiedBy", SortDirection.Ascending)
SortProperties = new System.Collections.Generic.List<SortOption>()
{
new SortOption("DocId"),
new SortOption("ModifiedBy", SortDirection.Ascending)
},
};

// Issue the search query
var searchResult = await context.Web.SearchAsync(searchOptions);

// Add the returned page of results to our search results list
searchResults.AddRange(searchResult.Rows);
searchResults.AddRange(searchResult.Rows);

// If we're not done yet update the start row and issue a query to retrieve the next page
if (searchResults.Count < searchResult.TotalRows)
Expand Down Expand Up @@ -151,15 +170,15 @@ PnP Core SDK also allows you to batch multiple search queries and send them in o

```csharp
var batch = context.NewBatch();
Dictionary<Guid, IBatchSingleResult<ISearchResult>> batchResults =
Dictionary<Guid, IBatchSingleResult<ISearchResult>> batchResults =
new Dictionary<Guid, IBatchSingleResult<ISearchResult>>();

List<Guid> uniqueListIds = new List<Guid>();
// Imagine the uniqueListIds contains a series of list id's that you want to issue a search query for

foreach (var listId in uniqueListIds)
{
// Issue a search query with a refinement on `contenttypeid`, we don't need the
// Issue a search query with a refinement on `contenttypeid`, we don't need the
// result rows, so `RowLimit` is set to 0
var request = await context.Web.SearchBatchAsync(batch, new SearchOptions($"listid:{listId}")
{
Expand All @@ -174,14 +193,14 @@ foreach (var listId in uniqueListIds)
// Execute the batch
await context.ExecuteAsync(batch);

// Process the search results
// Process the search results
foreach (var batchResult in batchResults)
{
// Check the IsAvailable attribute to ensure the search request was executed
if (batchResult.Value.IsAvailable)
{
// The Result property is of type ISearchResult and can be used to process the search results
if (batchResult.Value.Result.Refinements.Count > 0 &&
if (batchResult.Value.Result.Refinements.Count > 0 &&
batchResult.Value.Result.Refinements.ContainsKey("contenttypeid"))
{
foreach (var refinementResult in batchResult.Value.Result.Refinements["contenttypeid"])
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

26 changes: 20 additions & 6 deletions src/sdk/PnP.Core.Test/SharePoint/WebTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1612,7 +1612,7 @@ public void SearchSortingPagingTest()
[TestMethod]
public void SearchRefinerTest()
{
//TestCommon.Instance.Mocking = false;
///TestCommon.Instance.Mocking = false;

using (var context = TestCommon.Instance.GetContext(TestCommon.TestSite))
{
Expand All @@ -1621,17 +1621,17 @@ public void SearchRefinerTest()
{
RowsPerPage = 10,
TrimDuplicates = false,
SelectProperties = new System.Collections.Generic.List<string>() { "Path", "Url", "Title", "ListId" },
SelectProperties = new System.Collections.Generic.List<string>() { "Path", "Url", "Title", "ListId", "ContentTypeId" },
SortProperties = new System.Collections.Generic.List<SortOption>() { new SortOption("DocId") },
RefineProperties = new System.Collections.Generic.List<string>() { "ContentTypeId" }
RefineProperties = new System.Collections.Generic.List<string>() { "ContentTypeId" },
};

var searchResult = context.Web.Search(searchOptions);

Assert.IsTrue(searchResult != null);
Assert.IsTrue(searchResult.ElapsedTime > 0);
Assert.IsTrue(searchResult.TotalRows > 0);
Assert.IsTrue(searchResult.TotalRowsIncludingDuplicates > 0);
Assert.IsTrue(searchResult.ElapsedTime > 0);
Assert.IsTrue(searchResult.Rows.Count == 10);
Assert.IsTrue(searchResult.Refinements.Count > 0);
foreach (var refiner in searchResult.Refinements)
Expand All @@ -1646,6 +1646,20 @@ public void SearchRefinerTest()
}
}

var refinementOption = searchResult.Refinements.First();
Assert.IsTrue(refinementOption.Key == "ContentTypeId");

var refinementData = refinementOption.Value.First();

var refinedOptions = new SearchOptions(searchOptions.Query);
refinedOptions.RefinementFilters.Add($"{refinementOption.Key}:{refinementData.Token}");

searchResult = context.Web.Search(refinedOptions);

Assert.IsTrue(searchResult.TotalRows == refinementData.Count);
var firstRow = searchResult.Rows.First();

Assert.IsTrue(Convert.ToString(firstRow["ContentTypeId"]) == refinementData.Value);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mock data has been manually updated so that this is true

}
}

Expand Down Expand Up @@ -1819,7 +1833,7 @@ public async Task AddWebEventReceiverAsyncNoEventTypeExceptionTest()
{
var eventReceiverOptions = new EventReceiverOptions
{

};
await context.Web.EventReceivers.AddAsync(eventReceiverOptions);
}
Expand Down Expand Up @@ -1899,7 +1913,7 @@ public async Task GetWebSearchManagedPropertiesTest()
{
var mps = context.Web.GetSearchConfigurationManagedProperties();

Assert.IsNotNull(mps);
Assert.IsNotNull(mps);
}
}

Expand Down
78 changes: 52 additions & 26 deletions src/sdk/PnP.Core/Model/SharePoint/Core/Internal/Web.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,7 @@ public IBrandingManager GetBrandingManager()
public async Task<ISearchResult> SearchAsync(SearchOptions query)
{
var apiCall = BuildSearchApiCall(query);
var response = await RawRequestAsync(apiCall, HttpMethod.Get).ConfigureAwait(false);
var response = await RawRequestAsync(apiCall, HttpMethod.Post).ConfigureAwait(false);

SearchResult searchResult = new SearchResult();
ProcessSearchResults(searchResult, response.Json);
Expand Down Expand Up @@ -1339,7 +1339,7 @@ public async Task<IBatchSingleResult<ISearchResult>> SearchBatchAsync(Batch batc
ProcessSearchResults(apiCall.RawSingleResult as SearchResult, json);
};

var batchRequest = await RawRequestBatchAsync(batch, apiCall, HttpMethod.Get).ConfigureAwait(false);
var batchRequest = await RawRequestBatchAsync(batch, apiCall, HttpMethod.Post).ConfigureAwait(false);
return new BatchSingleResult<ISearchResult>(batch, batchRequest.Id, apiCall.RawSingleResult as ISearchResult);
}

Expand All @@ -1355,7 +1355,10 @@ private void ProcessSearchResults(SearchResult searchResult, string json)
var parsedSearchResult = JsonSerializer.Deserialize<JsonElement>(json);
if (parsedSearchResult.ValueKind != JsonValueKind.Null)
{
searchResult.ElapsedTime = parsedSearchResult.GetProperty("ElapsedTime").GetInt32();
if (parsedSearchResult.TryGetProperty("ElapsedTime", out JsonElement elapsedTime))
{
searchResult.ElapsedTime = parsedSearchResult.GetProperty("ElapsedTime").GetInt32();
}

// Process the result rows
if (parsedSearchResult.TryGetProperty("PrimaryQueryResult", out JsonElement primaryQueryResult) &&
Expand Down Expand Up @@ -1534,65 +1537,88 @@ private Dictionary<string, object> ProcessSearchResultRow(JsonElement row)

private ApiCall BuildSearchApiCall(SearchOptions query)
{
StringBuilder uriBuilder = new StringBuilder();

uriBuilder.AppendFormat("_api/search/query?querytext='{0}'", HttpUtility.UrlEncode(query.Query?.Replace("'", "''")));
var endpointUri = $"_api/search/postquery";

if (query.TrimDuplicates == true)
dynamic request = new
{
uriBuilder.Append("&trimduplicates=true");
}
else
Querytext = query.Query,
EnableQueryRules = false,
SourceId = query.ResultSourceId,
}.AsExpando();

// The default is true
if (!query.TrimDuplicates)
{
uriBuilder.Append("&trimduplicates=false");
request.TrimDuplicates = false;
}

if (query.StartRow.HasValue && query.StartRow.Value > 0)
{
uriBuilder.AppendFormat("&startrow={0}", query.StartRow.Value);
request.StartRow = query.StartRow.Value;
}

if (query.RowsPerPage.HasValue)
{
uriBuilder.AppendFormat("&rowsperpage={0}", query.RowsPerPage.Value);
request.RowsPerPage = query.RowsPerPage.Value;
}

if (query.RowLimit.HasValue && query.RowLimit.Value > 0)
{
uriBuilder.AppendFormat("&rowlimit={0}", query.RowLimit.Value);
request.RowLimit = query.RowLimit.Value;
}
else if (query.RowsPerPage.HasValue)
{
uriBuilder.AppendFormat("&rowlimit={0}", query.RowsPerPage.Value);
request.RowLimit = query.RowsPerPage.Value;
}

if (query.SelectProperties.Count > 0)
{
uriBuilder.AppendFormat("&selectproperties='{0}'", HttpUtility.UrlEncode(string.Join(",", query.SelectProperties)));
request.SelectProperties = new
{
results = query.SelectProperties.ToArray(),
};
}

if (query.SortProperties.Count > 0)
{
uriBuilder.AppendFormat("&sortlist='{0}'", HttpUtility.UrlEncode(string.Join(",", query.SortProperties)));
request.SortList = new
{
results = query.SortProperties.Select(o => new
{
Property = o.Property,
Direction = (int)o.Sort,
}).ToArray()
};
}

if (query.RefineProperties.Count > 0)
{
uriBuilder.AppendFormat("&refiners='{0}'", HttpUtility.UrlEncode(string.Join(",", query.RefineProperties)));
request.Refiners = string.Join(",", query.RefineProperties);
}

if (!string.IsNullOrEmpty(query.ClientType))
if (query.RefinementFilters.Count > 0)
{
uriBuilder.AppendFormat("&clienttype='{0}'", query.ClientType);
request.RefinementFilters = new
{
results = query.RefinementFilters.ToArray()
};
}

uriBuilder.Append("&enablequeryrules=false");
if (!string.IsNullOrEmpty(query.ClientType))
{
request.ClientType = query.ClientType;
}

uriBuilder.AppendFormat("&sourceid='{0}'", HttpUtility.UrlEncode(query.ResultSourceId));
var body = new
{
request
};

return new ApiCall(uriBuilder.ToString(), ApiType.SPORest);
var jsonBody = JsonSerializer.Serialize(body);
return new ApiCall(endpointUri, ApiType.SPORest, jsonBody);
}
#endregion

#endregion Search

#region Web Templates

Expand Down Expand Up @@ -1764,7 +1790,7 @@ public async Task<IUnfurledResource> UnfurlLinkAsync(string link, UnfurlOptions

return await UnfurlHandler.UnfurlAsync(PnPContext, link, unfurlOptions).ConfigureAwait(false);
}

public IUnfurledResource UnfurlLink(string link, UnfurlOptions unfurlOptions = null)
{
return UnfurlLinkAsync(link, unfurlOptions).GetAwaiter().GetResult();
Expand Down Expand Up @@ -1894,7 +1920,7 @@ public void SetSearchConfigurationXml(string configuration)
#endregion

#region Get WSS Id for term

public async Task<int> GetWssIdForTermAsync(string termId)
{
if (TaxonomyHiddenList == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public SearchOptions(string query)
/// </summary>
public List<string> RefineProperties { get; set; } = new List<string>();

/// <summary>
/// The set of refinement filters used when issuing a refinement query
/// </summary>
public List<string> RefinementFilters { get; set; } = new List<string>();

/// <summary>
/// Client type used for the search query (defaults to ContentSearchRegular)
/// </summary>
Expand All @@ -72,4 +77,4 @@ public SearchOptions(string query)
/// </summary>
public string ResultSourceId { get; internal set; } = "8413cd39-2156-4e00-b54d-11efd9abdb89";
}
}
}