C# Exchange Web 服务托管 API 模拟 -> Microsoft Graph API
C# Exchange Web Services Managed API Impersonation -> Microsoft Graph API
我有一个 c# 应用程序可以查询我们的 Microsoft Exchange 服务器(现在是 Exchange Online)。它是使用 Microsoft.Exchange.WebServices .NET 库编写的。 IIS 中的应用程序池在 Exchange 中具有提升权限的帐户下运行。这允许它查询所有用户的日历,以便应用程序可以显示他们是 busy/out 在办公室还是在其他地方工作。 _service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
设置服务告诉服务应用程序池帐户将模拟用户(电子邮件地址)来查询日历。
综上所述,Microsoft Exchange Web Services Managed API 将于今年年底折旧。我想用 Microsoft Graph 重写这个过程。我找到了大量关于如何使用 this.
访问交换数据和查询日历的信息
有没有人找到任何关于如何使用 Microsoft Graph API 完成以下功能的好例子?是否有 .NET 包装器 class 我可以使用或者我是否需要使用 REST Web 服务端点并创建我自己的?
public FindItemsResults<Appointment> GetCalendarAppointments(string emailAddress, string calendarName, DateTime start, DateTime end)
{
// start with on prem exchange
_service.UseDefaultCredentials = true; // use app pool security context
_service.Url = new Uri(ConfigurationManager.ConnectionStrings["ExchangeURL"].ConnectionString);
_service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
FolderView folderView = new FolderView(25);
folderView.PropertySet = new PropertySet(BasePropertySet.IdOnly);
folderView.PropertySet.Add(FolderSchema.DisplayName);
SearchFilter searchFilter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, calendarName);
folderView.Traversal = FolderTraversal.Deep;
FindFoldersResults findFolderResults = _service.FindFolders(WellKnownFolderName.Root, searchFilter, folderView);
if (findFolderResults.TotalCount == 0)
return null;
FindItemsResults<Appointment> appointments;
CalendarFolder calendarFolder;
CalendarView calendarView = new CalendarView(start, end, 30);
calendarView.PropertySet = new PropertySet(AppointmentSchema.Id,
AppointmentSchema.Start,
AppointmentSchema.End,
AppointmentSchema.Subject,
AppointmentSchema.Location);
calendarFolder = (CalendarFolder)findFolderResults.Folders[0];
try
{
appointments = calendarFolder.FindAppointments(calendarView);
}
catch (Exception e)
{
if (e.Message == "The SMTP address has no mailbox associated with it.")
{
// try exchange online
_service.Credentials = new WebCredentials(ConfigurationManager.ConnectionStrings["ExchangeOnlineServiceAccountUsername"].ConnectionString,
ConfigurationManager.ConnectionStrings["ExchangeOnlineServiceAccountPassword"].ConnectionString);
_service.Url = new Uri(ConfigurationManager.ConnectionStrings["ExchangeOnlineUrl"].ConnectionString);
try
{
appointments = calendarFolder.FindAppointments(calendarView);
}
catch (Exception ex)
{
throw new Exception("Error when trying to read exchange to get calendar " + calendarName + " from exchange online inbox " + emailAddress + ": " + ex.Message);
}
}
else
{
throw new Exception("Error when trying to read exchange to get calendar " + calendarName + " from on prem exchange inbox " + emailAddress + ": " + e.Message);
}
}
if (appointments == null || appointments.Items.Count < 1)
return null;
return appointments;
}
@埃里克
您可以使用微软提供的 sdk,通过 Graph API Endpoints 实现上述功能。可在 here.
中找到各种平台的 SDK 概述以及示例
您也可以尝试 Graph explorer 和他们的邮递员集合来了解 API 端点。
Github link 到 MS-GRAPH-DOTNET-SDK.
我能够通过为我的应用程序注册设置 Microsoft Graph 应用程序 API 权限来完成此操作。对于我的场景,我需要 Calendars.Read + Users.Read.All + Groups.Read.All + GroupMember.Read.All。在我使用这些权限之前,必须先获得 Azure 管理员的管理员同意。在 Azure 中创建客户端密码后,我参考了 this example from GitHub 开始。最后,当我从 Azure AD 获取令牌时,我创建了一个扩展 class,将其附加到请求并检索特定组用户的当前日历约会。随心所欲地引用它,我希望它能对以后的其他人有所帮助。
/// <summary>
/// Class will contain all MS graph API types of requests for now
/// </summary>
/// <see cref="https://github.com/microsoftgraph/msgraph-sdk-dotnet" />
public class MicrosoftGraphExtensions
{
private GraphServiceClient GraphServiceClient;
public MicrosoftGraphExtensions()
{
// Note: Per post at https://prcode.co.uk/2020/03/24/microsoft-graph-client-clientcredentialprovider-not-recognised/
// the Microsoft.Graph.Auth nuget package (which is required to use the ClientCredentialProvider code below)
// is not yet available except of pre-release.
// For now, we can use the following method and manually add the token to the authorization header of the API
GraphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (request) =>
{
string[] tokenScopes = ConfigurationManager.ConnectionStrings["Azure_TokenScopes"].ConnectionString.Split(new char[] { ',' });
// build the confidential client application the same way as before
var confidentailClient = ConfidentialClientApplicationBuilder
.Create(ConfigurationManager.ConnectionStrings["CLIENTIDFROMAZURE"].ConnectionString)
.WithTenantId(ConfigurationManager.ConnectionStrings["TENANTIDFROMAZURE"].ConnectionString)
.WithClientSecret(ConfigurationManager.ConnectionStrings["CLIENTSECRETFROMAZURE"].ConnectionString)
.Build();
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentailClient.AcquireTokenForClient(tokenScopes).ExecuteAsync().ConfigureAwait(false);
// Add the access token in the Authorization header of the API
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}));
/* eventually we should be able to do the following when the nuget package is available
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(ConfigurationManager.ConnectionStrings["Azure_ClientId"].ConnectionString)
.WithTenantId(ConfigurationManager.ConnectionStrings["Azure_TenantId"].ConnectionString)
.WithClientSecret(ConfigurationManager.ConnectionStrings["Azure_ClientSecret"].ConnectionString)
.Build();
// to reference different authProviders supported with graph, look https://docs.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient = new GraphServiceClient(authProvider);
*/
}
/// <summary>
/// Get a list of the group's members. A group can have users, devices, organizational contacts, and other groups as members.
/// This operation is transitive and returns a flat list of all nested members.
/// </summary>
/// <param name="groupName">displayName of the group</param>
/// <returns>List of NON GROUP objects with only id, displayName & mail properties</returns>
public async Task<IEnumerable<User>> GetGroupMembersAsync(string groupName)
{
var groups =
await GraphServiceClient.Groups
.Request()
// https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
.Filter("displayName+eq+'" + groupName + "'")
// want to select minimal properties necessary
.Select("id,displayName")
// we are assumning that the group name is unique so only get top 1
.Top(1)
.GetAsync();
if (groups.FirstOrDefault() == null)
throw new Exception("Group with name of " + groupName + " not found");
var members =
await GraphServiceClient.Groups[groups.FirstOrDefault().Id].TransitiveMembers
.Request()
// currently api does not support filtering by odata.type to
// get users or groups etc but all of our role groups do not have emails
// so we can filter them out this way
// atm it seems like checking for null or empty strings isn't even supported
// we would have to do it client side after query is complete
//.Filter("displayName+ne+'Intern, Human Resources' and not startswith(surname,'Scanner')")
.Select("id,displayName,mail,givenName,surname")
.GetAsync();
List<User> allUsers = new List<User>();
var pageIterator = PageIterator<DirectoryObject>
.CreatePageIterator(GraphServiceClient, members, (m) =>
{
// this is where we are filtering and only adding users to collection
// only add users with email property who are not first name "Intern" and who are not last name "Scanner"
// Not a fan of having to do this here, BUT can't find very many things that the .Filter attribute
// actually supports, so we need to do it somewhere
if(m is User user && !string.IsNullOrEmpty(user.Mail) && user.Surname != "Intern" && user.Surname != "Scanner")
{
allUsers.Add(user);
}
return true;
});
await pageIterator.IterateAsync();
return allUsers;
}
/// <summary>
/// Returns the current event the user is in that isn't marked as private, free,
/// tentative or unknown. If none is found, null is returned
/// </summary>
/// <param name="id">id of the user from MS Graph</param>
/// <returns>A single event</returns>
public async Task<Event> GetUsersCurrentAppointmentAsync(string id)
{
// give me anything that "occurs" within the specified timeframe
// we use 3 min here because we know that is the typical update time from the client
var queryOptions = new List<QueryOption>()
{
new QueryOption("startDateTime", DateTime.UtcNow.ToString("o")),
new QueryOption("endDateTime", DateTime.UtcNow.ToString("o"))
};
var events =
await GraphServiceClient.Users[id].CalendarView
.Request(queryOptions)
// https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
.Filter(
"sensitivity+eq+'normal'" + // show apts that are marked normal sensitivity
" and showAs+ne+'free'" + // show apts that are not marked showAs = free
" and showAs+ne+'tentative'" + // show apts that are not marked showAs = tentative
" and showAs+ne+'Unknown'" + // show apts that are nto marked showAs = unknown
" and isCancelled+eq+false" // show apts that have not been cancelled
)
// want to select minimal properties necessary
.Select("showAs,location,start,end,sensitivity")
.GetAsync();
if (events.Count < 1)
return null;
// once its back client side, we will only return one appointment
// out of office takes precedence
// then working elsewere
// then finally Busy
List<Event> lstEvents = events.ToList();
// oof takes precedence so start with that
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Oof).ToList().Count > 0)
{
// we know there is at least one oof apt, is there more?
if(lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Oof).ToList().Count > 1)
{
// there is more than one, so we show the one ending LATEST
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Oof).OrderByDescending(e => e.End.DateTime).FirstOrDefault();
}
else
{
// we know there is only one, so return that
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Oof).FirstOrDefault();
}
}
// now do workingElsewhere
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.WorkingElsewhere).ToList().Count > 0)
{
// we know there is at least one workingelsewhere apt, is there more?
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.WorkingElsewhere).ToList().Count > 1)
{
// there is more than one, so we show the one ending LATEST
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.WorkingElsewhere).OrderByDescending(e => e.End.DateTime).FirstOrDefault();
}
else
{
// we know there is only one, so return that
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.WorkingElsewhere).FirstOrDefault();
}
}
// finally do busy
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Busy).ToList().Count > 0)
{
// we know there is at least one workingelsewhere apt, is there more?
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Busy).ToList().Count > 1)
{
// there is more than one, so we show the one ending LATEST
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Busy).OrderByDescending(e => e.End.DateTime).FirstOrDefault();
}
else
{
// we know there is only one, so return that
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Busy).FirstOrDefault();
}
}
// technically it should never get here because we are initially only getting apts not marked as showAs free, tentative or unknown
// the only remaining possible showAs are handled above with oof, workingElsewhere and busy
return lstEvents.OrderByDescending(e => e.End).FirstOrDefault();
}
/// <summary>
/// Returns the calendar view for the given user principal name
/// </summary>
/// <param name="userPrincipalName">UserPrincipalName</param>
/// <param name="start">Start time must be in UTC</param>
/// <param name="end">End time must be in UTC</param>
/// <returns></returns>
public async Task<List<Event>> GetUserCalendar(string userPrincipalName, string calendarName, DateTime start, DateTime end)
{
var users =
await GraphServiceClient.Users
.Request()
.Filter("userPrincipalName+eq+'" + userPrincipalName + "'")
.Select("id")
.Top(1)
.GetAsync();
User user = users.FirstOrDefault();
if (user == null)
throw new Exception("Could not find user " + userPrincipalName + ".");
// next we have to get the id for the calendar by name provided
var calendars =
await GraphServiceClient.Users[user.Id].Calendars
.Request()
.Filter("name+eq+'" + calendarName + "'")
.Select("id")
.GetAsync();
Calendar calendar = calendars.FirstOrDefault();
if (calendar == null)
throw new Exception("Could not find calendar with name " + calendarName + " for user " + userPrincipalName);
// give me anything that "occurs" within the specified timeframe
// we use 3 min here because we know that is the typical update time from the client
var queryOptions = new List<QueryOption>()
{
new QueryOption("startDateTime",start.ToString("o")),
new QueryOption("endDateTime", end.ToString("o"))
};
var events =
await GraphServiceClient.Users[user.Id].Calendars[calendar.Id].CalendarView
.Request(queryOptions)
// https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
// want to select minimal properties necessary
.Select("id,subject,location,start,end")
.GetAsync();
return events.ToList();
}
}
我有一个 c# 应用程序可以查询我们的 Microsoft Exchange 服务器(现在是 Exchange Online)。它是使用 Microsoft.Exchange.WebServices .NET 库编写的。 IIS 中的应用程序池在 Exchange 中具有提升权限的帐户下运行。这允许它查询所有用户的日历,以便应用程序可以显示他们是 busy/out 在办公室还是在其他地方工作。 _service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
设置服务告诉服务应用程序池帐户将模拟用户(电子邮件地址)来查询日历。
综上所述,Microsoft Exchange Web Services Managed API 将于今年年底折旧。我想用 Microsoft Graph 重写这个过程。我找到了大量关于如何使用 this.
访问交换数据和查询日历的信息有没有人找到任何关于如何使用 Microsoft Graph API 完成以下功能的好例子?是否有 .NET 包装器 class 我可以使用或者我是否需要使用 REST Web 服务端点并创建我自己的?
public FindItemsResults<Appointment> GetCalendarAppointments(string emailAddress, string calendarName, DateTime start, DateTime end)
{
// start with on prem exchange
_service.UseDefaultCredentials = true; // use app pool security context
_service.Url = new Uri(ConfigurationManager.ConnectionStrings["ExchangeURL"].ConnectionString);
_service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
FolderView folderView = new FolderView(25);
folderView.PropertySet = new PropertySet(BasePropertySet.IdOnly);
folderView.PropertySet.Add(FolderSchema.DisplayName);
SearchFilter searchFilter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, calendarName);
folderView.Traversal = FolderTraversal.Deep;
FindFoldersResults findFolderResults = _service.FindFolders(WellKnownFolderName.Root, searchFilter, folderView);
if (findFolderResults.TotalCount == 0)
return null;
FindItemsResults<Appointment> appointments;
CalendarFolder calendarFolder;
CalendarView calendarView = new CalendarView(start, end, 30);
calendarView.PropertySet = new PropertySet(AppointmentSchema.Id,
AppointmentSchema.Start,
AppointmentSchema.End,
AppointmentSchema.Subject,
AppointmentSchema.Location);
calendarFolder = (CalendarFolder)findFolderResults.Folders[0];
try
{
appointments = calendarFolder.FindAppointments(calendarView);
}
catch (Exception e)
{
if (e.Message == "The SMTP address has no mailbox associated with it.")
{
// try exchange online
_service.Credentials = new WebCredentials(ConfigurationManager.ConnectionStrings["ExchangeOnlineServiceAccountUsername"].ConnectionString,
ConfigurationManager.ConnectionStrings["ExchangeOnlineServiceAccountPassword"].ConnectionString);
_service.Url = new Uri(ConfigurationManager.ConnectionStrings["ExchangeOnlineUrl"].ConnectionString);
try
{
appointments = calendarFolder.FindAppointments(calendarView);
}
catch (Exception ex)
{
throw new Exception("Error when trying to read exchange to get calendar " + calendarName + " from exchange online inbox " + emailAddress + ": " + ex.Message);
}
}
else
{
throw new Exception("Error when trying to read exchange to get calendar " + calendarName + " from on prem exchange inbox " + emailAddress + ": " + e.Message);
}
}
if (appointments == null || appointments.Items.Count < 1)
return null;
return appointments;
}
@埃里克 您可以使用微软提供的 sdk,通过 Graph API Endpoints 实现上述功能。可在 here.
中找到各种平台的 SDK 概述以及示例您也可以尝试 Graph explorer 和他们的邮递员集合来了解 API 端点。
Github link 到 MS-GRAPH-DOTNET-SDK.
我能够通过为我的应用程序注册设置 Microsoft Graph 应用程序 API 权限来完成此操作。对于我的场景,我需要 Calendars.Read + Users.Read.All + Groups.Read.All + GroupMember.Read.All。在我使用这些权限之前,必须先获得 Azure 管理员的管理员同意。在 Azure 中创建客户端密码后,我参考了 this example from GitHub 开始。最后,当我从 Azure AD 获取令牌时,我创建了一个扩展 class,将其附加到请求并检索特定组用户的当前日历约会。随心所欲地引用它,我希望它能对以后的其他人有所帮助。
/// <summary>
/// Class will contain all MS graph API types of requests for now
/// </summary>
/// <see cref="https://github.com/microsoftgraph/msgraph-sdk-dotnet" />
public class MicrosoftGraphExtensions
{
private GraphServiceClient GraphServiceClient;
public MicrosoftGraphExtensions()
{
// Note: Per post at https://prcode.co.uk/2020/03/24/microsoft-graph-client-clientcredentialprovider-not-recognised/
// the Microsoft.Graph.Auth nuget package (which is required to use the ClientCredentialProvider code below)
// is not yet available except of pre-release.
// For now, we can use the following method and manually add the token to the authorization header of the API
GraphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (request) =>
{
string[] tokenScopes = ConfigurationManager.ConnectionStrings["Azure_TokenScopes"].ConnectionString.Split(new char[] { ',' });
// build the confidential client application the same way as before
var confidentailClient = ConfidentialClientApplicationBuilder
.Create(ConfigurationManager.ConnectionStrings["CLIENTIDFROMAZURE"].ConnectionString)
.WithTenantId(ConfigurationManager.ConnectionStrings["TENANTIDFROMAZURE"].ConnectionString)
.WithClientSecret(ConfigurationManager.ConnectionStrings["CLIENTSECRETFROMAZURE"].ConnectionString)
.Build();
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentailClient.AcquireTokenForClient(tokenScopes).ExecuteAsync().ConfigureAwait(false);
// Add the access token in the Authorization header of the API
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}));
/* eventually we should be able to do the following when the nuget package is available
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(ConfigurationManager.ConnectionStrings["Azure_ClientId"].ConnectionString)
.WithTenantId(ConfigurationManager.ConnectionStrings["Azure_TenantId"].ConnectionString)
.WithClientSecret(ConfigurationManager.ConnectionStrings["Azure_ClientSecret"].ConnectionString)
.Build();
// to reference different authProviders supported with graph, look https://docs.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient = new GraphServiceClient(authProvider);
*/
}
/// <summary>
/// Get a list of the group's members. A group can have users, devices, organizational contacts, and other groups as members.
/// This operation is transitive and returns a flat list of all nested members.
/// </summary>
/// <param name="groupName">displayName of the group</param>
/// <returns>List of NON GROUP objects with only id, displayName & mail properties</returns>
public async Task<IEnumerable<User>> GetGroupMembersAsync(string groupName)
{
var groups =
await GraphServiceClient.Groups
.Request()
// https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
.Filter("displayName+eq+'" + groupName + "'")
// want to select minimal properties necessary
.Select("id,displayName")
// we are assumning that the group name is unique so only get top 1
.Top(1)
.GetAsync();
if (groups.FirstOrDefault() == null)
throw new Exception("Group with name of " + groupName + " not found");
var members =
await GraphServiceClient.Groups[groups.FirstOrDefault().Id].TransitiveMembers
.Request()
// currently api does not support filtering by odata.type to
// get users or groups etc but all of our role groups do not have emails
// so we can filter them out this way
// atm it seems like checking for null or empty strings isn't even supported
// we would have to do it client side after query is complete
//.Filter("displayName+ne+'Intern, Human Resources' and not startswith(surname,'Scanner')")
.Select("id,displayName,mail,givenName,surname")
.GetAsync();
List<User> allUsers = new List<User>();
var pageIterator = PageIterator<DirectoryObject>
.CreatePageIterator(GraphServiceClient, members, (m) =>
{
// this is where we are filtering and only adding users to collection
// only add users with email property who are not first name "Intern" and who are not last name "Scanner"
// Not a fan of having to do this here, BUT can't find very many things that the .Filter attribute
// actually supports, so we need to do it somewhere
if(m is User user && !string.IsNullOrEmpty(user.Mail) && user.Surname != "Intern" && user.Surname != "Scanner")
{
allUsers.Add(user);
}
return true;
});
await pageIterator.IterateAsync();
return allUsers;
}
/// <summary>
/// Returns the current event the user is in that isn't marked as private, free,
/// tentative or unknown. If none is found, null is returned
/// </summary>
/// <param name="id">id of the user from MS Graph</param>
/// <returns>A single event</returns>
public async Task<Event> GetUsersCurrentAppointmentAsync(string id)
{
// give me anything that "occurs" within the specified timeframe
// we use 3 min here because we know that is the typical update time from the client
var queryOptions = new List<QueryOption>()
{
new QueryOption("startDateTime", DateTime.UtcNow.ToString("o")),
new QueryOption("endDateTime", DateTime.UtcNow.ToString("o"))
};
var events =
await GraphServiceClient.Users[id].CalendarView
.Request(queryOptions)
// https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
.Filter(
"sensitivity+eq+'normal'" + // show apts that are marked normal sensitivity
" and showAs+ne+'free'" + // show apts that are not marked showAs = free
" and showAs+ne+'tentative'" + // show apts that are not marked showAs = tentative
" and showAs+ne+'Unknown'" + // show apts that are nto marked showAs = unknown
" and isCancelled+eq+false" // show apts that have not been cancelled
)
// want to select minimal properties necessary
.Select("showAs,location,start,end,sensitivity")
.GetAsync();
if (events.Count < 1)
return null;
// once its back client side, we will only return one appointment
// out of office takes precedence
// then working elsewere
// then finally Busy
List<Event> lstEvents = events.ToList();
// oof takes precedence so start with that
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Oof).ToList().Count > 0)
{
// we know there is at least one oof apt, is there more?
if(lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Oof).ToList().Count > 1)
{
// there is more than one, so we show the one ending LATEST
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Oof).OrderByDescending(e => e.End.DateTime).FirstOrDefault();
}
else
{
// we know there is only one, so return that
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Oof).FirstOrDefault();
}
}
// now do workingElsewhere
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.WorkingElsewhere).ToList().Count > 0)
{
// we know there is at least one workingelsewhere apt, is there more?
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.WorkingElsewhere).ToList().Count > 1)
{
// there is more than one, so we show the one ending LATEST
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.WorkingElsewhere).OrderByDescending(e => e.End.DateTime).FirstOrDefault();
}
else
{
// we know there is only one, so return that
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.WorkingElsewhere).FirstOrDefault();
}
}
// finally do busy
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Busy).ToList().Count > 0)
{
// we know there is at least one workingelsewhere apt, is there more?
if (lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Busy).ToList().Count > 1)
{
// there is more than one, so we show the one ending LATEST
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Busy).OrderByDescending(e => e.End.DateTime).FirstOrDefault();
}
else
{
// we know there is only one, so return that
return lstEvents.Where(e => e.ShowAs == FreeBusyStatus.Busy).FirstOrDefault();
}
}
// technically it should never get here because we are initially only getting apts not marked as showAs free, tentative or unknown
// the only remaining possible showAs are handled above with oof, workingElsewhere and busy
return lstEvents.OrderByDescending(e => e.End).FirstOrDefault();
}
/// <summary>
/// Returns the calendar view for the given user principal name
/// </summary>
/// <param name="userPrincipalName">UserPrincipalName</param>
/// <param name="start">Start time must be in UTC</param>
/// <param name="end">End time must be in UTC</param>
/// <returns></returns>
public async Task<List<Event>> GetUserCalendar(string userPrincipalName, string calendarName, DateTime start, DateTime end)
{
var users =
await GraphServiceClient.Users
.Request()
.Filter("userPrincipalName+eq+'" + userPrincipalName + "'")
.Select("id")
.Top(1)
.GetAsync();
User user = users.FirstOrDefault();
if (user == null)
throw new Exception("Could not find user " + userPrincipalName + ".");
// next we have to get the id for the calendar by name provided
var calendars =
await GraphServiceClient.Users[user.Id].Calendars
.Request()
.Filter("name+eq+'" + calendarName + "'")
.Select("id")
.GetAsync();
Calendar calendar = calendars.FirstOrDefault();
if (calendar == null)
throw new Exception("Could not find calendar with name " + calendarName + " for user " + userPrincipalName);
// give me anything that "occurs" within the specified timeframe
// we use 3 min here because we know that is the typical update time from the client
var queryOptions = new List<QueryOption>()
{
new QueryOption("startDateTime",start.ToString("o")),
new QueryOption("endDateTime", end.ToString("o"))
};
var events =
await GraphServiceClient.Users[user.Id].Calendars[calendar.Id].CalendarView
.Request(queryOptions)
// https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
// want to select minimal properties necessary
.Select("id,subject,location,start,end")
.GetAsync();
return events.ToList();
}
}