Microsoft Graph API: How to tell if an attendee is a group - microsoft-graph-api

I need to get events from a calendar, and find out individual users in the event.
var graphServiceClient = new GraphServiceClient(...);
var events = graphServiceClient.Me.CalendarView.Request().GetAsync();
// ...
var attendee = events[0].Attendees[0];
// Is attendee a group or user?
// If a group, how do we expand it?

We can determine whether the mail address is a user or the group via retrieve the user/group. For example, we can get the specific group via the REST below:
https://graph.microsoft.com/v1.0/groups?$filter=mail+eq+'group1#yourtenant.onmicrosoft.com'
If the email we provide is a user, then the response would have an empty value, otherwise it return the information for this group.
And to get the members of specific group, we can make a request with the id return by the above request:
https://graph.microsoft.com/v1.0/groups/{groupid}/members
And here is the code to get the group and its members for your reference. And I recommend you make the REST via HttpClient because it is more flexible and efficient.
public async Task GetGroup(string mailAddress)
{
var groups = await graphserviceClient.Groups.Request().Top(10).GetAsync();
foreach (var group in groups.CurrentPage)
{
Console.WriteLine(group.Mail);
if (mailAddress.Equals(group.Mail))
return group.Id;
}
while (groups.NextPageRequest != null)
{
groups = await groups.NextPageRequest.GetAsync();
foreach (var group in groups.CurrentPage)
{
Console.WriteLine(group.Mail);
if (mailAddress.Equals(group.Mail))
return group.Id;
}
}
return null;
}
public async void GetMembers(string groupId)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "bearer " + _accessToken);
string serviceURL = String.Format("https://graph.microsoft.com/v1.0/groups/{0}/members?$select=mail", groupId);
var response = client.GetAsync(serviceURL).Result;
JObject json = JObject.Parse(response.Content.ReadAsStringAsync().Result);
foreach (var mail in json["value"].Values("mail"))
{
Console.WriteLine(mail);
}
}
update
We need to have the "Group.Read.All" scope to read the groups:

Related

Microsoft.Graph SDK connects to user's OneDrive but Items returns NULL

I'm writing a utility in C# using the Microsoft.Graph SDK that connects and reads a user's OneDrive. I have created an App Registration, granted the application Files.Read.All permissions, and given Admin consent per the documentation.
I am able to connect to the Graph and the metadata for the OneDrive:
List<string> scopes = new List<string>();
scopes.Add("https://graph.microsoft.com/.default");
var authenticationProvider = new MsalAuthenticationProvider(cca, scopes.ToArray());
GraphServiceClient graphClient = new GraphServiceClient(authenticationProvider);
var drive = await graphClient.Users[userId].Drive
.Request()
.GetAsync();
It appears to correctly connect to the OneDrive, as evidenced by the properties that do return correct data like Quota, Owner, etc.
The issue is that the Items object is null, so I can't read any of the drive items:
I tried using the returned drive Id to access the drive directly, but received the same result:
var driveById = await graphClient.Drives[drive.Id]
.Request()
.GetAsync();
The few examples I found don't indicate any additional Request options or missing permissions. So how do I access the OneDrive Items?
The solution for this issue was presented in the comments, so I'm writing it up here for completeness.
ORIGINAL CODE:
var rootDrive = await GraphClient.Users[UserId].Drive.Request().GetAsync();
This returns the metadata of the user's OneDrive, but does not capture the Children. We will need this information later, however, so the final solution uses both this reference and the updated code.
UPDATED CODE:
To do that, you need to reference the drive's Root and its Children:
var driveItems = await GraphClient.Users[UserId].Drive
.Root
.Children
.Request()
.GetAsync();
Doing so returns an IDriveItemChildrenCollectionPage:
PROCESS THE CHILDREN:
For small samples, a standard foreach will work fine, but for larger collections you will need to implement a PageIterator (which I have not done yet). To get the children of this driveItem, you will need the drive Id of the root element as well as the current driveItem.Id:
var children = await GraphClient.Drives[rootDriveId].Items[item.Id].Children.Request().GetAsync()
Putting it altogether, it looks something like this:
public async Task ListOneDrive()
{
var rootDrive = await GraphClient.Users[UserId].Drive.Request().GetAsync();
var driveItems = await GraphClient.Users[UserId].Drive
.Root
.Children
.Request()
.GetAsync();
foreach (var item in driveItems)
{
await ListDriveItem(rootDrive.Id, item);
}
}
public async Task ListDriveItem(string rootDriveId, DriveItem item, int indentLevel = 1)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indentLevel; i++)
{
sb.Append($" ");
}
if (item.Folder != null)
{
Console.WriteLine($"{sb.ToString()}> {item.Name}/");
var children = await GraphClient.Drives[rootDriveId].Items[item.Id].Children.Request().GetAsync();
foreach (var child in children)
{
await (ListDriveItem(rootDriveId, child, indentLevel + 1));
}
}
else if (item.File != null)
{
Console.WriteLine($"{sb.ToString()} {item.Name}");
}
}
This example is from a Console app that uses a recursive call to drill down through all the folders. Both methods really should have a PageIterator as mentioned above.

https://graph.microsoft.com/v1.0/users/ is returning only 100 records, I want to fetch all the record present in Azure AD

https://graph.microsoft.com/v1.0/users/ is returning only 100 records, I want to fetch all the record present in Azure AD.
I have tried with above API to but it always gives me 100 records and with top, I am able to fetch only 999 records. But I have more than 100k records and want to fetch at a time.
The call to https://graph.microsoft.com/v1.0/users/ returns a property called #odata.nextlink. Use #odata.nextlink to request more pages of user data.
I think it is not possible to fetch all users with one request but you could wirte a method getting all users for you. The following code is requesting the first 100 users. Afterwards it is calling the next 100 users until there is no more users.
It is only a work around. You should keep in mind that this function needs a long time to run
public async Task<List<GraphApiUser>> GetAllCloudUserAsync()
{
var query = "/users";
var response = await SendGraphApiRequest(HttpMethod.Get, query);
var data = JsonConvert.DeserializeObject<GetMultipleUserResponse>(await response.Content.ReadAsStringAsync());
var result = new List<GraphApiUser>();
result.AddRange(data.value);
var debugCounter = 1;
while (!string.IsNullOrEmpty(data.NextLink))
{
response = await SendGraphApiRequest(HttpMethod.Get, "/"+data.NextLink);
data = JsonConvert.DeserializeObject<GetMultipleUserResponse>(await response.Content.ReadAsStringAsync());
result.AddRange(data.value);
debugCounter++;
}
return result;
}
GetMultipleUserResponse-Class looks like that:
public class GetMultipleUserResponse
{
public List<GraphApiUser> value { get; set; }
[JsonProperty("odata.nextLink")]
public string NextLink { get; set; }
}
The GraphApiUser-Class looks diffrent from AD to AD because everyone is able to define own claims. Setting up this class belongs to you!
Sending a request can be done like this:
private async Task<HttpResponseMessage> SendGraphApiRequest(HttpMethod httpMethod, string query,
string json = "")
{
HttpClient http = new HttpClient();
var requestUri = "Your Ressource Id" + "Your Tenant" + "your query"+ "your api version";
HttpRequestMessage request = new HttpRequestMessage(httpMethod, requestUri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", GetYourTokenHere());
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
Debug.WriteLine("Error Calling the Graph API: \n" +
JsonConvert.SerializeObject(formatted, Newtonsoft.Json.Formatting.Indented));
return null;
}
return response;
}

Microsoft Graph API

I am using one of the Microsoft Graph API in my project, i.e Outlook mail - "listconferencerooms" api.
Although it is giving he result, but the rooms listed in the result is not as expected.
When I cross check the Api result with the Meeting rooms available in my mailbox, both are not matching. In fact everyday i see the same set of meeting rooms which is not changing as per availability.
Code as below,
[HttpGet]
[Route("api/GetMeetingRooms")]
public async Task<string> getMeetingRooms()
{
//HttpResponseMessage message = null;
try
{
AppConfig appConfig = new AppConfig();
string token = await appConfig.GetTokenForApplication();
string response = string.Empty;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://graph.microsoft.com/");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage result = client.GetAsync("https://graph.microsoft.com/beta/me/findRooms").Result;
response = result.Content.ReadAsStringAsync().Result;
}
return response;
}
catch (Exception ex)
{
return ex.Message;
}
}
Kindly help me whether I am using correct API to get all available meeting, Conference rooms for my organization?
Or since the API is in Beta version I am not able to get the correct result.

Graph REST AddMember to Group - Bad Request

I am attempting to add members to a group. I am able to list all groups in my org, get user by email, get all users and I can even remove a Member from a group but I cannot add one - The error returned is 400 Bad Request.
Here is the function which is the same function signature as those that work: (I do have the accesstoken, valid group id and a valid member id)
I have confirmed the body data looks correct at least as far as I can see from the example in the docs.
Not sure what else I can add to make things clearer, ask and I'll update
public async Task<string> AddGroupMember(string accessToken, string groupId, string memberId)
{
var status = string.Empty;
string endpoint = $"https://graph.microsoft.com/v1.0/groups/{groupId}/members/$ref";
string queryParameter = "";
// pass body data
var keyOdataId = "#odata.id";
var valueODataId = $"https://graph.microsoft.com/v1.0/directoryObjects/{memberId}";
var values = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(keyOdataId, valueODataId)
};
var body = new FormUrlEncodedContent(values);
try
{
using(var client = new HttpClient())
{
using(var request = new HttpRequestMessage(HttpMethod.Post, endpoint + queryParameter))
{
request.Content = body;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using(var response = await client.SendAsync(request))
{
if (response.StatusCode == HttpStatusCode.NoContent)
status = "Member added to Group";
else
status = $"Unable to add Member to Group: {response.StatusCode}";
}
}
}
}
catch (Exception ex)
{
status = $"Error adding Member to Group: {ex.Message}";
}
return status;
}
Thanks for any help that anyone can offer - this is the last call I have to make then home free
Found the issue for any who care to know for the future:
var body = new FormUrl... my code was incorrect, what's needed is a simple json string changed to this UPDATED:
var jsonData = $#"{{ ""{keyOdataId}"": ""{valueODataId}"" }}";
var body = new StringContent(jsonData, Encoding.UTF8, "application/json");
I would normally put the values in a class but this is for proof of concept and the json key needs to look exactly like this #odata.id
Clarifying what is happening here:
The request body for this call should be JSON encoded (application/json). The FormUrlEncodedContent method returns your dictionary as Form encoded (application/x-www-form-urlencoded).
You can write the JSON by hand (like you have so far) but a better solution would be to leverage Json.NET. This will let you encode the dictionary in much the same way you were with FormUrlEncodedContent:
var values = new Dictionary<string, string>
{
{ keyOdataId, valueODataId}
};
var body = JsonConvert.SerializeObject(values);
If you're going to be doing a lot of work with Microsoft Graph, I would highly recommend switching to the Microsoft Graph .NET SDK.
You're method here would be far simpler using the SDK:
public async Task<string> AddGroupMember(string groupId, string memberId)
{
GraphServiceClient graphClient = AuthenticationHelper.GetAuthenticatedClient();
User userToAdd = new User { Id = memberId };
await graphClient.Groups[groupId].Members.References.Request().AddAsync(userToAdd);
}

Add filter on property for odata query

I have an entity: ItContract and every ItContract belongs to an organisation unit.
A user logs-in to an organisation unit and have read access to all data.
How can I set a filter on organisationUnitId on the server for every odata query?
I am using odata v4 with asp.net.
There is a way to override the queryOption you get in server side.
public IHttpActionResult Get(ODataQueryOptions<People> queryOptions)
{
// get the original request before the alterations
HttpRequestMessage originalRequest = queryOptions.Request;
// get the original URL before the alterations
string url = originalRequest.RequestUri.AbsoluteUri;
// rebuild the URL
if (queryOptions.Filter != null)
{
// apply the new filter
url = url.Replace("$filter=", "$filter=organisationUnitId%20eq%20" + organisationUnitId + ",");
}
else
{
if (url.Contains("$"))
{
url += "&";
}
url += "$filter=organisationUnitId%20eq%20" + organisationUnitId;
}
// build a new request for the filter
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, url);
// reset the query options with the new request
queryOptions = new ODataQueryOptions(queryOptions.Context, req);
var result = queryOptions.ApplyTo(_db.Prople);
return Ok(result, result.GetType());
}
private IHttpActionResult Ok(object content, Type type)
{
var resultType = typeof(OkNegotiatedContentResult<>).MakeGenericType(type);
return Activator.CreateInstance(resultType, content, this) as IHttpActionResult;
}

Resources