Highlights of the article series
- How to register app in SharePoint
- How to access SharePoint data in Chat bot
Prerequisite
Code file is attached with article; you can pull code from GitHub also.In the previous part of the article, we have completed initial setup. In this part of the article, we will implement chat bot which will access data from SharePoint.
Request flow
In this diagram, I have mentioned sequence numbers to identify the flow.
- User will start a conversation with bot by greeting it; i.e., saying ‘Hi’ or ‘Hello’.
- We have already developed a bot application to consume LUIS service in this article. For SharePoint related communication, we will add SharePointController and SharePointDialog in our bot application.
SharePointController.cs
I have updated Post( ) method to invoke SharePointDialog with parameter activity.
- public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
- {
- if (activity.Type == ActivityTypes.Message)
- {
- await Conversation.SendAsync(activity, () => new Dialogs.SharePointDialog(activity));
- }
- else
- {
- HandleSystemMessage(activity);
- }
- var response = Request.CreateResponse(HttpStatusCode.OK);
- return response;
- }
SharePointDialog.cs
I have modified GreetWelcome method, which will greet user and provide url for authentication. I have kept this url in web.config as ‘SHAREPOINT_LOGIN_URI’ key. As we are working on development environment, I put localhost web application value. This url is targeted to action ‘LoginWithSharePoint’ of ‘Home’ controller. We will pass username of current user as query string parameter to save token against it after login.
web.config entries (bot application)
- <add key="SHAREPOINT_LOGIN_URI" value="https://localhost:44331/HOME/LoginWithSharePoint"/>
- [LuisIntent("Greet.Welcome")]
- public async Task GreetWelcome(IDialogContext context, LuisResult luisResult)
- {
- StringBuilder response = new StringBuilder();
-
- if (this.msgReceivedDate.ToString("tt") == "AM")
- {
- response.Append($"Good morning, {userName}.. :)");
- }
- else
- {
- response.Append($"Hey {userName}.. :)");
- }
-
- string sharepointLoginUrl = ConfigurationManager.AppSettings["SHAREPOINT_LOGIN_URI"];
-
- response.Append($"<br>Click <a href='{sharepointLoginUrl}?userName={this.userName}' >here</a> to login");
-
- await context.PostAsync(response.ToString());
- context.Wait(this.MessageReceived);
- }
3. When user clicks on link, control will go to LoginWithSharePoint action method in Home controller of our MVC web app. We will get user name from query string parameter. We will save it to session for future use.
4. We will form O365 authentication url using SharePoint site url, client id of app and redirect uri. Navigate the user to login page. I have provided redirect URI as action method LoggedinToSharePoint of Home controller.
web.config entries (AuthenticationWebApp)
- <add key="SPAUTH_APPCLIENTID" value=""/>
- <add key="SPAUTH_SITEURI" value=""/>
- <add key="SPAUTH_REDIRECTURI" value="https://localhost:44331/HOME/LoggedinToSharePoint"/>
- public ActionResult LoginWithSharePoint(string userName)
- {
-
- Session["SkypeUserID"] = userName;
-
- string spAuth_SiteUri = Convert.ToString(ConfigurationManager.AppSettings["SPAUTH_SITEURI"]);
-
- string spAuth_AppClientId = Convert.ToString(ConfigurationManager.AppSettings["SPAUTH_APPCLIENTID"]);
-
- string spAuth_RedirectUri = Convert.ToString(ConfigurationManager.AppSettings["SPAUTH_REDIRECTURI"]);
-
- string url = $"{spAuth_SiteUri}/_layouts/15/appredirect.aspx?client_id={spAuth_AppClientId}&redirect_uri={spAuth_RedirectUri}";
-
-
- return Redirect(url);
- }
5. After successful login, ACS will return control to Redirect URI i.e. in LoggedinToSharePoint action method. ACS will make post call to reditect URI and send ContextToken as Form parameter named SPAppToken.
6. Our application will save ContextToken against user name in MongoDB and ask user to continue with chat by returning View.
- public ActionResult LoggedinToSharePoint()
- {
- string contextToken = this.Request.Form["SPAppToken"];
- string userName = Convert.ToString(Session["SkypeUserID"]);
- new Mongo().Insert("ContextTokens", new Token(userName, contextToken));
-
- return View();
- }
Contracts.cs
- public class Token
- {
- public ObjectId _id;
- public string UserName;
- public string ContextToken;
-
- public Token(string userName, string contextToken)
- {
- UserName = userName;
- ContextToken = contextToken;
- }
- }
Mongo.cs
- public class Mongo
- {
- private IMongoDatabase _database;
-
- public Mongo()
- {
- string _connectionString = ConfigurationManager.AppSettings["MONGO_CONNECTIONSTRING"];
-
- MongoUrl mongoUrl = MongoUrl.Create(_connectionString);
-
- MongoClient _client = new MongoClient(new MongoClientSettings
- {
- Server = new MongoServerAddress(mongoUrl.Server.Host, mongoUrl.Server.Port)
- });
-
- _database = _client.GetDatabase(MongoUrl.Create(_connectionString).DatabaseName, null);
-
- }
-
- public void Insert<T>(string collectionName, T document)
- {
- IMongoCollection<T> collection = _database.GetCollection<T>(collectionName);
-
- collection.InsertOne(document);
- }
-
- public T Get<T>(string collectionName, string property, string value)
- {
- IMongoCollection<T> collection = _database.GetCollection<T>(collectionName);
-
- var filter = Builders<T>.Filter.Eq<string>(property, value);
-
- return collection.Find(filter).SingleOrDefault();
- }
- }
You can see context tokens are saved in MongoDB using RoboMongo (client app for MongoDB)
7. After successful login user comes back to chat window and starts a conversation with bot. Let’s say, user will search for Amit. LUIS will identify intent and control will go to SearchPeople method of SharePointDialog. Here it will extract the entity as Amit and invoke FindUserByName method of SharePoint repository.
- public async Task SearchPeople(IDialogContext context, LuisResult luisResult)
- {
- EntityRecommendation employeeName;
-
- string searchTerm_PersonName = string.Empty;
-
- if (luisResult.TryFindEntity("Person.Name", out employeeName))
- {
- searchTerm_PersonName = employeeName.Entity;
- }
-
- if (string.IsNullOrWhiteSpace(searchTerm_PersonName))
- {
- await context.PostAsync($"Unable to get search term.");
- }
- else
- {
- await context.PostAsync(new SharePoint(this.userName).FindUsersByName(searchTerm_PersonName));
- }
-
- context.Wait(this.MessageReceived);
- }
8. In FindUsersByName( ) method, Token for respective user is retrieved from MongoDB. And it will be used to create clientcontext. I have PeopleDetails list in SharePoint site with following details. Then CAML CONTAINS query will be executed against PeopleDetails list for Title column.
- public string FindUsersByName(string searchTermName)
- {
- string users = string.Empty;
-
- Token token = new Mongo().Get<Token>("ContextTokens", "UserName", this._userName);
-
- using (ClientContext context = TokenHelper.GetClientContextWithContextToken(_siteUri, token.ContextToken, "localhost:44331"))
- {
- CamlQuery query = new CamlQuery();
-
- query.ViewXml = $"<View><Query><Where><Contains><FieldRef Name='Title' /><Value Type='Text'>{searchTermName}</Value></Contains></Where></Query></View>";
-
- ListItemCollection peopleDetails = context.Web.Lists.GetByTitle("PeopleDetails").GetItems(query);
-
- context.Load(peopleDetails);
-
- context.ExecuteQuery();
-
- users = string.Join("<br>", peopleDetails.Select(x => x["Title"] + "(" + x["ContactNumber"] + "),"));
- }
-
- return users;
- }
9. Chat bot will return html containing list of users and their contact numbers. Keep trying different authentication services. Happy Chatting! :)