.Net Core Console Application for SharePoint Online using PNPCore library Interactive Login

Sathish Nadarajan
 
Solution Architect
October 16, 2021
 
Rate this article
 
Views
11605

In Many cases, we may need a Console application to do some sort of maintenance or any patch work to be done on the SharePoint Environment.

To connect with the SharePoint, we usually use the SharePointPNPCoreOnline Nuget package. But now, it is deprecated. Instead, we can use either PNP.Framework or PNP.Core.

PNP.Framework can be used from a .Net Framework application.

PNP.Core can be used from a .Net Core application.

In this application, let us see the steps to create the application step by step.

1. Open VS 2019 and create a new project.

 

2. Select the Framework as .Net 5.0

 

3. Open the NuGet Manager.

 

4. The below one is deprecated.

 

5. Install the PnP.Core

 

 

 

6. Install PnP.Core.Auth

 

 

7. After installing, the Program.cs will looks as below.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PnP.Core.Services;

using System;
using System.Linq;
using System.Threading.Tasks;

namespace DotNet.Core.Console
{
class Program
{
public static async Task Main(string[] args)
{
var host = Host.CreateDefaultBuilder()

.ConfigureLogging((hostingContext, logging) =>
{
logging.AddEventSourceLogger();
logging.AddConsole();
})
.ConfigureServices((hostingContext, services) =>
{
// Read the custom configuration from the appsettings..json file
var customSettings = new CustomSettings();
hostingContext.Configuration.Bind("CustomSettings", customSettings);

// Add the PnP Core SDK services
services.AddPnPCore(options => {

options.PnPContext.GraphFirst = true;

options.Sites.Add("DemoSite",
new PnP.Core.Services.Builder.Configuration.PnPCoreSiteOptions
{
SiteUrl = customSettings.DemoSiteUrl

});
});

services.AddPnPCoreAuthentication(
options =>
{
options.Credentials.Configurations.Add("interactive",
new PnP.Core.Auth.Services.Builder.Configuration.PnPCoreAuthenticationCredentialConfigurationOptions
{
ClientId = customSettings.ClientId,
TenantId = customSettings.TenantId,
Interactive = new PnP.Core.Auth.Services.Builder.Configuration.PnPCoreAuthenticationInteractiveOptions
{
RedirectUri = customSettings.RedirectUri
}
});

options.Credentials.Configurations.Add("credentials",
new PnP.Core.Auth.Services.Builder.Configuration.PnPCoreAuthenticationCredentialConfigurationOptions
{
ClientId = customSettings.ClientId,
TenantId = customSettings.TenantId,
Interactive = new PnP.Core.Auth.Services.Builder.Configuration.PnPCoreAuthenticationInteractiveOptions
{
RedirectUri = customSettings.RedirectUri
},
//},
//CredentialManager = new PnP.Core.Auth.Services.Builder.Configuration.PnPCoreAuthenticationCredentialManagerOptions
//{
// CredentialManagerName = customSettings.CredentialManager
//}

});

// 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("DemoSite",
new PnP.Core.Auth.Services.Builder.Configuration.PnPCoreAuthenticationSiteOptions
{
AuthenticationProviderName = "interactive"
});
});
})
// Let the builder know we're running in a console
.UseConsoleLifetime()
// Add services to the container
.Build();

await host.StartAsync();

using (var scope = host.Services.CreateScope())
{
var pnpContextFactory = scope.ServiceProvider.GetRequiredService();

using (var context = await pnpContextFactory.CreateAsync("DemoSite"))
{
//Use the context to do the operations.

}
}

host.Dispose();
}
}
}

8. Appsettings.json will be as below.

{
"key": "generic",
"CustomSettings": {
"ClientId": "2ab1db09-3a59-48bc-*******-******",
"TenantId": "61943e96-b3a9-49dd-*****-*****",
"DemoSiteUrl": "https://sppalsmvp.sharepoint.com/sites/MySiteCollection/",
"DemoSubSiteUrl": "https://sppalsmvp.sharepoint.com/sites/MySiteCollection/",
"CredentialManager": "sppalsmvp",
"RedirectUri": "http://localhost",
"UserPrincipalName": "sathish@sppals.com",
"Password": "*****"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}

9. Csproj as below

<Project Sdk="Microsoft.NET.Sdk">

 

<PropertyGroup>

<OutputType>Exe</OutputType>

<TargetFramework>net5.0</TargetFramework>

 

</PropertyGroup>

 

<ItemGroup>

<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />

<PackageReference Include="PnP.Core" Version="1.4.0" />

<PackageReference Include="PnP.Core.Auth" Version="1.4.0" />

</ItemGroup>

 

<ItemGroup>

<None Update="appsettings.json">

<CopyToOutputDirectory>Always</CopyToOutputDirectory>

</None>

</ItemGroup>

 

</Project>

 

10. When we execute this, the interactive login will be popped up.

 

With this, the .Net Core Console application is ready to work with SharePoint tenant.  We can do our activities with this PNPCoreContext object.

 

There are many other ways of authentication and getting the client context.  In this article, I have demonstrated the Interactive Login and the Client Credentials method.  In the upcoming articles, let us see how to get the context using user name and password, Azure Certificates etc.,

 

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

NodeJS – Get the List of Site Collections in my Office 365 Tenant by PNP JS Search Query

Sathish Nadarajan
 
Solution Architect
October 12, 2021
 
Rate this article
 
Views
518

In the earlier articles, we saw how to get the context of SharePoint from the Node application. As part of that, let us see, how to get the list of site collections in my office 365 tenant.

The Index.ts will be as below.

import express, { Application, ErrorRequestHandler, Request, response, Response } from 'express';
import NodeFetchClient from 'node-pnp-js';
import * as pnp from 'sp-pnp-js';

const app: Application = express();
const PORT = process.env.PORT || 2000;

let url = "https://sppalsmvp.sharepoint.com/sites/MySiteCollection/";
let credentialOptions = {
    username: 'sathish@sppals.com',
    password: '****'
};

app.get("/", async (req: Request, res: Response): Promise<void> => {


    pnp.setup({
        sp: {
            fetchClientFactory: () => {
                return new NodeFetchClient(credentialOptions, url);
            }
        }
    });

    pnp.sp.search({
        Querytext: "contentclass:STS_Site", SelectProperties: ["Title","SPWebUrl", "SPSiteUrl", "WebTemplate"], RowLimit: 500, TrimDuplicates: false
    }).then((r) => {

        r.PrimarySearchResults.forEach((value) => {
            console.log(`${value.Title} - ${value.SPWebUrl} - ${value.WebTemplate}`);
       });

        res.send('success');
        
    }).catch(e=>{
       res.status(500).send(e); 
    });

});

app.use(function (err: any, req: Request, res: Response, next: ErrorCallback) {
    res.status(err.status || 500);
    res.send(err);
});

app.listen(PORT, (): void => {
    console.log(`Server Running here https://localhost:${PORT}`);
});

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

NodeJS – Get SharePoint PnP-Core Client Context using UserName and Password and access SharePoint Objects from Node Application

Sathish Nadarajan
 
Solution Architect
October 11, 2021
 
Rate this article
 
Views
997

In the earlier article, we saw how to setup a NodeJS application and start the development. As a continuation, let us see, how to create the SharePoint Context and access the SharePoint Sites.

The intention is to access the SharePoint objects and do some functionality from a Node JS application. i.e., a Separate standalone application which will do some action on our SharePoint Tenant.

To get the SharePoint Client Context, install the node-pnp-js and sp-pnp-js.

Let us take the earlier application itself for this continuation.

Install the following node modules.

npm install node-pnp-js –save
npm install sp-pnp-js –save
npm install pnp-auth –save
npm install @pnp/logging @pnp/common @pnp/odata @pnp/sp –save

Update the Index.ts file as below.

import express, { Application, ErrorRequestHandler, Request, response, Response } from 'express';
import NodeFetchClient from 'node-pnp-js';
import * as pnp from 'sp-pnp-js';


const app: Application = express();
const PORT = process.env.PORT || 2000;

let url = "https://sppalsmvp.sharepoint.com/sites/MySiteCollection/";
let credentialOptions = {
    username: 'sathish@sppals.com',
    password: '*******'
};

app.get("/", async (req: Request, res: Response): Promise<void> => {


    pnp.setup({
        sp: {
            fetchClientFactory: () => {
                return new NodeFetchClient(credentialOptions, url);
            }
        }
    });

    try {
        let list = await pnp.sp.web.lists.getByTitle('TempList').get();
        console.log(list);
        res.send(list);
    }
    catch (error) {
        res.send(error);
    }

});

app.use(function (err: any, req: Request, res: Response, next: ErrorCallback) {
    res.status(err.status || 500);
    res.send(err);
});

app.listen(PORT, (): void => {
    console.log(`Server Running here https://localhost:${PORT}`);
});

Then on the command prompt, execute npm run dev to run the server.

If we browse, then the output will be as below.

Download the Source HERE

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

CSOM – Get the Pages modified after a time stamp by C# CSOM – SharePoint Online

Sathish Nadarajan
 
Solution Architect
October 7, 2021
 
Rate this article
 
Views
1299

In one of the requirements, got to retrieve the files which are modified after a specific time stamp. As part of that, created a CAML Query and thought of sharing with the community with the usecase.

One thing, just wanted to highlight is, the below code uses pnpFramework to get the context. As the earlier PNPCoreOnline is deprecated. Please search for pnpframework on the Nuget Manager.

public static void GetPagesByTimeStamp()
        {
            System.IO.File.AppendAllText(logFilePath, "Started.." + Environment.NewLine + Environment.NewLine);

            string siteUrl = ConfigurationManager.AppSettings["siteUrl"];
            string userName = ConfigurationManager.AppSettings["userName"];
            string password = ConfigurationManager.AppSettings["password"];
            string timestamp = ConfigurationManager.AppSettings["timestamp"];

            try
            {
                using (var ctx = new PnPClientContext(siteUrl))
                {
                    ctx.Credentials = new SharePointOnlineCredentials(userName, password.ToSecureString());
                    //Get the web from the current context
                    Web web = ctx.Web;
                    ctx.Load(web);
                    ctx.Load(web.Lists);
                    ctx.ExecuteQueryRetry();

                    //Get the Pages Library
                    var pages = ctx.Web.Lists.GetByTitle("Site Pages");
                    ctx.Load(pages);

                    //
                    
                    string datetime = Convert.ToDateTime(timestamp).ToString("yyyy-MM-ddTHH:mm:ssZ");
                    //Get all Items inside the Pages Library
                    CamlQuery camlQuery = new CamlQuery();
                    camlQuery.ViewXml = @"<View Scope='Recursive'>
                                             <Query>
                                                <Where>
                                                    <Gt>
                                                        <FieldRef Name='Modified'/>
                                                        <Value IncludeTimeValue='TRUE' Type='DateTime'>" + datetime + "</Value>" + 
                                                    @"</Gt>
                                                </Where>
                                                <OrderBy>
                                                    <FieldRef Name='Modified' Ascending = 'true' />
                                                </OrderBy>
                                            </Query>
                                         </View>";
                    ListItemCollection listItems = pages.GetItems(camlQuery);
                    ctx.Load(listItems);
                    ctx.ExecuteQuery();

                    List<Page> lstPages = new List<Page>();

                    foreach (var listItem in listItems)
                    {
                        System.Console.WriteLine(listItem["Title"]);
                        Page p = new Page();
                        p.Id = Convert.ToInt32(listItem["ID"]);
                        p.Name = Convert.ToString(listItem["Title"]);

                        lstPages.Add(p);


                    }



                    TextWriter txtWriter = new StreamWriter(ConfigurationManager.AppSettings["LogFilePath"] + "Pages.csv");

                    using (CsvWriter writer = new CsvWriter(txtWriter, new CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture)))
                    {
                        writer.WriteRecords(lstPages);

                        writer.Flush();

                    }




                }
            }
            catch (Exception ex)
            {
                System.Console.WriteLine("Exception occurred : " + ex.Message);
                System.Console.ReadLine();
            }
        }

The above code is self explanatory and doesn’t require much explanation I guess.


Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

NodeJS – Step by Step Procedure to Setup and Create a NodeJS application and use TypeScript as a Programming Language

Sathish Nadarajan
 
Solution Architect
October 1, 2021
 
Rate this article
 
Views
1011

All of us are familiar about the popularity and the power of Node JS. By default the NodeJS application will have the start page as a JS file and predominantly it is meant for the JavaScript. But, when I wanted to create a Node WebSite, which is going to interact with SharePoint Online using PNPJS, I was in a situation that, TypeScript is more friendlier than JS for any PNP and SharePoint Online coding.

Hence, wanted to explore, how to create or use Typescript within the Node JS application and make the DEV environment a bit faster and cleaner.

In this article, let us see the steps to create a NodeJS application and use typescript as a Programming Language on it.

1. Install Node LTS from the below path.

https://nodejs.org/en/download/

 

 

 

2. Ensure that the Node is installed properly by typing “node -v”. This will give the version of the node installed.

3. Create a folder with the app name – C:\NodeApps\DemoNodeJSWithTypeScript
4. Open the command prompt.

5. Create a solution using “npm init”.

6. The command will create the package.json file.

 

Package.json

{
"name": "demonodejswithtypescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "",
"license": "ISC"
}

7. Install TypeScript using the command npm install -g typescript

8. Ensure the version of typescript by tsc –version

9. We need to create the config file for the TypeScript. Tscofig.json.
a. We can create this by running the command “tsc –init”.

 

10. Now, the tsconfig.json has been created. Have a look on the site, for more information about the tsconfig.
11. Update the tsconfig as below.

{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
"target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": [
"./node_modules"
]
}

12. Create the folders SRC and DIST as updated above on the tsconfig.json file. The folder structure will looks as below.

 

13. Create a file called index.ts inside the src folder.
14. Write some TS code here.

function sum (num1:number, num2:number){
return num1 + num2;
}
console.log(sum(8,4))

15. Now, install the typescript compiler npm install -D typescript

16. Now run tsc ;- this will create the corresponding index.js on the outdir dist folder.

17. Now, the solution and the index.js will be shown as below.

 

18. Now execute “node dist/index.js” to run the application.

 

19. Install ts-node by “npm install -D ts-node
a. Ts-node allows us to point to a Typescript file. It will run .ts, compile it and run it with Node.js for us.
20. Update the config file as below.

{
"name": "demonodejswithtypescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"start": "ts-node ./src/index.ts"
},
"author": "",
"license": "ISC",
"devDependencies": {
"ts-node": "^10.2.1",
"typescript": "^4.4.3"
}
}

21. Now we can execute by using “npm start” itself. This will automatically trigger the index.ts file.
22. Then install the below packages for various purposes.

Npm install express
Npm install -D @types/node
Npm install -D @types/express

 

23. Update the index.ts as below.

import express, { Application, ErrorRequestHandler, Request, response, Response } from 'express';

const app: Application = express();
const PORT = process.env.PORT || 2000;

app.get("/", async (req: Request, res: Response): Promise => {

res.send("Hello Typescript with Node.js!")

});

app.use(function (err: any, req: Request, res: Response, next: ErrorCallback) {
res.status(err.status || 500);
res.send(err);
});

app.listen(PORT, (): void => {
console.log(`Server Running here https://localhost:${PORT}`);
});

24. Now run “npm start

 

25. Browse the page http://localhost:2000

 

26. The next step is to continuously monitor the TS files changes, so that the npm server should automatically refresh. For that, I prefer to go with Nodemon.

27. Npm install -D nodemon

28. Update the package.json with the script parameter dev. The final package.json will looks like below.

{
"name": "demonodejswithtypescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"start": "ts-node ./src/index.ts",
"dev": "nodemon ./src/index.ts"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.13",
"@types/node": "^16.10.2",
"ts-node": "^10.2.1",
"typescript": "^4.4.3"
},
"dependencies": {
"express": "^4.17.1"
}
}

29. Now we can use the command “npm run dev

Though it’s a bit lengthy, this will be useful for someone who wants to develop a Node application with TypeScript. Especially the stand alone application for the SharePoint Online. Yes, this is a start and the objective is to use this application with PNPJS to interact with SharePoint Online sites in the upcoming articles. Stay Tuned!!!

Download the Source HERE

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

Refreshing JavaScript – Calling an Async method within Map method in Typescript – SPFx

Sathish Nadarajan
 
Solution Architect
August 27, 2021
 
Rate this article
 
Views
1117

Recently faced an issue. I need to iterate a list of items and need to call an Async method. But the map needs to wait until the last async method is been returned. Usually what happens is, since the method is async, after the first value, it goes beyond the map method and returns. For that, we must write the method with async and Promise All. The pseudo method is as below.

public getTasks = async (Id: number): Promise<any> => {
    try {
      let tasksListItems = await sp.web.lists.getByTitle('Tasks').items.expand('AssignedTo').select('*', 'AssignedTo/Title', 'AssignedTo/EMail', 'AssignedTo/Name').filter(`ProjectID eq ${Id} `).get();

      let taskIds = [];
      taskIds = await Promise.all(tasksListItems.map(async item => {
        return {
          key: item.Id,
          text: item.AssignedTo1.Title,
          taskTitle: item.Title,
          isMember: await graphDataProvider.isMemberOfOffice365Group(item.AssignedTo.Name.split('|')[2], await this.getCurrentUserPrinciple())
        }
      }));
      return taskIds.filter(task => task.isMember);
    }
   catch (error) {
      return null;
    }
  }

Points to be considered.
1. The method should be declared as async method.
2. The map should be surrounded with await Promise.all()
3. Within the map method, we can very well call the async method using the await keyword.

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

PowerShell : Change the Layout of the SharePoint Page

Sathish Nadarajan
 
Solution Architect
July 9, 2021
 
Rate this article
 
Views
2137

In SharePoint Online, when we create a new modern page, by default, the header of the page is coming as below. Sometimes, we don’t want that to be displayed. To hide the title bar, we can change the layout of the page itself.

 

#Connect to SharePoint Online site
$SiteURL = "https://sppalsmvp.sharepoint.com/sites/ReactRepository/"
Connect-PnPOnline $SiteURL -Credential (Get-Credential)

#Get the ID of the Page
Get-PnPListItem -List SitePages

#Change Page layout from "Article" to "Home"
Set-PnPListItem -List SitePages –Identity "13" -Values @{"PageLayoutType"="Home"}

Then, the title bar will not appear on this layout.

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

Leave a comment