Karine Bosch’s Blog

On SharePoint

Working with Microsoft Graph to access o365 Planner

I’m currently working on a project where I have to migrate classic SharePoint tasks lists to Planner in O365. It cannot be done using CSOM, but there is a Planner REST API you can use.

This post does not contain new stuff, but I had to read trough hundreds of posts to get it all together. Therefore, I decided to write a post for whom it may be helpful.

I wrote a simple MVC-based Azure web site to do the work, nothing fancy, as it is just to migrate existing tasks lists. But you cannot communicate with your O365 tenant through Graph using SharePointOnlineCredentials. Therefore, I first registered an Azure App to have a ClientID/ClientSecret combination:

The Redirect URL is the URL to where I deployed my Azure web site (which is not described in this blog post)

Planner is part of O365 groups, so you have to give this Azure App at least read permissions to the O365 groups in your tenant. Via the Required permissions setting, I can give access to my application to as many APIs as available.

I selected the Graph API, and gave my application the permission to read from site collections and to read/write O365 groups:

I also had to click the Grant Permissions button. As an you then consent to an application’s delegated permissions on behalf of all the users in your tenant.

Read more Azure App registrations.

This gives me the permission to request data about all O365 groups in my tenant.

Now it is coding time ūüôā

First I need to  get an access token based on the client ID and client secret of the Azure App I just registered:

private async Task<string> GetAccessToken(string resourceId)
{
    try
    {
        var authority = ConfigurationManager.AppSettings["ida:AuthorizationUri"] + ConfigurationManager.AppSettings["ida:TenantId"];
        var clientCredential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"],
        ConfigurationManager.AppSettings["ida:ClientSecret"]);

        AuthenticationContext ac = new AuthenticationContext(authority);
        AuthenticationResult result = await ac.AcquireTokenAsync(resourceId, clientCredential);

        return result.AccessToken;
     }
     catch (Exception ex)
     {
         // TODO: log the exception
         return null;
     }
 }
  • The AuthorizationUri is¬†https://login.windows.net/
  • The TenantId is the ID of your O365 tenant. If you don’t know the ID, you can find it in your Azure portal: navigate to Azure Active Directory > Properties, and in the Map ID, you’ll find your tenant ID
  • The ResourceId is the Graph URI:¬†https://graph.microsoft.com

Tip: You can verify if you have a valid access token by going to https://jwt.io/

Next step is to retrieve the O365 group:

 string restUrl = string.Format("https://graph.microsoft.com/v1.0/kboske.com/groups?$filter=displayname eq '{0}'", groupName);
 Task<string> getGroupTask = Task<string>.Run(() => GetResponse(restUrl, accessToken));
 getGroupTask.Wait();

If you get a successful response, you will get something like this:

My GetResponse method is very basic, but I add it in here for completeness:

private async Task<string> GetResponse(string restUrl, string accessToken)
{
    string jsonresult = null;

    try
    {
       using (HttpClient client = new HttpClient())
       {
          var accept = "application/json";
          client.DefaultRequestHeaders.Add("Accept", accept);
          client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
          using (var response = await client.GetAsync(restUrl))
          {
              if (response.IsSuccessStatusCode)
              {
                  jsonresult = await response.Content.ReadAsStringAsync();
              }
              else
              {
                   throw new Exception("Error getting data: " + response.StatusCode.ToString());
              }
          }
       }
    }
    catch (Exception ex)
    {
       // TODO: handle the exception
    }
    return jsonresult;
}

Once I get the group Id, I can get to the plan, using the following REST call, no?

restUrl = string.Format("https://graph.microsoft.com/v1.0/recomatics.com/groups/{0}/planner/plans", groupId);

No, you can not!! You get a

But when I try to access Planner, I get a 401 РUnauthorized error instead.

It seems you can only access the plans by using user credentials because you can only access the plans to which you have permissions.

I changed the GetAccessToken method, in order to authenticate with my user credentials:

private async Task<string> GetAccessToken(string resourceId, string userName, string password)
{
    try
    {
        var authority = ConfigurationManager.AppSettings["ida:AuthorizationLoginUri"] + ConfigurationManager.AppSettings["ida:TenantId"];
        var authContext = new AuthenticationContext(authority);
        var credentials = new UserPasswordCredential(userName, password);
        var authResult = await authContext.AcquireTokenAsync(resourceId, ConfigurationManager.AppSettings["ida:ClientIdNativeClient"], credentials);

        // Get the result
        return authResult.AccessToken;
    }
    catch (Exception ex)
    {
       // TODO: handle the exception
       return;
    }
 }

But then I get a different error:

"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'."

That’s because my Azure App only accepts client id / client secret combination because I registered it as a Web service.¬†To be able to authenticate using my user credentials, I have to register an Azure App as a Native client:

When I now call the REST endpoint to get to the plan of my O365 group, I get a successful response:

Tip: try out your REST calls in the Graph explorer

Helpful posts:

December 18, 2017 Posted by | CSOM, Graph | 1 Comment

Setting the value of a lookup field using CSOM

Today I had a hard time to find out how to set the value of a lookup field using CSOM. In most of the code samples I found on the internet, the LookupId is set hard coded, and this is not what I needed. Additionally, in some cases the lookup list was situated on the root web, and in another case I had to query a different field than the Title field. I ended up writing the following method:

public static FieldLookupValue GetLookupValue(ClientContext clientContext, string value, 
   string lookupListName, string lookupFieldName, string lookupFieldType, bool onRootWeb)
{
     List list = null;
     FieldLookupValue lookupValue = null;

     if (onRootWeb)
     {
          list = clientContext.Site.RootWeb.Lists.GetByTitle(listName);
     }
     else
     {
          list = clientContext.Web.Lists.GetByTitle(listName);
     }

     if (list != null)
     {
         CamlQuery camlQueryForItem = new CamlQuery();
         camlQueryForItem.ViewXml = string.Format(@"<View>
                  <Query>
                      <Where>
                         <Eq>
                             <FieldRef Name='{0}'/>
                             <Value Type='{1}'>{2}</Value>
                         </Eq>
                       </Where>
                   </Query>
            </View>", lookupFieldName, lookupFieldType, value);

          listItemCollection listItems = list.GetItems(camlQueryForItem);
          clientContext.Load(listItems, items => items.Include
                                            (listItem => listItem["ID"],
                                             listItem => listItem[lookupFieldName]));
          clientContext.ExecuteQuery();

          if (listItems != null)
          {
              ListItem item = listItems[0];
              lookupValue = new FieldLookupValue();
              lookupValue.LookupId = Int.Parse(item["ID"].ToString());
          }             
     }

     return lookupValue;
}

This method works on both SharePoint 2010 and 2013.

I hope this code snippet can help others.

Enjoy!

 

May 11, 2015 Posted by | CSOM, SharePoint 2010 | 3 Comments