Search Results for

    Show / Hide Table of Contents

    Making custom API calls

    PnP Core SDK uses a strongly typed model allowing you to interact with SharePoint and Microsoft Teams using either SharePoint REST, Microsoft Graph or SharePoint CSOM requests. If the provided functionality is not meeting your needs you do have the option to execute SPO REST or Microsoft Graph requests yourselves, as explained in this article.

    In the remainder of this article you'll see a lot of context use: in this case this is a PnPContext which was obtained via the PnPContextFactory as explained in the overview article and show below:

    using (var context = await pnpContextFactory.CreateAsync("SiteToWorkWith"))
    {
        // See next chapter on how to use the PnPContext
    }
    

    General concepts

    To perform a custom API request you use the ExecuteRequestAsync or ExecuteRequest methods providing as input a ApiRequest holding the information for making the API call. You can use these methods on any domain model object (e.g. Web, List, Team) and this will not impact the call. However calling the ExecuteRequestAsync or ExecuteRequest methods on the correct domain model object enables you to use the tokens in your API call. Following tokens can be used:

    Token Description
    {Id} Value of the SharePoint Id property of the current model instance (e.g. List).
    {Parent.Id} Value of the SharePoint Id property of the current model's parent instance (e.g. Web for a List --> ListCollection is skipped in this approach).
    {GraphId} Value of the Microsoft Graph Id property of the current model instance (e.g. TeamChannel).
    {Parent.GraphId} Value of the Microsoft Graph Id property of the current model's parent instance (e.g. Team for a TeamChannel --> TeamChannelCollection is skipped in this approach).
    {Site.GroupId} Id value of the Microsoft 365 Group connected to the Site loaded in the current PnPContext (Id is the same for SharePoint REST as Microsoft Graph usage).
    {Site.Id} SharePoint Id value of the Site loaded in the current PnPContext.
    {Web.Id} SharePoint Id value of the Web loaded in the current PnPContext.
    {Web.GraphId} Microsoft Graph Id value of the Web loaded in the current PnPContext.
    {List.Id} SharePoint Id value of the List loaded in the current PnPContext (works only when the target object is of type List or ListItem).
    {hostname} Host name of the current site (so for https://contoso.sharepoint.com/sites/team1 this is contoso.sharepoint.com)
    {serverrelativepath} Server relative path of the current site (so for https://contoso.sharepoint.com/sites/team1 this is /sites/team1)

    The returned response is of type ApiRequestResponse and contains the JSON result, the HTTP result code, the original ApiRequest instance and optional extra HTTP headers returned by the call.

    Making a custom SPO REST request

    Below sample shows how you can perform a custom REST request on the current web and on a web in another site collection:

    // Custom SPO REST request for the current connected web
    // Will result in a GET https://contoso.sharepoint.com/sites/currentsite/_api/web
    var apiRequest = new ApiRequest(ApiRequestType.SPORest, "_api/web");
    var response = context.Web.ExecuteRequest(apiRequest);
    
    // Parse the json response returned via response.Response
    
    // Custom SPO REST Request for another web
    // Will result in a GET https://contoso.sharepoint.com/sites/anothersite/_api/web
    var apiRequest = new ApiRequest(ApiRequestType.SPORest, "https://contoso.sharepoint.com/sites/anothersite/_api/web");
    var response = await context.Web.ExecuteRequestAsync(apiRequest);
    
    Note

    When you're using text content (e.g. json for the request body) then you need to ensure it's correctly encoded. Using the default System.Text.Json library will handle unicode characters automatically. When using Newtonsoft then use the JsonSerializerSettings.StringEscapeHandling property of the JsonSerializerSettings class.

    Making a custom Microsoft Graph request

    Below sample shows how to make a custom Microsoft Graph request:

    // Custom Microsoft Graph request
    // Will result in a GET "https://graph.microsoft.com/v1.0/me" 
    var apiRequest = new ApiRequest(ApiRequestType.Graph, "me");
    var response = context.Team.ExecuteRequest(apiRequest);
    
    // Parse the json response returned via response.Response
    
    Note

    When you're using text content (e.g. json for the request body) then you need to ensure it's correctly encoded. Using the default System.Text.Json library will handle unicode characters automatically. When using Newtonsoft then use the JsonSerializerSettings.StringEscapeHandling property of the JsonSerializerSettings class.

    Batching custom API requests

    To optimize performance it's recommended to limit the amount of server roundtrips and therefore batching custom API requests can be used. Batching requests is quite similar to interactive requests, you simply use one of the available ExecuteRequestBatch methods. Since batching requests implies that you'll only get a result once the batch is executed you do not get back a JSON string, instead you get an IBatchSingleResult<BatchResultValue<string>> which allows you to check if the batch was executed and if so get the batch result via the Value property.

    // Create a new batch
    var batch = context.NewBatch();
    
    // Add requests to the batch
    var meResponse = context.Web.ExecuteRequestBatch(batch, new ApiRequest(ApiRequestType.Graph, "me"));
    // Note: meResponse.IsAvailable is false
    var drivesResponse = context.Web.ExecuteRequestBatch(batch, new ApiRequest(ApiRequestType.Graph, "drives"));
    // Note: drivesResponse.IsAvailable is false
    
    // Execute the batch
    await context.ExecuteAsync(batch);
    
    // Use the batch results
    if (meResponse.IsAvailable)
    {
        string meJsonResonse = meResponse.Result.Value;
    }
    
    if (drivesResponse.IsAvailable)
    {
        string drivesJsonResponse = drivesResponse.Result.Value;
    }
    

    Batching can be used with an dedicated batch like shown in above example, but it's also possible to use the implicit batch which is always available, in that case you'd just leave out the batch parameter in the ExecuteRequestBatch methods:

    // Add requests to the batch
    var meResponse = await context.Web.ExecuteRequestBatchAsync(new ApiRequest(ApiRequestType.Graph, "me"));
    var drivesResponse = await context.Web.ExecuteRequestBatchAsync(new ApiRequest(ApiRequestType.Graph, "drives"));
    
    // Execute the batch
    await context.ExecuteAsync();
    
    // Use the batch results
    if (meResponse.IsAvailable)
    {
        string meJsonResonse = meResponse.Result.Value;
    }
    
    if (drivesResponse.IsAvailable)
    {
        string drivesJsonResponse = drivesResponse.Result.Value;
    }
    

    Handling failing requests in a batch

    What if one of the requests in a batch fails? The default behavior is that a SharePointRestServiceException or MicrosoftGraphServiceException exception is thrown when the first failed request is processed, but you can also choose to get back a list of failed batch requests and then handle the follow-up in your code. To do this you need to tell the Execute methods to not throw an exception and collect the output of the Execute method. Following snippet shows how to do this:

    // Create a new batch
    var batch = context.NewBatch();
    
    // Add requests to the batch
    var meResponse = await context.Web.ExecuteRequestBatchAsync(batch, new ApiRequest(ApiRequestType.Graph, "me"));
    var drivesResponse = await context.Web.ExecuteRequestBatchAsync(batch, new ApiRequest(ApiRequestType.Graph, "thiswillgiveanerror"));
    
    // Execute the batch, notice we tell here to not throw an exception and we collect the possible errors in a collection
    var batchErrors = await context.ExecuteAsync(batch, false);
    
    // Use the batch results
    if (batchErrors.Any())
    {
        // there were errors
    }
    else
    {
        // all good
    }
    

    Can I mix custom API requests with out-the-box API requests in a single batch?

    Yes, this is perfectly possible, below example combines a custom API with an API request that will load a set of lists.

    // Create a new batch
    var batch = context.NewBatch();
    
    // Add requests to the batch
    var result1 = await context.Web.ExecuteRequestBatchAsync(batch, new ApiRequest(ApiRequestType.Graph, "_api/web"));
    var result2 = await context.Web.Lists
        .Where(p => p.TemplateType == ListTemplateType.GenericList)
        .QueryProperties(
            p => p.Title, p => p.TemplateType,
            p => p.ContentTypes.QueryProperties(
                p => p.Name, p => p.FieldLinks.QueryProperties(p => p.Name)))
        .AsBatchAsync(batch);
    
    // Execute the batch
    await context.ExecuteAsync(batch);
    

    I want to specify custom request headers or read the response headers

    To specify request headers you can use the WithHeaders extension method from the PnP.Core.Model namespace: using a Dictionary<string,string> you provide input headers and using a delegate your code get notified of the resulting response headers. Below are some sample:

    Dictionary<string, string> extraHeaders = new Dictionary<string, string>() { { "myheader", "myheadervalue" } };
    
    // Use WithHeaders on custom API request, process the returned response headers
    var meResponse = await context.Web.WithHeaders(extraHeaders, (responseHeaders) => { /* process the response headers */ }).ExecuteRequestBatchAsync(batch, new ApiRequest(ApiRequestType.Graph, "me"));
    
    // Use WithHeaders on OOB API request, ignore the response headers
    context.Web.WithHeaders(extraHeaders).Load(p => p.All);
    
    Back to top PnP Core SDK
    Generated by DocFX with Material UI
    spacer