diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 56eee02abc8e..5f0602cd5cd7 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -84,7 +84,7 @@
-
+
@@ -144,7 +144,7 @@
-
+
diff --git a/dotnet/samples/Concepts/Plugins/MsGraph_CalendarPlugin.cs b/dotnet/samples/Concepts/Plugins/MsGraph_CalendarPlugin.cs
new file mode 100644
index 000000000000..810c92f48b52
--- /dev/null
+++ b/dotnet/samples/Concepts/Plugins/MsGraph_CalendarPlugin.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Azure.Identity;
+using Microsoft.Graph;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Plugins.MsGraph;
+using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
+
+namespace Plugins;
+
+///
+/// This example shows how to use Microsoft Graph Plugin
+/// These examples require a valid Microsoft account and delegated/application access for the Microsoft Graph used resources.
+///
+public class MsGraph_CalendarPlugin(ITestOutputHelper output) : BaseTest(output)
+{
+ private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
+
+ /// Shows how to use Microsoft Graph Calendar Plugin with AI Models.
+ [Fact]
+ public async Task UsingWithAIModel()
+ {
+ // Setup the Kernel
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddOpenAIChatClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
+ .Build();
+
+ using var graphClient = GetGraphClient();
+
+ var calendarConnector = new OutlookCalendarConnector(graphClient);
+
+ // Add the plugin to the Kernel
+ var graphPlugin = kernel.Plugins.AddFromObject(new CalendarPlugin(calendarConnector, jsonSerializerOptions: s_options));
+
+ var settings = new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
+
+ string Prompt = $"""
+ 1. Show me the next 10 calendar events I have
+ 2. If I don't have any event named "Semantic Kernel", please create a new event named "Semantic Kernel"
+ starting at {DateTimeOffset.Now.AddHours(1)} with 1 hour of duration.
+ """;
+
+ // Invoke the OneDrive plugin multiple times
+ var result = await kernel.InvokePromptAsync(Prompt, new(settings));
+
+ Console.WriteLine(result);
+ }
+
+ private static GraphServiceClient GetGraphClient()
+ {
+ var credential = new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions()
+ {
+ ClientId = TestConfiguration.MSGraph.ClientId,
+ TenantId = TestConfiguration.MSGraph.TenantId,
+ RedirectUri = TestConfiguration.MSGraph.RedirectUri,
+ });
+
+ return new GraphServiceClient(credential);
+ }
+}
diff --git a/dotnet/samples/Concepts/Plugins/MsGraph_EmailPlugin.cs b/dotnet/samples/Concepts/Plugins/MsGraph_EmailPlugin.cs
new file mode 100644
index 000000000000..1d4b7bd77aa5
--- /dev/null
+++ b/dotnet/samples/Concepts/Plugins/MsGraph_EmailPlugin.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.Identity;
+using Microsoft.Graph;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
+
+namespace Plugins;
+
+///
+/// This example shows how to use Microsoft Graph Plugin
+/// These examples require a valid Microsoft account and delegated/application access for the used resources.
+///
+public class MsGraph_EmailPlugin(ITestOutputHelper output) : BaseTest(output)
+{
+ /// Shows how to use Microsoft Graph Email Plugin with AI Models.
+ [Fact]
+ public async Task EmailPlugin_SendEmailToMyself()
+ {
+ // Setup the Kernel
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddOpenAIChatClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
+ .Build();
+
+ using var graphClient = GetGraphClient();
+
+ var emailConnector = new OutlookMailConnector(graphClient);
+
+ // Add the plugin to the Kernel
+ var graphPlugin = kernel.Plugins.AddFromObject(new Microsoft.SemanticKernel.Plugins.MsGraph.EmailPlugin(emailConnector));
+
+ var settings = new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
+
+ const string Prompt = """
+ Using the tools available, please do the following:
+ 1. Get my email address
+ 2. Send an email to myself with the subject "FYI" and content "This is a very important email"
+ 3. List 10 of my email messages
+ """;
+
+ // Invoke the Graph plugin with a prompt
+ var result = await kernel.InvokePromptAsync(Prompt, new(settings));
+
+ Console.WriteLine(result);
+ }
+
+ private static GraphServiceClient GetGraphClient()
+ {
+ var credential = new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions()
+ {
+ ClientId = TestConfiguration.MSGraph.ClientId,
+ TenantId = TestConfiguration.MSGraph.TenantId,
+ RedirectUri = TestConfiguration.MSGraph.RedirectUri,
+ });
+
+ return new GraphServiceClient(credential);
+ }
+}
diff --git a/dotnet/samples/Concepts/Plugins/MsGraph_OneDrivePlugin.cs b/dotnet/samples/Concepts/Plugins/MsGraph_OneDrivePlugin.cs
new file mode 100644
index 000000000000..7b53158cbabb
--- /dev/null
+++ b/dotnet/samples/Concepts/Plugins/MsGraph_OneDrivePlugin.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.Identity;
+using Microsoft.Graph;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Plugins.MsGraph;
+using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
+
+namespace Plugins;
+
+///
+/// This example shows how to use Microsoft Graph Plugin
+/// These examples require a valid Microsoft account and delegated/application access for the used resources.
+///
+public class MsGraph_OneDrivePlugin(ITestOutputHelper output) : BaseTest(output)
+{
+ /// Shows how to use Microsoft Graph OneDrive Plugin with AI Models.
+ [Fact]
+ public async Task UsingWithAIModel()
+ {
+ // Setup the Kernel
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddOpenAIChatClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
+ .Build();
+
+ using var graphClient = GetGraphClient();
+ var connector = new OneDriveConnector(graphClient);
+
+ // Add the plugin to the Kernel
+ var graphPlugin = kernel.Plugins.AddFromObject(new CloudDrivePlugin(connector));
+
+ var settings = new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
+
+ const string Prompt = """
+ I need you to do the following things with the tools available:
+ 1. Update the current file: "Resources/travelinfo.txt" to my OneDrive into the "Test" folder.
+ 2. Generate a OneDrive Link for sharing the file
+ 3. Summarize for me the contents of the uploaded file
+ 4. Show me the generated shared link.
+ """;
+
+ // Invoke the OneDrive plugin multiple times
+ var result = await kernel.InvokePromptAsync(Prompt, new(settings));
+
+ Console.WriteLine($"Assistant: {result}");
+ }
+
+ private static GraphServiceClient GetGraphClient()
+ {
+ var credential = new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions()
+ {
+ ClientId = TestConfiguration.MSGraph.ClientId,
+ TenantId = TestConfiguration.MSGraph.TenantId,
+ RedirectUri = TestConfiguration.MSGraph.RedirectUri,
+ });
+
+ return new GraphServiceClient(credential);
+ }
+}
diff --git a/dotnet/samples/Concepts/Plugins/MsGraph_OrganizationHierarchyPlugin.cs b/dotnet/samples/Concepts/Plugins/MsGraph_OrganizationHierarchyPlugin.cs
new file mode 100644
index 000000000000..1e6b2f5b0fe7
--- /dev/null
+++ b/dotnet/samples/Concepts/Plugins/MsGraph_OrganizationHierarchyPlugin.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Azure.Identity;
+using Microsoft.Graph;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Plugins.MsGraph;
+using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
+
+namespace Plugins;
+
+///
+/// This example shows how to use Microsoft Graph Plugin
+/// These examples require a valid Microsoft account and delegated/application access for the used resources.
+///
+public class MsGraph_OrganizationHierarchyPlugin(ITestOutputHelper output) : BaseTest(output)
+{
+ private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
+
+ /// Shows how to use Microsoft Graph Organization Hierarchy Plugin with AI Models.
+ [Fact]
+ public async Task UsingWithAIModel()
+ {
+ // Setup the Kernel
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddOpenAIChatClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
+ .Build();
+
+ using var graphClient = GetGraphClient();
+ var connector = new OrganizationHierarchyConnector(graphClient);
+
+ // Add the plugin to the Kernel
+ var graphPlugin = kernel.Plugins.AddFromObject(new OrganizationHierarchyPlugin(connector, s_options));
+
+ var settings = new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
+
+ const string Prompt = "I need you to show my manager details as well as my direct reports using the tools available:";
+
+ // Invoke the OneDrive plugin multiple times
+ var result = await kernel.InvokePromptAsync(Prompt, new(settings));
+
+ Console.WriteLine($"Assistant: {result}");
+ }
+
+ private static GraphServiceClient GetGraphClient()
+ {
+ var credential = new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions()
+ {
+ ClientId = TestConfiguration.MSGraph.ClientId,
+ TenantId = TestConfiguration.MSGraph.TenantId,
+ RedirectUri = TestConfiguration.MSGraph.RedirectUri,
+ });
+
+ return new GraphServiceClient(credential);
+ }
+}
diff --git a/dotnet/samples/Concepts/Plugins/MsGraph_TaskListPlugin.cs b/dotnet/samples/Concepts/Plugins/MsGraph_TaskListPlugin.cs
new file mode 100644
index 000000000000..70ac5ea360d2
--- /dev/null
+++ b/dotnet/samples/Concepts/Plugins/MsGraph_TaskListPlugin.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Azure.Identity;
+using Microsoft.Graph;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Plugins.MsGraph;
+using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
+
+namespace Plugins;
+
+///
+/// This example shows how to use Microsoft Graph Plugin
+/// These examples require a valid Microsoft account and delegated/application access for the used resources.
+///
+public class MsGraph_TaskListPlugin(ITestOutputHelper output) : BaseTest(output)
+{
+ private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
+
+ /// Shows how to use Microsoft Graph To-Do Tasks Plugin with AI Models.
+ [Fact]
+ public async Task UsingWithAIModel()
+ {
+ // Setup the Kernel
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddOpenAIChatClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
+ .Build();
+
+ using var graphClient = GetGraphClient();
+ var connector = new MicrosoftToDoConnector(graphClient);
+
+ // Add the plugin to the Kernel
+ var graphPlugin = kernel.Plugins.AddFromObject(new TaskListPlugin(connector, jsonSerializerOptions: s_options));
+
+ var settings = new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
+
+ const string Prompt = """
+ 1. Show me all the tasks I have
+ 3. If I don't have a task named "Semantic Kernel", please create one
+ """;
+
+ // Invoke the OneDrive plugin multiple times
+ var result = await kernel.InvokePromptAsync(Prompt, new(settings));
+
+ Console.WriteLine($"Assistant: {result}");
+ }
+
+ private static GraphServiceClient GetGraphClient()
+ {
+ var credential = new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions()
+ {
+ ClientId = TestConfiguration.MSGraph.ClientId,
+ TenantId = TestConfiguration.MSGraph.TenantId,
+ RedirectUri = TestConfiguration.MSGraph.RedirectUri,
+ });
+
+ return new GraphServiceClient(credential);
+ }
+}
diff --git a/dotnet/samples/Concepts/README.md b/dotnet/samples/Concepts/README.md
index d58d2d959fda..0facc49d7196 100644
--- a/dotnet/samples/Concepts/README.md
+++ b/dotnet/samples/Concepts/README.md
@@ -185,6 +185,11 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom
- [DescribeAllPluginsAndFunctions](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/DescribeAllPluginsAndFunctions.cs)
- [GroundednessChecks](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/GroundednessChecks.cs)
- [ImportPluginFromGrpc](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/ImportPluginFromGrpc.cs)
+- [MsGraph_CalendarPlugin](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/MsGraph_CalendarPlugin.cs)
+- [MsGraph_EmailPlugin](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/MsGraph_EmailPlugin.cs)
+- [MsGraph_ContactsPlugin](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/MsGraph_ContactsPlugin.cs)
+- [MsGraph_DrivePlugin](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/MsGraph_DrivePlugin.cs)
+- [MsGraph_TasksPlugin](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/MsGraph_TasksPlugin.cs)
- [TransformPlugin](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/TransformPlugin.cs)
- [CopilotAgentBasedPlugins](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CopilotAgentBasedPlugins.cs)
- [WebPlugins](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/WebPlugins.cs)
diff --git a/dotnet/src/Functions/Functions.Prompty/Functions.Prompty.csproj b/dotnet/src/Functions/Functions.Prompty/Functions.Prompty.csproj
index 3b7fb3d4839c..3e59d45c3fb3 100644
--- a/dotnet/src/Functions/Functions.Prompty/Functions.Prompty.csproj
+++ b/dotnet/src/Functions/Functions.Prompty/Functions.Prompty.csproj
@@ -19,6 +19,7 @@
+
\ No newline at end of file
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/CalendarPlugin.cs b/dotnet/src/Plugins/Plugins.MsGraph/CalendarPlugin.cs
index 9b62a1f3cd5c..4999432c4dfb 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/CalendarPlugin.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/CalendarPlugin.cs
@@ -22,6 +22,7 @@ public sealed class CalendarPlugin
{
private readonly ICalendarConnector _connector;
private readonly ILogger _logger;
+ private readonly JsonSerializerOptions? _jsonSerializerOptions;
private static readonly JsonSerializerOptions s_options = new()
{
WriteIndented = false,
@@ -34,10 +35,13 @@ public sealed class CalendarPlugin
///
/// Calendar connector.
/// The to use for logging. If null, no logging will be performed.
- public CalendarPlugin(ICalendarConnector connector, ILoggerFactory? loggerFactory = null)
+ /// The to use for serialization. If null, default options will be used.
+ public CalendarPlugin(ICalendarConnector connector, ILoggerFactory? loggerFactory = null, JsonSerializerOptions? jsonSerializerOptions = null)
{
Ensure.NotNull(connector, nameof(connector));
+ this._jsonSerializerOptions = jsonSerializerOptions ?? s_options;
+
this._connector = connector;
this._logger = loggerFactory?.CreateLogger(typeof(CalendarPlugin)) ?? NullLogger.Instance;
}
@@ -87,13 +91,13 @@ public async Task GetCalendarEventsAsync(
const string SelectString = "start,subject,organizer,location";
- IEnumerable events = await this._connector.GetEventsAsync(
+ IEnumerable? events = await this._connector.GetEventsAsync(
top: maxResults,
skip: skip,
select: SelectString,
cancellationToken
).ConfigureAwait(false);
- return JsonSerializer.Serialize(value: events, options: s_options);
+ return JsonSerializer.Serialize(value: events, options: this._jsonSerializerOptions);
}
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/CloudDrivePlugin.cs b/dotnet/src/Plugins/Plugins.MsGraph/CloudDrivePlugin.cs
index 6c87c2736bb7..de8660092fe4 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/CloudDrivePlugin.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/CloudDrivePlugin.cs
@@ -39,12 +39,18 @@ public CloudDrivePlugin(ICloudDriveConnector connector, ILoggerFactory? loggerFa
/// A cancellation token to observe while waiting for the task to complete.
/// A string containing the file content.
[KernelFunction, Description("Get the contents of a file in a cloud drive.")]
- public async Task GetFileContentAsync(
+ public async Task GetFileContentAsync(
[Description("Path to file")] string filePath,
CancellationToken cancellationToken = default)
{
this._logger.LogDebug("Getting file content for '{0}'", filePath);
- Stream fileContentStream = await this._connector.GetFileContentStreamAsync(filePath, cancellationToken).ConfigureAwait(false);
+ Stream? fileContentStream = await this._connector.GetFileContentStreamAsync(filePath, cancellationToken).ConfigureAwait(false);
+
+ if (fileContentStream is null)
+ {
+ this._logger.LogDebug("File content stream for '{0}' is null", filePath);
+ return null;
+ }
using StreamReader sr = new(fileContentStream);
return await sr.ReadToEndAsync(
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/MicrosoftGraphModelExtensions.cs b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/MicrosoftGraphModelExtensions.cs
index 1c5280a4894f..b55ba8d9b1f7 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/MicrosoftGraphModelExtensions.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/MicrosoftGraphModelExtensions.cs
@@ -2,8 +2,7 @@
using System;
using System.Linq;
-using Microsoft.Graph;
-using Microsoft.Graph.Extensions;
+using Microsoft.Graph.Models;
using Microsoft.SemanticKernel.Plugins.MsGraph.Models;
namespace Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
@@ -16,19 +15,19 @@ internal static class MicrosoftGraphModelExtensions
///
/// Convert a Microsoft Graph message to an email message.
///
- public static Models.EmailMessage ToEmailMessage(this Message graphMessage)
+ public static Models.EmailMessage ToEmailMessage(this Graph.Models.Message graphMessage)
=> new()
{
- BccRecipients = graphMessage.BccRecipients?.Select(r => r.EmailAddress.ToEmailAddress()),
+ BccRecipients = graphMessage.BccRecipients?.Select(r => r.EmailAddress!.ToEmailAddress()),
Body = graphMessage.Body?.Content,
#pragma warning disable CA1307 // Specify StringComparison for clarity
- BodyPreview = graphMessage.BodyPreview.Replace("\u200C", ""), // BodyPreviews are sometimes filled with zero-width non-joiner characters - remove them.
+ BodyPreview = graphMessage.BodyPreview?.Replace("\u200C", ""), // BodyPreviews are sometimes filled with zero-width non-joiner characters - remove them.
#pragma warning restore CA1307
- CcRecipients = graphMessage.CcRecipients?.Select(r => r.EmailAddress.ToEmailAddress()),
+ CcRecipients = graphMessage.CcRecipients?.Select(r => r.EmailAddress!.ToEmailAddress()),
From = graphMessage.From?.EmailAddress?.ToEmailAddress(),
IsRead = graphMessage.IsRead,
ReceivedDateTime = graphMessage.ReceivedDateTime,
- Recipients = graphMessage.ToRecipients?.Select(r => r.EmailAddress.ToEmailAddress()),
+ Recipients = graphMessage.ToRecipients?.Select(r => r.EmailAddress!.ToEmailAddress()),
SentDateTime = graphMessage.SentDateTime,
Subject = graphMessage.Subject
};
@@ -36,7 +35,7 @@ public static Models.EmailMessage ToEmailMessage(this Message graphMessage)
///
/// Convert a Microsoft Graph email address to an email address.
///
- public static Models.EmailAddress ToEmailAddress(this Microsoft.Graph.EmailAddress graphEmailAddress)
+ public static Models.EmailAddress ToEmailAddress(this Microsoft.Graph.Models.EmailAddress graphEmailAddress)
=> new()
{
Address = graphEmailAddress.Address,
@@ -46,25 +45,25 @@ public static Models.EmailAddress ToEmailAddress(this Microsoft.Graph.EmailAddre
///
/// Convert a calendar event to a Microsoft Graph event.
///
- public static Graph.Event ToGraphEvent(this CalendarEvent calendarEvent)
+ public static Graph.Models.Event ToGraphEvent(this CalendarEvent calendarEvent)
=> new()
{
Subject = calendarEvent.Subject,
- Body = new ItemBody { Content = calendarEvent.Content, ContentType = BodyType.Html },
+ Body = new Graph.Models.ItemBody { Content = calendarEvent.Content, ContentType = Microsoft.Graph.Models.BodyType.Html },
Start = calendarEvent.Start.HasValue
- ? DateTimeTimeZone.FromDateTimeOffset(calendarEvent.Start.Value)
- : DateTimeTimeZone.FromDateTime(System.DateTime.Now),
+ ? calendarEvent.Start.Value.ToDateTimeTimeZone()
+ : System.DateTime.Now.ToDateTimeTimeZone(),
End = calendarEvent.End.HasValue
- ? DateTimeTimeZone.FromDateTimeOffset(calendarEvent.End.Value)
- : DateTimeTimeZone.FromDateTime(System.DateTime.Now + TimeSpan.FromHours(1)),
- Location = new Location { DisplayName = calendarEvent.Location },
- Attendees = calendarEvent.Attendees?.Select(a => new Attendee { EmailAddress = new Microsoft.Graph.EmailAddress { Address = a } })
+ ? calendarEvent.End.Value.ToDateTimeTimeZone()
+ : (System.DateTime.Now + TimeSpan.FromHours(1)).ToDateTimeTimeZone(),
+ Location = new Microsoft.Graph.Models.Location { DisplayName = calendarEvent.Location },
+ Attendees = calendarEvent.Attendees?.Select(a => new Microsoft.Graph.Models.Attendee { EmailAddress = new Microsoft.Graph.Models.EmailAddress { Address = a } })?.ToList()
};
///
/// Convert a Microsoft Graph event to a calendar event.
///
- public static Models.CalendarEvent ToCalendarEvent(this Event msGraphEvent)
+ public static Models.CalendarEvent ToCalendarEvent(this Graph.Models.Event msGraphEvent)
=> new()
{
Subject = msGraphEvent.Subject,
@@ -72,6 +71,6 @@ public static Models.CalendarEvent ToCalendarEvent(this Event msGraphEvent)
Start = msGraphEvent.Start?.ToDateTimeOffset(),
End = msGraphEvent.End?.ToDateTimeOffset(),
Location = msGraphEvent.Location?.DisplayName,
- Attendees = msGraphEvent.Attendees?.Select(a => a.EmailAddress.Address)
+ Attendees = msGraphEvent.Attendees?.Where(a => a.EmailAddress?.Address is not null).Select(a => a.EmailAddress!.Address!),
};
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/MicrosoftToDoConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/MicrosoftToDoConnector.cs
index cfba57b21c2c..e738165d11d2 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/MicrosoftToDoConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/MicrosoftToDoConnector.cs
@@ -7,9 +7,10 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
+using Microsoft.Graph.Models;
using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors.Diagnostics;
using Microsoft.SemanticKernel.Plugins.MsGraph.Models;
-using TaskStatus = Microsoft.Graph.TaskStatus;
+using TaskStatus = Microsoft.Graph.Models.TaskStatus;
namespace Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
@@ -35,87 +36,128 @@ public MicrosoftToDoConnector(GraphServiceClient graphServiceClient)
// .Filter("wellknownListName eq 'defaultList'") does not work as expected so we grab all the lists locally and filter them by name.
// GH issue: https://github.com/microsoftgraph/microsoft-graph-docs/issues/17694
- ITodoListsCollectionPage lists = await this._graphServiceClient.Me
- .Todo.Lists
- .Request().GetAsync(cancellationToken).ConfigureAwait(false);
+ // Get the initial page (response won't be null if successful; exceptions are thrown on failure)
+ var initialPage = await this._graphServiceClient.Me.Todo.Lists.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
- TodoTaskList? result = lists.SingleOrDefault(list => list.WellknownListName == WellknownListName.DefaultList);
+ TodoTaskList? result = null;
- while (result is null && lists.Count != 0 && lists.NextPageRequest is not null)
+ if (initialPage is null)
{
- lists = await lists.NextPageRequest.GetAsync(cancellationToken).ConfigureAwait(false);
- result = lists.SingleOrDefault(list => list.WellknownListName == WellknownListName.DefaultList);
+ return null;
}
+ var pageIterator = PageIterator.CreatePageIterator(
+ this._graphServiceClient,
+ initialPage,
+ (list) =>
+ {
+ if (list?.WellknownListName == WellknownListName.DefaultList)
+ {
+ result = list;
+ return false; // Stop iterating once found
+ }
+ return true; // Continue to next item/page
+ });
+
+ await pageIterator.IterateAsync(cancellationToken).ConfigureAwait(false);
+
if (result is null)
{
- throw new KernelException("Could not find default task list.");
+ return null; // No default list found
}
- return new TaskManagementTaskList(result.Id, result.DisplayName);
- }
+ if (string.IsNullOrEmpty(result.Id))
+ {
+ return null; // Ensure the ID is not null or empty
+ }
+ return new TaskManagementTaskList(
+ result.Id, // We've checked it's not null/empty
+ result.DisplayName ?? "Unnamed Default List" // Coalesce to a fallback if null
+ );
+ }
///
- public async Task> GetTaskListsAsync(CancellationToken cancellationToken = default)
+ public async Task?> GetTaskListsAsync(CancellationToken cancellationToken = default)
{
- ITodoListsCollectionPage lists = await this._graphServiceClient.Me
- .Todo.Lists
- .Request().GetAsync(cancellationToken).ConfigureAwait(false);
-
- List taskLists = [.. lists];
+ // Get the initial page (response won't be null if successful; exceptions thrown on failure)
+ var response = await this._graphServiceClient.Me.Todo.Lists
+ .GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
- while (lists.Count != 0 && lists.NextPageRequest is not null)
+ if (response?.Value == null)
{
- lists = await lists.NextPageRequest.GetAsync(cancellationToken).ConfigureAwait(false);
- taskLists.AddRange(lists);
+ return null;
}
- return taskLists.Select(list => new TaskManagementTaskList(
- id: list.Id,
- name: list.DisplayName));
+ List? taskLists = null;
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ this._graphServiceClient,
+ response,
+ (list) =>
+ {
+ (taskLists = []).Add(list);
+ return true; // Continue to fetch all pages
+ });
+
+ await pageIterator.IterateAsync(cancellationToken).ConfigureAwait(false);
+
+ return taskLists?.Select(list => new TaskManagementTaskList(
+ id: list?.Id,
+ name: list?.DisplayName));
}
///
- public async Task> GetTasksAsync(string listId, bool includeCompleted, CancellationToken cancellationToken = default)
+ public async Task?> GetTasksAsync(string listId, bool includeCompleted, CancellationToken cancellationToken = default)
{
Ensure.NotNullOrWhitespace(listId, nameof(listId));
- string filterValue = string.Empty;
- if (!includeCompleted)
+ // Get the initial page with optional filter
+ var response = await this._graphServiceClient.Me.Todo.Lists[listId].Tasks
+ .GetAsync(requestConfig =>
+ {
+ if (!includeCompleted)
+ {
+ requestConfig.QueryParameters.Filter = "status ne 'completed'";
+ }
+ }, cancellationToken).ConfigureAwait(false);
+
+ if (response?.Value == null)
{
- filterValue = "status ne 'completed'";
+ return Enumerable.Empty();
}
- ITodoTaskListTasksCollectionPage tasksPage = await this._graphServiceClient.Me
- .Todo.Lists[listId]
- .Tasks.Request().Filter(filterValue).GetAsync(cancellationToken).ConfigureAwait(false);
-
- List tasks = [.. tasksPage];
-
- while (tasksPage.Count != 0 && tasksPage.NextPageRequest is not null)
- {
- tasksPage = await tasksPage.NextPageRequest.GetAsync(cancellationToken).ConfigureAwait(false);
- tasks.AddRange(tasksPage);
- }
-
- return tasks.Select(task => new TaskManagementTask(
- id: task.Id,
- title: task.Title,
- reminder: task.ReminderDateTime?.DateTime,
- due: task.DueDateTime?.DateTime,
- isCompleted: task.Status == TaskStatus.Completed));
+ List? tasks = null;
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ this._graphServiceClient,
+ response,
+ (task) =>
+ {
+ (tasks = []).Add(task);
+ return true; // Continue to fetch all pages
+ });
+
+ await pageIterator.IterateAsync(cancellationToken).ConfigureAwait(false);
+
+ return tasks?.Select(task => new TaskManagementTask(
+ id: task?.Id,
+ title: task?.Title,
+ reminder: task?.ReminderDateTime?.DateTime,
+ due: task?.DueDateTime?.DateTime,
+ isCompleted: task?.Status == TaskStatus.Completed));
}
///
- public async Task AddTaskAsync(string listId, TaskManagementTask task, CancellationToken cancellationToken = default)
+ public async Task AddTaskAsync(string listId, TaskManagementTask task, CancellationToken cancellationToken = default)
{
Ensure.NotNullOrWhitespace(listId, nameof(listId));
Ensure.NotNull(task, nameof(task));
- return ToTaskListTask(await this._graphServiceClient.Me
- .Todo.Lists[listId]
- .Tasks
- .Request().AddAsync(FromTaskListTask(task), cancellationToken).ConfigureAwait(false));
+ var createdTask = await this._graphServiceClient.Me.Todo.Lists[listId].Tasks
+ .PostAsync(FromTaskListTask(task), cancellationToken: cancellationToken)
+ .ConfigureAwait(false);
+
+ return createdTask != null ? ToTaskListTask(createdTask) : null;
}
///
@@ -126,8 +168,7 @@ public Task DeleteTaskAsync(string listId, string taskId, CancellationToken canc
return this._graphServiceClient.Me
.Todo.Lists[listId]
- .Tasks[taskId]
- .Request().DeleteAsync(cancellationToken);
+ .Tasks[taskId].DeleteAsync(cancellationToken: cancellationToken);
}
private static TodoTask FromTaskListTask(TaskManagementTask task)
@@ -139,10 +180,10 @@ private static TodoTask FromTaskListTask(TaskManagementTask task)
Title = task.Title,
ReminderDateTime = task.Reminder is null
? null
- : DateTimeTimeZone.FromDateTimeOffset(DateTimeOffset.Parse(task.Reminder, CultureInfo.InvariantCulture.DateTimeFormat)),
+ : DateTimeOffset.Parse(task.Reminder, CultureInfo.InvariantCulture.DateTimeFormat).ToDateTimeTimeZone(),
DueDateTime = task.Due is null
? null
- : DateTimeTimeZone.FromDateTimeOffset(DateTimeOffset.Parse(task.Due, CultureInfo.InvariantCulture.DateTimeFormat)),
+ : DateTimeOffset.Parse(task.Due, CultureInfo.InvariantCulture.DateTimeFormat).ToDateTimeTimeZone(),
Status = task.IsCompleted ? TaskStatus.Completed : TaskStatus.NotStarted
};
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OneDriveConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OneDriveConnector.cs
index ff2b541807e1..78c93e472665 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OneDriveConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OneDriveConnector.cs
@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
+using Microsoft.Graph.Models;
using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors.Diagnostics;
namespace Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
@@ -28,14 +29,16 @@ public OneDriveConnector(GraphServiceClient graphServiceClient)
}
///
- public async Task GetFileContentStreamAsync(string filePath, CancellationToken cancellationToken = default)
+ public async Task GetFileContentStreamAsync(string filePath, CancellationToken cancellationToken = default)
{
Ensure.NotNullOrWhitespace(filePath, nameof(filePath));
- return await this._graphServiceClient.Me
- .Drive.Root
- .ItemWithPath(filePath).Content
- .Request().GetAsync(cancellationToken).ConfigureAwait(false);
+ var myDrive = await this._graphServiceClient.Me.Drive.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ return await this._graphServiceClient
+ .Drives[myDrive!.Id].Root.ItemWithPath(filePath).Content
+ .GetAsync(cancellationToken: cancellationToken)
+ .ConfigureAwait(false);
}
///
@@ -50,9 +53,10 @@ public async Task FileExistsAsync(string filePath, CancellationToken cance
try
{
- await this._graphServiceClient.Me
- .Drive.Root
- .ItemWithPath(filePath).Request().GetAsync(cancellationToken).ConfigureAwait(false);
+ var myDrive = await this._graphServiceClient.Me.Drive.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ await this._graphServiceClient
+ .Drives[myDrive!.Id].Root.ItemWithPath(filePath).GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
// If no exception is thrown, the file exists.
return true;
@@ -60,12 +64,12 @@ await this._graphServiceClient.Me
catch (ServiceException ex)
{
// If the exception is a 404 Not Found, the file does not exist.
- if (ex.StatusCode == HttpStatusCode.NotFound)
+ if (ex.ResponseStatusCode == (int)HttpStatusCode.NotFound)
{
return false;
}
- throw new HttpOperationException(ex.StatusCode, responseContent: null, ex.Message, ex);
+ throw new HttpOperationException((HttpStatusCode)ex.ResponseStatusCode, responseContent: null, ex.Message, ex);
}
}
@@ -85,24 +89,27 @@ public async Task UploadSmallFileAsync(string filePath, string destinationPath,
using FileStream fileContentStream = new(filePath, FileMode.Open, FileAccess.Read);
- GraphResponse? response = null;
+ DriveItem? response = null;
try
{
- response = await this._graphServiceClient.Me
- .Drive.Root
- .ItemWithPath(destinationPath).Content
- .Request().PutResponseAsync(fileContentStream, cancellationToken, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false);
+ var myDrive = await this._graphServiceClient.Me.Drive.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
- response.ToHttpResponseMessage().EnsureSuccessStatusCode();
+ response = await this._graphServiceClient
+ .Drives[myDrive!.Id].Root
+ .ItemWithPath(destinationPath).Content.PutAsync(fileContentStream, cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch (ServiceException ex)
{
- throw new HttpOperationException(ex.StatusCode, responseContent: null, ex.Message, ex);
+ throw new HttpOperationException((HttpStatusCode)ex.ResponseStatusCode, responseContent: null, ex.Message, ex);
}
catch (HttpRequestException ex)
{
- throw new HttpOperationException(response?.StatusCode, responseContent: null, ex.Message, ex);
+#if NET8_0_OR_GREATER
+ throw new HttpOperationException(ex.StatusCode, responseContent: null, ex.Message, ex);
+#else
+ throw new HttpOperationException(null, responseContent: null, ex.Message, ex);
+#endif
}
}
@@ -114,28 +121,32 @@ public async Task CreateShareLinkAsync(string filePath, string type = "v
Ensure.NotNullOrWhitespace(type, nameof(type));
Ensure.NotNullOrWhitespace(scope, nameof(scope));
- GraphResponse? response = null;
+ Permission? response = null;
try
{
- response = await this._graphServiceClient.Me
- .Drive.Root
- .ItemWithPath(filePath)
- .CreateLink(type, scope)
- .Request().PostResponseAsync(cancellationToken).ConfigureAwait(false);
+ var myDrive = await this._graphServiceClient.Me.Drive.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
- response.ToHttpResponseMessage().EnsureSuccessStatusCode();
+ response = await this._graphServiceClient
+ .Drives[myDrive!.Id].Root
+ .ItemWithPath(filePath)
+ .CreateLink.PostAsync(new() { Type = type, Scope = scope }, cancellationToken: cancellationToken)
+ .ConfigureAwait(false);
}
catch (ServiceException ex)
{
- throw new HttpOperationException(ex.StatusCode, responseContent: null, ex.Message, ex);
+ throw new HttpOperationException((HttpStatusCode)ex.ResponseStatusCode, responseContent: null, ex.Message, ex);
}
catch (HttpRequestException ex)
{
- throw new HttpOperationException(response?.StatusCode, responseContent: null, ex.Message, ex);
+#if NET8_0_OR_GREATER
+ throw new HttpOperationException(ex.StatusCode, responseContent: null, ex.Message, ex);
+#else
+ throw new HttpOperationException(null, responseContent: null, ex.Message, ex);
+#endif
}
- string? result = (await response.GetResponseObjectAsync().ConfigureAwait(false)).Link?.WebUrl;
+ string? result = response?.Link?.WebUrl;
if (string.IsNullOrWhiteSpace(result))
{
throw new KernelException("Shareable file link was null or whitespace.");
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OrganizationHierarchyConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OrganizationHierarchyConnector.cs
index 04893f4cf9ba..b28d4448c32c 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OrganizationHierarchyConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OrganizationHierarchyConnector.cs
@@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
+using Microsoft.Graph.Models;
namespace Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
@@ -25,32 +26,32 @@ public OrganizationHierarchyConnector(GraphServiceClient graphServiceClient)
}
///
- public async Task GetManagerEmailAsync(CancellationToken cancellationToken = default) =>
- ((User)await this._graphServiceClient.Me
- .Manager
- .Request().GetAsync(cancellationToken).ConfigureAwait(false)).UserPrincipalName;
+ public async Task GetManagerEmailAsync(CancellationToken cancellationToken = default) =>
+ ((User?)await this._graphServiceClient.Me
+ .Manager.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false))?.UserPrincipalName;
///
- public async Task GetManagerNameAsync(CancellationToken cancellationToken = default) =>
- ((User)await this._graphServiceClient.Me
- .Manager
- .Request().GetAsync(cancellationToken).ConfigureAwait(false)).DisplayName;
+ public async Task GetManagerNameAsync(CancellationToken cancellationToken = default) =>
+ ((User?)await this._graphServiceClient.Me
+ .Manager.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false))?.DisplayName;
///
- public async Task> GetDirectReportsEmailAsync(CancellationToken cancellationToken = default)
+ public async Task?> GetDirectReportsEmailAsync(CancellationToken cancellationToken = default)
{
- IUserDirectReportsCollectionWithReferencesPage directsPage = await this._graphServiceClient.Me
- .DirectReports
- .Request().GetAsync(cancellationToken).ConfigureAwait(false);
+ DirectoryObjectCollectionResponse? directsPage = await this._graphServiceClient.Me
+ .DirectReports.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
- List directs = directsPage.Cast().ToList();
+ List? directs = directsPage?.Value?.Cast().ToList();
- while (directs.Count != 0 && directsPage.NextPageRequest is not null)
+ while (directs is { Count: > 0 } && directsPage!.OdataNextLink is not null)
{
- directsPage = await directsPage.NextPageRequest.GetAsync(cancellationToken).ConfigureAwait(false);
- directs.AddRange(directsPage.Cast());
+ directsPage = await this._graphServiceClient.Me.DirectReports.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+ if (directsPage?.Value is not null)
+ {
+ directs.AddRange(directsPage!.Value.Cast());
+ }
}
- return directs.Select(d => d.UserPrincipalName);
+ return directs?.Where(d => d.UserPrincipalName is not null)?.Select(d => d.UserPrincipalName!);
}
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OutlookCalendarConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OutlookCalendarConnector.cs
index bc856c8bbd4b..8fb4da2c50f7 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OutlookCalendarConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OutlookCalendarConnector.cs
@@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
+using Microsoft.Graph.Models;
using Microsoft.SemanticKernel.Plugins.MsGraph.Models;
namespace Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
@@ -26,37 +27,26 @@ public OutlookCalendarConnector(GraphServiceClient graphServiceClient)
}
///
- public async Task AddEventAsync(CalendarEvent calendarEvent, CancellationToken cancellationToken = default)
+ public async Task AddEventAsync(CalendarEvent calendarEvent, CancellationToken cancellationToken = default)
{
- Event resultEvent = await this._graphServiceClient.Me.Events.Request()
- .AddAsync(calendarEvent.ToGraphEvent(), cancellationToken).ConfigureAwait(false);
- return resultEvent.ToCalendarEvent();
+ Event? resultEvent = await this._graphServiceClient.Me.Events
+ .PostAsync(calendarEvent.ToGraphEvent(), cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ return resultEvent?.ToCalendarEvent();
}
///
- public async Task> GetEventsAsync(
+ public async Task?> GetEventsAsync(
int? top, int? skip, string? select, CancellationToken cancellationToken = default)
{
- ICalendarEventsCollectionRequest query = this._graphServiceClient.Me.Calendar.Events.Request();
-
- if (top.HasValue)
- {
- query.Top(top.Value);
- }
-
- if (skip.HasValue)
+ var result = await this._graphServiceClient.Me.Calendar.Events.GetAsync(config =>
{
- query.Skip(skip.Value);
- }
-
- if (!string.IsNullOrEmpty(select))
- {
- query.Select(select);
- }
-
- ICalendarEventsCollectionPage result = await query.GetAsync(cancellationToken).ConfigureAwait(false);
+ config.QueryParameters.Top = top;
+ config.QueryParameters.Skip = skip;
+ config.QueryParameters.Select = !string.IsNullOrEmpty(select) ? [select] : null;
+ }, cancellationToken: cancellationToken).ConfigureAwait(false);
- IEnumerable events = result.Select(e => e.ToCalendarEvent());
+ IEnumerable? events = result?.Value?.Select(e => e.ToCalendarEvent());
return events;
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OutlookMailConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OutlookMailConnector.cs
index 78c484910bff..6428c08b7d14 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OutlookMailConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/Connectors/OutlookMailConnector.cs
@@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
+using Microsoft.Graph.Models;
using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors.Diagnostics;
using Microsoft.SemanticKernel.Plugins.MsGraph.Models;
@@ -27,8 +28,8 @@ public OutlookMailConnector(GraphServiceClient graphServiceClient)
}
///
- public async Task GetMyEmailAddressAsync(CancellationToken cancellationToken = default)
- => (await this._graphServiceClient.Me.Request().GetAsync(cancellationToken).ConfigureAwait(false)).UserPrincipalName;
+ public async Task GetMyEmailAddressAsync(CancellationToken cancellationToken = default)
+ => (await this._graphServiceClient.Me.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false))?.UserPrincipalName;
///
public async Task SendEmailAsync(string subject, string content, string[] recipients, CancellationToken cancellationToken = default)
@@ -47,36 +48,24 @@ public async Task SendEmailAsync(string subject, string content, string[] recipi
{
Address = recipientAddress
}
- })
+ }).ToList()
};
- await this._graphServiceClient.Me.SendMail(message).Request().PostAsync(cancellationToken).ConfigureAwait(false);
+ await this._graphServiceClient.Me.SendMail.PostAsync(new() { Message = message }, cancellationToken: cancellationToken).ConfigureAwait(false);
}
///
- public async Task> GetMessagesAsync(
+ public async Task?> GetMessagesAsync(
int? top, int? skip, string? select, CancellationToken cancellationToken = default)
{
- IUserMessagesCollectionRequest query = this._graphServiceClient.Me.Messages.Request();
-
- if (top.HasValue)
- {
- query.Top(top.Value);
- }
-
- if (skip.HasValue)
+ var result = await this._graphServiceClient.Me.Messages.GetAsync((config) =>
{
- query.Skip(skip.Value);
- }
-
- if (!string.IsNullOrEmpty(select))
- {
- query.Select(select);
- }
-
- IUserMessagesCollectionPage result = await query.GetAsync(cancellationToken).ConfigureAwait(false);
+ config.QueryParameters.Top = top;
+ config.QueryParameters.Skip = skip;
+ config.QueryParameters.Select = !string.IsNullOrEmpty(select) ? [select] : null;
+ }, cancellationToken: cancellationToken).ConfigureAwait(false);
- IEnumerable messages = result.Select(m => m.ToEmailMessage());
+ IEnumerable? messages = result?.Value?.Select(m => m.ToEmailMessage());
return messages;
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/EmailPlugin.cs b/dotnet/src/Plugins/Plugins.MsGraph/EmailPlugin.cs
index d4aefd72d64b..bda0d312aa9a 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/EmailPlugin.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/EmailPlugin.cs
@@ -21,6 +21,7 @@ public sealed class EmailPlugin
{
private readonly IEmailConnector _connector;
private readonly ILogger _logger;
+ private readonly JsonSerializerOptions? _jsonSerializerOptions;
private static readonly JsonSerializerOptions s_options = new()
{
WriteIndented = false,
@@ -33,10 +34,12 @@ public sealed class EmailPlugin
///
/// Email connector.
/// The to use for logging. If null, no logging will be performed.
- public EmailPlugin(IEmailConnector connector, ILoggerFactory? loggerFactory = null)
+ /// The to use for serialization. If null, default options will be used.
+ public EmailPlugin(IEmailConnector connector, ILoggerFactory? loggerFactory = null, JsonSerializerOptions? jsonSerializerOptions = null)
{
Ensure.NotNull(connector, nameof(connector));
+ this._jsonSerializerOptions = jsonSerializerOptions ?? s_options;
this._connector = connector;
this._logger = loggerFactory?.CreateLogger(typeof(EmailPlugin)) ?? NullLogger.Instance;
}
@@ -45,7 +48,7 @@ public EmailPlugin(IEmailConnector connector, ILoggerFactory? loggerFactory = nu
/// Get my email address.
///
[KernelFunction, Description("Gets the email address for me.")]
- public async Task GetMyEmailAddressAsync()
+ public async Task GetMyEmailAddressAsync()
=> await this._connector.GetMyEmailAddressAsync().ConfigureAwait(false);
///
@@ -78,7 +81,7 @@ public async Task SendEmailAsync(
/// Get email messages with specified optional clauses used to query for messages.
///
[KernelFunction, Description("Get email messages.")]
- public async Task GetEmailMessagesAsync(
+ public async Task GetEmailMessagesAsync(
[Description("Optional limit of the number of message to retrieve.")] int? maxResults = 10,
[Description("Optional number of message to skip before retrieving results.")] int? skip = 0,
CancellationToken cancellationToken = default)
@@ -87,13 +90,18 @@ public async Task GetEmailMessagesAsync(
const string SelectString = "subject,receivedDateTime,bodyPreview";
- IEnumerable messages = await this._connector.GetMessagesAsync(
+ IEnumerable? messages = await this._connector.GetMessagesAsync(
top: maxResults,
skip: skip,
select: SelectString,
cancellationToken)
.ConfigureAwait(false);
- return JsonSerializer.Serialize(value: messages, options: s_options);
+ if (messages is null)
+ {
+ return null;
+ }
+
+ return JsonSerializer.Serialize(value: messages, options: this._jsonSerializerOptions);
}
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/ICalendarConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/ICalendarConnector.cs
index e12f87cfda79..52fd2ec7d1b4 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/ICalendarConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/ICalendarConnector.cs
@@ -18,7 +18,7 @@ public interface ICalendarConnector
/// Event to add.
/// The to monitor for cancellation requests. The default is .
/// Event that was added.
- Task AddEventAsync(CalendarEvent calendarEvent, CancellationToken cancellationToken = default);
+ Task AddEventAsync(CalendarEvent calendarEvent, CancellationToken cancellationToken = default);
///
/// Get the user's calendar events.
@@ -29,6 +29,6 @@ public interface ICalendarConnector
/// Cancellation token
/// The user's calendar events.
#pragma warning disable CA1716 // Identifiers should not match keywords
- Task> GetEventsAsync(int? top, int? skip, string? @select, CancellationToken cancellationToken = default);
+ Task?> GetEventsAsync(int? top, int? skip, string? @select, CancellationToken cancellationToken = default);
#pragma warning restore CA1716 // Identifiers should not match keywords
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/ICloudDriveConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/ICloudDriveConnector.cs
index f13fa7240c57..a54d46464ae1 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/ICloudDriveConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/ICloudDriveConnector.cs
@@ -26,7 +26,7 @@ public interface ICloudDriveConnector
///
/// Path to the remote file.
/// The to monitor for cancellation requests. The default is .
- Task GetFileContentStreamAsync(string filePath, CancellationToken cancellationToken = default);
+ Task GetFileContentStreamAsync(string filePath, CancellationToken cancellationToken = default);
///
/// Upload a small file (less than 4MB).
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/IEmailConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/IEmailConnector.cs
index 8faf50f973e2..8b25b2b21123 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/IEmailConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/IEmailConnector.cs
@@ -17,7 +17,7 @@ public interface IEmailConnector
///
/// The to monitor for cancellation requests. The default is .
/// The user's email address.
- Task GetMyEmailAddressAsync(CancellationToken cancellationToken = default);
+ Task GetMyEmailAddressAsync(CancellationToken cancellationToken = default);
///
/// Send an email to the specified recipients.
@@ -37,6 +37,6 @@ public interface IEmailConnector
/// Cancellation token
/// The user's email messages.
#pragma warning disable CA1716 // Identifiers should not match keywords
- Task> GetMessagesAsync(int? top, int? skip, string? @select, CancellationToken cancellationToken = default);
+ Task?> GetMessagesAsync(int? top, int? skip, string? @select, CancellationToken cancellationToken = default);
#pragma warning restore CA1716 // Identifiers should not match keywords
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/IOrganizationHierarchyConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/IOrganizationHierarchyConnector.cs
index 543cbf57cad6..48aa3661df27 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/IOrganizationHierarchyConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/IOrganizationHierarchyConnector.cs
@@ -16,19 +16,19 @@ public interface IOrganizationHierarchyConnector
///
/// The to monitor for cancellation requests. The default is .
/// The user's direct reports' email addresses.
- Task> GetDirectReportsEmailAsync(CancellationToken cancellationToken = default);
+ Task?> GetDirectReportsEmailAsync(CancellationToken cancellationToken = default);
///
/// Get the user's manager's email address.
///
/// The to monitor for cancellation requests. The default is .
/// The user's manager's email address.
- Task GetManagerEmailAsync(CancellationToken cancellationToken = default);
+ Task GetManagerEmailAsync(CancellationToken cancellationToken = default);
///
/// Get the user's manager's name.
///
/// The to monitor for cancellation requests. The default is .
/// The user's manager's name.
- Task GetManagerNameAsync(CancellationToken cancellationToken = default);
+ Task GetManagerNameAsync(CancellationToken cancellationToken = default);
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/ITaskManagementConnector.cs b/dotnet/src/Plugins/Plugins.MsGraph/ITaskManagementConnector.cs
index 56c6cf7d99a3..b1897aae1af9 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/ITaskManagementConnector.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/ITaskManagementConnector.cs
@@ -19,7 +19,7 @@ public interface ITaskManagementConnector
/// Task to add.
/// The to monitor for cancellation requests. The default is .
/// Added task definition.
- Task AddTaskAsync(string listId, TaskManagementTask task, CancellationToken cancellationToken = default);
+ Task AddTaskAsync(string listId, TaskManagementTask task, CancellationToken cancellationToken = default);
///
/// Delete a task from a task list.
@@ -40,7 +40,7 @@ public interface ITaskManagementConnector
///
/// The to monitor for cancellation requests. The default is .
/// All of the user's task lists.
- Task> GetTaskListsAsync(CancellationToken cancellationToken = default);
+ Task?> GetTaskListsAsync(CancellationToken cancellationToken = default);
///
/// Get the all tasks in a task list.
@@ -49,5 +49,5 @@ public interface ITaskManagementConnector
/// Whether to include completed tasks.
/// The to monitor for cancellation requests. The default is .
/// All of the tasks in the specified task list.
- Task> GetTasksAsync(string listId, bool includeCompleted, CancellationToken cancellationToken = default);
+ Task?> GetTasksAsync(string listId, bool includeCompleted, CancellationToken cancellationToken = default);
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/Models/TaskManagementTask.cs b/dotnet/src/Plugins/Plugins.MsGraph/Models/TaskManagementTask.cs
index 1cdb5e79317b..1a5eb8bb52cd 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/Models/TaskManagementTask.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/Models/TaskManagementTask.cs
@@ -10,12 +10,12 @@ public class TaskManagementTask
///
/// ID of the task.
///
- public string Id { get; set; }
+ public string? Id { get; set; }
///
/// Title of the task.
///
- public string Title { get; set; }
+ public string? Title { get; set; }
///
/// Reminder date/time for the task.
@@ -40,7 +40,7 @@ public class TaskManagementTask
/// Reminder date/time for the task.
/// Task's due date/time.
/// True if the task is completed, otherwise false.
- public TaskManagementTask(string id, string title, string? reminder = null, string? due = null, bool isCompleted = false)
+ public TaskManagementTask(string? id, string? title, string? reminder = null, string? due = null, bool isCompleted = false)
{
this.Id = id;
this.Title = title;
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/Models/TaskManagementTaskList.cs b/dotnet/src/Plugins/Plugins.MsGraph/Models/TaskManagementTaskList.cs
index e8cea6d8ead8..fb529eb32946 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/Models/TaskManagementTaskList.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/Models/TaskManagementTaskList.cs
@@ -10,19 +10,19 @@ public class TaskManagementTaskList
///
/// ID of the task list.
///
- public string Id { get; set; }
+ public string? Id { get; set; }
///
/// Name of the task list.
///
- public string Name { get; set; }
+ public string? Name { get; set; }
///
/// Initializes a new instance of the class.
///
/// ID of the task list.
/// Name of the task list.
- public TaskManagementTaskList(string id, string name)
+ public TaskManagementTaskList(string? id, string? name)
{
this.Id = id;
this.Name = name;
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/OrganizationHierarchyPlugin.cs b/dotnet/src/Plugins/Plugins.MsGraph/OrganizationHierarchyPlugin.cs
index a38274d3bd29..05133c9369f6 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/OrganizationHierarchyPlugin.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/OrganizationHierarchyPlugin.cs
@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Plugins.MsGraph.Diagnostics;
@@ -15,15 +16,23 @@ namespace Microsoft.SemanticKernel.Plugins.MsGraph;
public sealed class OrganizationHierarchyPlugin
{
private readonly IOrganizationHierarchyConnector _connector;
+ private readonly JsonSerializerOptions? _jsonSerializerOptions;
+ private static readonly JsonSerializerOptions s_options = new()
+ {
+ WriteIndented = false,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ };
///
/// Initializes a new instance of the class.
///
/// The connector to be used for fetching organization hierarchy data.
- public OrganizationHierarchyPlugin(IOrganizationHierarchyConnector connector)
+ /// The to use for serialization. If null, default options will be used.
+ public OrganizationHierarchyPlugin(IOrganizationHierarchyConnector connector, JsonSerializerOptions? jsonSerializerOptions = null)
{
Ensure.NotNull(connector, nameof(connector));
+ this._jsonSerializerOptions = jsonSerializerOptions ?? s_options;
this._connector = connector;
}
@@ -34,7 +43,7 @@ public OrganizationHierarchyPlugin(IOrganizationHierarchyConnector connector)
/// A JSON string containing the email addresses of the direct reports of the current user.
[KernelFunction, Description("Get my direct report's email addresses.")]
public async Task GetMyDirectReportsEmailAsync(CancellationToken cancellationToken = default)
- => JsonSerializer.Serialize(await this._connector.GetDirectReportsEmailAsync(cancellationToken).ConfigureAwait(false));
+ => JsonSerializer.Serialize(await this._connector.GetDirectReportsEmailAsync(cancellationToken).ConfigureAwait(false), this._jsonSerializerOptions);
///
/// Get the email of the manager of the current user.
@@ -42,7 +51,7 @@ public async Task GetMyDirectReportsEmailAsync(CancellationToken cancell
/// An optional to observe while waiting for the task to complete.
/// A string containing the email address of the manager of the current user.
[KernelFunction, Description("Get my manager's email address.")]
- public async Task GetMyManagerEmailAsync(CancellationToken cancellationToken = default)
+ public async Task GetMyManagerEmailAsync(CancellationToken cancellationToken = default)
=> await this._connector.GetManagerEmailAsync(cancellationToken).ConfigureAwait(false);
///
@@ -51,6 +60,6 @@ public async Task GetMyManagerEmailAsync(CancellationToken cancellationT
/// An optional to observe while waiting for the task to complete.
/// A string containing the name of the manager of the current user.
[KernelFunction, Description("Get my manager's name.")]
- public async Task GetMyManagerNameAsync(CancellationToken cancellationToken = default)
+ public async Task GetMyManagerNameAsync(CancellationToken cancellationToken = default)
=> await this._connector.GetManagerNameAsync(cancellationToken).ConfigureAwait(false);
}
diff --git a/dotnet/src/Plugins/Plugins.MsGraph/TaskListPlugin.cs b/dotnet/src/Plugins/Plugins.MsGraph/TaskListPlugin.cs
index 6c0649721090..947f43220804 100644
--- a/dotnet/src/Plugins/Plugins.MsGraph/TaskListPlugin.cs
+++ b/dotnet/src/Plugins/Plugins.MsGraph/TaskListPlugin.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -20,16 +21,24 @@ public sealed class TaskListPlugin
{
private readonly ITaskManagementConnector _connector;
private readonly ILogger _logger;
+ private readonly JsonSerializerOptions? _jsonSerializerOptions;
+ private static readonly JsonSerializerOptions s_options = new()
+ {
+ WriteIndented = false,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ };
///
/// Initializes a new instance of the class.
///
/// Task list connector.
/// The to use for logging. If null, no logging will be performed.
- public TaskListPlugin(ITaskManagementConnector connector, ILoggerFactory? loggerFactory = null)
+ /// The to use for serialization. If null, default options will be used.
+ public TaskListPlugin(ITaskManagementConnector connector, ILoggerFactory? loggerFactory = null, JsonSerializerOptions? jsonSerializerOptions = null)
{
Ensure.NotNull(connector, nameof(connector));
+ this._jsonSerializerOptions = jsonSerializerOptions ?? s_options;
this._connector = connector;
this._logger = loggerFactory?.CreateLogger(typeof(TaskListPlugin)) ?? NullLogger.Instance;
}
@@ -72,14 +81,14 @@ public async Task AddTaskAsync(
// Sensitive data, logging as trace, disabled by default
this._logger.LogTrace("Adding task '{0}' to task list '{1}'", task.Title, defaultTaskList.Name);
- await this._connector.AddTaskAsync(defaultTaskList.Id, task, cancellationToken).ConfigureAwait(false);
+ await this._connector.AddTaskAsync(defaultTaskList.Id!, task, cancellationToken).ConfigureAwait(false);
}
///
/// Get tasks from the default task list.
///
[KernelFunction, Description("Get tasks from the default task list.")]
- public async Task GetDefaultTasksAsync(
+ public async Task GetDefaultTasksAsync(
[Description("Whether to include completed tasks (optional)")] string includeCompleted = "false",
CancellationToken cancellationToken = default)
{
@@ -91,7 +100,7 @@ public async Task GetDefaultTasksAsync(
this._logger.LogWarning("Invalid value for '{0}' variable: '{1}'", nameof(includeCompleted), includeCompleted);
}
- IEnumerable tasks = await this._connector.GetTasksAsync(defaultTaskList.Id, includeCompletedValue, cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Serialize(tasks);
+ IEnumerable? tasks = await this._connector.GetTasksAsync(defaultTaskList.Id!, includeCompletedValue, cancellationToken).ConfigureAwait(false);
+ return JsonSerializer.Serialize(tasks, s_options);
}
}
diff --git a/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/CloudDrivePluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/CloudDrivePluginTests.cs
index 389c72663239..ee15a1a92725 100644
--- a/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/CloudDrivePluginTests.cs
+++ b/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/CloudDrivePluginTests.cs
@@ -68,7 +68,7 @@ public async Task GetFileContentAsyncSucceedsAsync()
CloudDrivePlugin target = new(connectorMock.Object);
// Act
- string actual = await target.GetFileContentAsync(anyFilePath);
+ string? actual = await target.GetFileContentAsync(anyFilePath);
// Assert
Assert.Equal(expectedContent, actual);
diff --git a/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/EmailPluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/EmailPluginTests.cs
index f2f27419b2ea..809dae82bd5d 100644
--- a/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/EmailPluginTests.cs
+++ b/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/EmailPluginTests.cs
@@ -80,7 +80,7 @@ public async Task GetMyEmailAddressAsyncSucceedsAsync()
EmailPlugin target = new(connectorMock.Object);
// Act
- string actual = await target.GetMyEmailAddressAsync();
+ string? actual = await target.GetMyEmailAddressAsync();
// Assert
Assert.Equal(anyEmailAddress, actual);
diff --git a/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/OrganizationHierarchyPluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/OrganizationHierarchyPluginTests.cs
index eeaa18446803..c641cbbe7b90 100644
--- a/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/OrganizationHierarchyPluginTests.cs
+++ b/dotnet/src/Plugins/Plugins.UnitTests/MsGraph/OrganizationHierarchyPluginTests.cs
@@ -46,7 +46,7 @@ public async Task GetMyManagerEmailAsyncSucceedsAsync()
OrganizationHierarchyPlugin target = new(connectorMock.Object);
// Act
- string actual = await target.GetMyManagerEmailAsync();
+ string? actual = await target.GetMyManagerEmailAsync();
// Assert
Assert.Equal(anyManagerEmail, actual);
@@ -63,7 +63,7 @@ public async Task GetMyManagerNameAsyncSucceedsAsync()
OrganizationHierarchyPlugin target = new(connectorMock.Object);
// Act
- string actual = await target.GetMyManagerNameAsync();
+ string? actual = await target.GetMyManagerNameAsync();
// Assert
Assert.Equal(anyManagerName, actual);
diff --git a/dotnet/src/VectorData/Milvus/Milvus.csproj b/dotnet/src/VectorData/Milvus/Milvus.csproj
index 07b6696cea8a..00e3d56491ef 100644
--- a/dotnet/src/VectorData/Milvus/Milvus.csproj
+++ b/dotnet/src/VectorData/Milvus/Milvus.csproj
@@ -23,6 +23,7 @@
+