Search Results for

    Show / Hide Table of Contents

    Getting started with the PnP Core SDK

    The PnP Core SDK is designed to be used in modern .Net development, hence it relies on dependency injection (generic host) for its core services. This implies that before you can actually use the PnP Core SDK you need to configure the needed services. Once that's done you can obtain a PnPContext from the PnPContextFactory and start using the library.

    Where is the code?

    The PnP Core SDK is maintained in the PnP GitHub repository: https://github.com/pnp/pnpcore. You'll find:

    • The code of the PnP Core SDK in the src\sdk folder
    • Examples of how to use the PnP Core SDK in the samples folder
    • The source of the documentation you are reading right now in the docs folder

    I don't have access to a Microsoft 365 tenant

    If you don't have a Microsoft 365 tenant you can, for developer purposes, always request a free developer tenant and use that for developing and testing your applications. When your organization already uses Microsoft 365 it's still a good practice to develop and test your applications on a non production tenant, such as the free developer tenant.

    Learning from live code via Polyglot notebooks

    If you want to read about PnP Core SDK, see the needed code and even execute the code against your tenant then checkout our Polyglot notebooks! Using Visual Studio Code you interactively use PnP Core SDK with your tenant, the available notebooks can be found here.

    Referencing the PnP Core SDK in your project

    The recommended approach is to use the preview PnP.Core nuget package together with the PnP.Core.Auth nuget package. The former is the actual PnP Core SDK library, while the latter is an helper library that provides a useful set of Authentication Providers to authenticate against Azure Active Directory. Each night these preview packages are refreshed, so you can always upgrade to the latest dev bits by upgrading your nuget package to the latest version.

    Note
    • If you want to use the PnP Core SDK authentication providers then simply add the PnP.Core.Auth nuget package, the correct PnP.Core nuget package will be automatically added as it's a dependency of the PnP.Core.Auth nuget package.

    Configuring the needed services

    In order to configure the needed services in a .Net Core console app, you can rely on the AddPnPCore extension method (defined in the PnP.Core nuget package) and on the AddPnPCoreAuthentication method (defined in the PnP.Core.Auth nuget package), like in the following code excerpt:

    var host = Host.CreateDefaultBuilder()
    // Configure logging
    .ConfigureServices((hostingContext, services) =>
    {
        // Add the PnP Core SDK library services
        services.AddPnPCore();
        // Add the PnP Core SDK library services configuration from the appsettings.json file
        services.Configure<PnPCoreOptions>(hostingContext.Configuration.GetSection("PnPCore"));
        // Add the PnP Core SDK Authentication Providers
        services.AddPnPCoreAuthentication();
        // Add the PnP Core SDK Authentication Providers configuration from the appsettings.json file
        services.Configure<PnPCoreAuthenticationOptions>(hostingContext.Configuration.GetSection("PnPCore"));
    })
    // Let the builder know we're running in a console
    .UseConsoleLifetime()
    // Add services to the container
    .Build();
    

    And you will also need to provide the configuration in the appsettings.json file, using a configuration section like the following one:

    {
      "PnPCore": {
        "DisableTelemetry": "false",
        "HttpRequests": {
          "UserAgent": "ISV|Contoso|ProductX",
          "Timeout": "100",
          "SharePointRest": {
            "UseRetryAfterHeader": "false",
            "MaxRetries": "10",
            "DelayInSeconds": "3",
            "UseIncrementalDelay": "true"
          },
          "MicrosoftGraph": {
            "UseRetryAfterHeader": "true",
            "MaxRetries": "10",
            "DelayInSeconds": "3",
            "UseIncrementalDelay": "true"
          }
        },
        "PnPContext": {
          "GraphFirst": "true",
          "GraphCanUseBeta": "true",
          "GraphAlwaysUseBeta": "false"
        },
        "Credentials": {
          "DefaultConfiguration": "interactive",
          "Configurations": {
            "interactive": {
              "ClientId": "{your_client_id}",
              "TenantId": "{your_tenant_id}",
              "Interactive": {
                "RedirectUri": "http://localhost"
              }
            }
          }
        },
        "Sites": {
          "SiteToWorkWith": {
            "SiteUrl": "https://contoso.sharepoint.com/sites/pnp",
            "AuthenticationProviderName": "interactive"
          },
        }
      }
    }
    
    Note

    Ensure you've set "Copy to output directory" to "Copy always" for the appsettings.json file as otherwise the config file is not used.

    You should provide the ClientId and TenantId for an application registered in Azure Active Directory and configured with proper permissions, accordingly to your needs. For example, you could register an app in Azure Active Directory with delegated permission for:

    • Microsoft Graph: Group.ReadWrite.All
    • Microsoft Graph: User.ReadWrite.All
    • SharePoint Online: AllSites.FullControl
    • SharePoint Online: TermStore.ReadWrite.All
    • SharePoint Online: User.ReadWrite.All

    As the Redirect URI, in Web platform enter http://localhost.

    If you don't want to register a custom app in your target Azure Active Directory, you can skip the ClientId and TenantId properties and the PnP Core SDK will rely on a multi-tenant application that will be registered on your tenant, upon admin consent.

    In the above example, the authentication will rely on the InteractiveAuthenticationProvider (defined in the PnP.Core.Auth nuget package) so that you will simply need to authenticate with a set of valid credentials for your target tenant.

    If you like to configure the .Net Core console app in code, without relying on the appsettings.json file, you can also use the following syntax:

    var host = Host.CreateDefaultBuilder()
    // Configure logging
    .ConfigureServices((hostingContext, services) =>
    {
      // Add the PnP Core SDK library
      services.AddPnPCore(options => {
          options.PnPContext.GraphFirst = true;
          options.HttpRequests.UserAgent = "ISV|Contoso|ProductX";
    
          options.Sites.Add("SiteToWorkWith", new PnPCoreSiteOptions
          {
              SiteUrl = "https://contoso.sharepoint.com/sites/pnp"
          });
      });
      services.AddPnPCoreAuthentication(
          options => {
              // Configure an Authentication Provider relying on the interactive authentication
              options.Credentials.Configurations.Add("interactive",
                  new PnPCoreAuthenticationCredentialConfigurationOptions
                  {
                      ClientId = "{your_client_id}",
                      TenantId = "{your_tenant_id}",
                      Interactive = new PnPCoreAuthenticationInteractiveOptions
                      {
                          RedirectUri = new Uri("http://localhost")
                      }
                  });
    
              // Configure the default authentication provider
              options.Credentials.DefaultConfiguration = "interactive";
    
              // Map the site defined in AddPnPCore with the 
              // Authentication Provider configured in this action
              options.Sites.Add("SiteToWorkWith",
                  new PnPCoreAuthenticationSiteOptions
                  {
                      AuthenticationProviderName = "interactive"
                  });
        }
      );
    })
    // Let the builder know we're running in a console
    .UseConsoleLifetime()
    // Add services to the container
    .Build();
    

    In advanced scenarios, you can consider using code-based configuration of registered services, like in the following code excerpt. Typically you would also include logging as well.

    var host = Host.CreateDefaultBuilder()
    // Set environment to use
    .UseEnvironment("demo") // you can eventually read it from environment variables
    // Configure logging
    .ConfigureServices((hostingContext, services) =>
    {
      // Read the custom configuration from the appsettings.<environment>.json file
      var customSettings = new CustomSettings();
      hostingContext.Configuration.Bind("CustomSettings", customSettings);
    
      // Create an instance of the Interactive Authentication Provider
      var authenticationProvider = new InteractiveAuthenticationProvider(
                      customSettings.ClientId,
                      customSettings.TenantId,
                      customSettings.RedirectUri);
    
      // Add the PnP Core SDK services
      services
      .AddPnPCore(options => {
    
          // You can explicitly configure all the settings, or you can
          // simply use the default values
          options.PnPContext.GraphFirst = true;
          options.PnPContext.GraphCanUseBeta = true;
          options.PnPContext.GraphAlwaysUseBeta = false;
    
          options.HttpRequests.UserAgent = "NONISV|SharePointPnP|PnPCoreSDK";
          options.HttpRequests.MicrosoftGraph = new PnPCoreHttpRequestsGraphOptions
          {
             UseRetryAfterHeader = true,
             MaxRetries = 10,
             DelayInSeconds = 3,
             UseIncrementalDelay = true,
          };
          options.HttpRequests.SharePointRest = new PnPCoreHttpRequestsSharePointRestOptions
          {
             UseRetryAfterHeader = true,
             MaxRetries = 10,
             DelayInSeconds = 3,
             UseIncrementalDelay = true,
          };
    
          options.DefaultAuthenticationProvider = authenticationProvider;
    
          options.Sites.Add("DemoSite",
              new PnP.Core.Services.Builder.Configuration.PnPCoreSiteOptions
              {
                  SiteUrl = customSettings.DemoSiteUrl,
                  AuthenticationProvider = authenticationProvider
              });
      });
    })
    // Let the builder know we're running in a console
    .UseConsoleLifetime()
    // Add services to the container
    .Build();
    

    In above sample the following configuration file is used: appsettings.demo.json

    {
      "CustomSettings": {
        "ClientId": "{client_id}",
        "TenantId": "{tenant_id}",
        "DemoSiteUrl": "https://contoso.sharepoint.com/sites/pnp",
        "RedirectUri": "http://localhost"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information"
        }
      }
    }
    
    Note

    To learn more about how to setup authentication check the Configuring authentication article.

    Obtaining a PnPContext

    The PnPContext is the entry point for using the PnP Core SDK, you can create a PnPContext from either a SharePoint site URL or the id of a Microsoft 365 group.

    Note

    You'll get a PnPContext for the root web of the site collection. Checkout the Getting sub webs content to learn how to get a PnPContext for the sub webs of the root web.

    // Start console host
    await host.StartAsync();
    
    using (var scope = host.Services.CreateScope())
    {
        // Obtain a PnP Context factory
        var pnpContextFactory = scope.ServiceProvider.GetRequiredService<IPnPContextFactory>();
        // Use the PnP Context factory to get a PnPContext for the given configuration
        using (var context = await pnpContextFactory.CreateAsync("SiteToWorkWith"))
        {
            // See next chapter on how to use the PnPContext
        }
    
        using (var context = await pnpContextFactory.CreateAsync("Microsoft 365 Group guid"))
        {
            // See next chapter on how to use the PnPContext
        }
    }
    
    // Cleanup console host
    host.Dispose();
    

    If you prefer to create a PnPContext by specifying the URL you need in code or you want to be able to easily switch between authentication providers then below sample shows how to so. This snippet works without an configuration file, all you need to do is add the needed authentication provider configuration(s) and then later on in your code acquire the needed authentication providers via the IAuthenticationProviderFactory. Once you have your authentication provider you can use it in the Create methods on the context factory:

    var host = Host.CreateDefaultBuilder()
        .ConfigureServices((context, services) => 
        {
            services.AddPnPCore();
            services.AddPnPCoreAuthentication(options =>
            {
                options.Credentials.Configurations.Add("interactive", 
                  new PnPCoreAuthenticationCredentialConfigurationOptions
                  {
                      ClientId = "c545f9ce-1c11-440b-812b-0b35217d9e83",
                      Interactive = new PnPCoreAuthenticationInteractiveOptions
                      {
                          RedirectUri = new Uri("http://localhost")
                      }
                  });
    
                options.Credentials.Configurations.Add("usernamepassword", 
                  new PnPCoreAuthenticationCredentialConfigurationOptions
                  {
                      ClientId = "c545f9ce-1c11-440b-812b-0b35217d9e83",                
                      UsernamePassword = new PnPCoreAuthenticationUsernamePasswordOptions
                      {
                          Username = "joe@contoso.onmicrosoft.com",
                          Password = "xxx"
                      }
                  });
    
            });
        })
        .UseConsoleLifetime()
        .Build();
    
    await host.StartAsync();
    
    using (var scope = host.Services.CreateScope())
    {
        var pnpContextFactory = scope.ServiceProvider.GetRequiredService<IPnPContextFactory>();
        var pnpAuthenticationProviderFactory = scope.ServiceProvider.GetRequiredService<IAuthenticationProviderFactory>();
    
        var interactiveAuthProvider = pnpAuthenticationProviderFactory.Create("interactive");
        var passwordManagerAuthProvider = pnpAuthenticationProviderFactory.Create("usernamepassword");
    
        using (var context = await pnpContextFactory.CreateAsync(new Uri("https://contoso.sharepoint.com/sites/prov-1"), 
                                                                 interactiveAuthProvider))
        {
            await context.Web.LoadAsync(p => p.Title);
            Console.WriteLine($"The title of the web is {context.Web.Title}");
    
            using (var context2 = await pnpContextFactory.CreateAsync(new Uri("https://contoso.sharepoint.com/sites/prov-1"), 
                                                                      passwordManagerAuthProvider))
            {
                await context2.Web.LoadAsync(p => p.Title);
                Console.WriteLine($"The title of the web is {context2.Web.Title}");
            }
        }
    }
    

    Next to creating a new PnPContext you can also clone an existing one, cloning is very convenient if you for example created a context for the root web of your site collection but now want to work with a sub site. Below snippet shows how to use cloning:

    using (var context = await pnpContextFactory.CreateAsync("SiteToWorkWith"))
    {
        var web = await context.Web.GetAsync();
        Console.WriteLine($"Title: {web.Title}");
    
        using (var subSiteContext = await context.CloneAsync(new Uri("https://contoso.sharepoint.com/sites/siteA/subsite")))
        {
            var subWeb = await subSiteContext.Web.GetAsync();
            Console.WriteLine($"Sub site title: {subWeb.Title}");
        }
    }
    

    Using the PnPContext for operations on Microsoft 365

    All operations on Microsoft 365 start from the PnPContext instance you've obtained from the PnPContextFactory. Below sample shows a simple get operation that requests data from Microsoft 365 and outputs it to the console:

    using (var context = await pnpContextFactory.CreateAsync("SiteToWorkWith"))
    {
        var web = await context.Web.GetAsync();
        Console.WriteLine($"Title: {web.Title}");
    }
    

    Here follows another example that shows how to define which properties need to be loaded while executing the request:

    var team = await context.Team.GetAsync(p => p.Description, p => p.FunSettings, p => p.DiscoverySettings, p => p.Members);
    

    When you see an asynchronous call being used, it means that the call is executed immediately. However, you can easily group multiple requests in a batch and send them in one call to the server via the built in batching support:

    var myList = await context.Web.Lists.GetByTitleAsync("TestList");
    if (myList != null)
    {
        // Create three list items and add them via single server request
        Dictionary<string, object> values = new Dictionary<string, object>
        {
            { "Title", "PnP Rocks!" }
        };
    
        await myList.Items.AddBatchAsync(values);
        await myList.Items.AddBatchAsync(values);
        await myList.Items.AddBatchAsync(values);
    
        // Send batch to the server
        await context.ExecuteAsync();
    }
    

    To update Microsoft 365 you simply update the needed properties in your model and then call UpdateAsync or UpdateBatchAsync (used for batching):

    var myList = await context.Web.Lists.GetByTitleAsync("Documents");
    
    if (myList != null)
    {
        myList.Description = $"Updated on UTC {DateTime.UtcNow}";
        await myList.UpdateAsync();
    }
    

    Deleting follows a similar pattern, but now you use DeleteAsync or Delete:

    var myList = await context.Web.Lists.GetByTitleAsync("ListToDelete");
    
    if (myList != null)
    {
        await myList.DeleteAsync();
    }
    

    If you like, you can also leverage a fluent syntax enriched with LINQ (Language Integrated Query). For example, in the following code excerpt you can see how to write a query for the items of a list.

    var list = await context.Web.Lists.GetByTitleAsync("Documents");
    var document = await list.Items.Where(i => i.Title == "Sample Document")
                                   .QueryProperties(i => i.Id, i => i.Title)
                                   .FirstOrDefaultAsync();
    
    if (document != null)
    {
        Console.WriteLine($"Document Title: {document.Title}");
    }
    

    Another approach to mainly limit the data that's being pulled from Microsoft 365 is using the QueryProperties() method on the properties specified in the lambda expression(s), below example shows using QueryProperties() in a recursive manner: next to the Title property of the Web this request also loads the Lists for the Web and for each List it loads the Id, Title, DocumentTemplate and ContentTypes property. Given List ContentTypes is a collection, the Name and FieldLinks properties of each content type are loaded and, in turn, for ContentType FieldLinks, the Name property is loaded.

    await context.Web.LoadAsync(p => p.Title,
                                p => p.ContentTypes.QueryProperties(p => p.Name),
                                p => p.Lists.QueryProperties(p => p.Id,
                                                             p => p.Title,
                                                             p => p.DocumentTemplate,
                                    p => p.ContentTypes.QueryProperties(p => p.Name,
                                         p => p.FieldLinks.QueryProperties(p => p.Name)))
                               );
    

    The QueryProperties() method can also be combined with the various GetBy methods: below call will load the list with as title "Documents" and for that list all ContentTypes are loaded with all their respective FieldLinks.

    var web = context.Web;
    var list = await web.Lists.GetByTitleAsync("Documents",
                                p => p.Title,
                                p => p.ListExperience,
                                p => p.ContentTypes.QueryProperties(p => p.Id,
                                       p => p.Name,
                                       p=>p.FieldLinks.QueryProperties(p=>p.Id, p=>p.Name)));
    
    Back to top PnP Core SDK
    Generated by DocFX with Material UI
    spacer