Webority.Graph 0.28.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Webority.Graph --version 0.28.1
                    
NuGet\Install-Package Webority.Graph -Version 0.28.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Webority.Graph" Version="0.28.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Webority.Graph" Version="0.28.1" />
                    
Directory.Packages.props
<PackageReference Include="Webority.Graph" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Webority.Graph --version 0.28.1
                    
#r "nuget: Webority.Graph, 0.28.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Webority.Graph@0.28.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Webority.Graph&version=0.28.1
                    
Install as a Cake Addin
#tool nuget:?package=Webority.Graph&version=0.28.1
                    
Install as a Cake Tool

webority-graph

Microsoft Graph for Webority products, published to nuget.org as Webority.Graph. One app-only Graph client (client credentials) and a typed service per Microsoft 365 area.

Setup

"MicrosoftGraph": {
  "Shared": {
    "ClientId": "<app registration id>",
    "CredentialKind": "ClientSecret",
    "ClientSecret": "<secret>",
    "Authority": "https://login.microsoftonline.com/<tenant id>"
  }
}

Signing in without a secret

"MicrosoftGraph": {
  "Shared": {
    "ClientId": "<app registration id>",
    "CredentialKind": "ManagedIdentityFederation",
    "ManagedIdentityClientId": "<identity client id>",
    "Authority": "https://login.microsoftonline.com/<tenant id>"
  }
}

With ManagedIdentityFederation the app registration gets a federated credential that trusts the user-assigned managed identity: issuer https://login.microsoftonline.com/<tenant-id>/v2.0, subject set to the identity's principal (object) id, audience api://AzureADTokenExchange. The identity must also be assigned to the host app. The same settings apply to MicrosoftGraph:MailSend.

Call the registration for each area you use. Each one also registers the shared client and validates its settings at startup, so a missing value stops the app from booting.

Area Registration Service Own settings
Bookings AddMicrosoftGraphBookings() IMicrosoftGraphBookingsService MicrosoftGraph:Bookings:BookingBusinessId, MicrosoftGraph:Bookings:BusinessTimeZone
Calendar AddMicrosoftGraphCalendar() IMicrosoftGraphCalendarService
Calendar subscriptions AddMicrosoftGraphCalendarSubscriptions() IMicrosoftGraphCalendarSubscriptionService MicrosoftGraph:CalendarSubscriptions
Mail AddMicrosoftGraphMail() IGraphMailService
Mailbox settings AddMicrosoftGraphMailbox() IMicrosoftGraphMailboxService
Teams AddMicrosoftGraphTeams() IMicrosoftGraphTeamsService MicrosoftGraph:Teams
Bot installation AddMicrosoftGraphBotInstaller() IMicrosoftGraphBotInstaller
Entra users and licences AddMicrosoftGraphEntra() IMicrosoftGraphEntraService
Mailbox addresses (Exchange Online) AddMicrosoftGraphExchange() IExchangeMailboxAddressService
App credentials AddEntraAppCredentials() IEntraAppCredentialService
Directory principals and consent policies AddMicrosoftGraphDirectory() IDirectoryPrincipalReader
People, presence, org chart AddPeopleAndPresenceGraph() IPeopleDirectoryService, IPresenceService, IOrgChartService
Rooms AddMicrosoftGraphRooms() IMicrosoftGraphRoomService
To Do AddMicrosoftGraphTodo() IMicrosoftGraphTodoService
Search connector AddGraphSearchConnector() IGraphSearchConnectionManager, IGraphSearchItemService MicrosoftGraph:SearchConnector

IGraphLivenessProbe (GET /organization) comes with every registration. It gives up after 10 seconds and reports unhealthy, so a health check never waits on a slow Graph.

The shared Graph client allows each request 3 minutes rather than the default 100 seconds. That is deliberate: a calendar delta page over a long window can take that long to come back.

Send-only mail

AddMicrosoftGraphMailSend() registers a send-only IGraphMailService under the key mail-send (MicrosoftGraphMailSend.ServiceKey), resolved with GetRequiredKeyedService<IGraphMailService>("mail-send"). It signs in with its own app registration from the MicrosoftGraph:MailSend section (ClientId, CredentialKind, ClientSecret or ManagedIdentityClientId, Authority), separate from the shared client. The app needs only Exchange's Application Mail.Send role, scoped to its mailboxes. Callers set ResolveSentCopy to false, because the app holds only the right to send and cannot read the mailbox's Sent Items.

Errors

A failed Graph call throws InvalidOperationException with the Graph ODataError as its inner exception and the Graph error code in its message. The package does not log it: the caller that handles the exception logs it, so each failure is recorded once.

IGraphMailService.GetMessageByIdAsync returns null, rather than throwing, when the message or the mailbox is gone (ErrorItemNotFound, ResourceNotFound, Request_ResourceNotFound, ErrorInvalidUser, MailboxNotEnabledForRESTAPI). GetFileAttachmentInfosAsync returns an empty list for the same reasons; OpenFileAttachmentAsync throws.

IMicrosoftGraphBotInstaller reads the Teams app definition's permissions once per catalog id per hour, so a republished app definition is used within the hour without a restart.

The install request body carries no @odata.type key. Versions 0.4.0 to 0.10.0 sent it as an explicit null, which made Graph refuse every personal install with expected a string for ODataType value; move to 0.10.1 or later.

Bookings

GetAvailableSlotsAsync returns bookable start times, not the free ranges Graph reports. It cuts them to the service's length on the scheduling interval, applies the minimum lead time and maximum advance (the service's own policy, else the business's), and counts only the staff assigned to the service. Start times sit on a grid counted from midnight in BusinessTimeZone, so a 60-minute service in India starts at 10:00, not 10:30. The grid restarts at every local midnight, which keeps it aligned after a daylight-saving change.

"MicrosoftGraph": {
  "Bookings": {
    "BookingBusinessId": "Sales@contoso.com",
    "BusinessTimeZone": "Asia/Kolkata"
  }
}

BusinessTimeZone is an IANA id and must match the zone the Bookings business itself uses. The app refuses to start when it is missing or names a zone the host does not know. On first use the service also reads the zone the Bookings business reports (its booking page settings) and refuses to offer or book times while the two differ, with an error naming both. When Graph reports no zone it can resolve, the service logs a warning and carries on, since there is nothing to compare.

BookAsync checks the time is still free, books one of the free staff members so their calendar is blocked, and fails with BookingSlotUnavailableError when the time has gone.

ReassignStaffAsync replaces the staff on an existing appointment and sends Graph only the new staff list, so the appointment keeps its id, time, customer and Teams link. Staff already on the appointment need no check. Anyone being added must be free for the appointment's whole length, read the same way BookAsync reads it, because Graph accepts a staff member who is busy. A busy one fails with BookingSlotUnavailableError and nothing changes; an appointment that is gone fails with BookingAppointmentNotFoundError. GetStaffMembersAsync lists the business's staff (id, name, email, role) to choose from.

CancelAppointmentAsync cancels an appointment through Bookings' cancel action, so the customer and staff receive Microsoft's cancellation notice carrying your message. Bookings removes a cancelled appointment, so cancelling one that is already cancelled fails with BookingAppointmentNotFoundError, the same as one that never existed; a caller retrying a cancel can treat that error as done.

GetStaffMembersOfAllBusinessesAsync lists the staff of every Bookings business the app can read, not only the configured one, once per email address (case-insensitive, first occurrence wins; staff with no email are left out).

RemoveStaffMemberFromAllBusinessesAsync deletes the staff member with an email address from every Bookings business and returns the names of the businesses they were removed from (empty when they were on none). Deleting a staff member removes them from every service of that business; existing appointments keep their history. A failure on one business does not stop the rest; the result then fails with one message per business that could not be changed.

Search connector items

IGraphSearchItemService writes items into a Graph search connection (the connection and its schema come from IGraphSearchConnectionManager).

  • UpsertItemAsync(connectionId, item) creates or replaces one item (PUT /external/connections/{id}/items/{itemId}).
  • UpsertManyAsync(connectionId, items) upserts them one after another and stops at the first failure. Upserting is idempotent, so the whole batch can be sent again.
  • DeleteItemAsync(connectionId, itemId) removes an item; one that is already gone counts as deleted.

When Graph answers 429 or 503, the service waits the Retry-After it sends (5 seconds when absent, never more than 5 minutes) and tries again, up to 5 attempts, before the failure is thrown.

A GraphSearchItem carries an Id, Properties (name to value, matching the connection schema), Content with its ContentType (Text or Html) and an Acl.

await items.UpsertItemAsync(connectionId, new GraphSearchItem(
    Id: "doc-42",
    Properties: new Dictionary<string, object> { ["title"] = "Quarterly plan" },
    Content: "Full text to index",
    ContentType: GraphSearchContentType.Text,
    Acl: new[]
    {
        new GraphSearchAclEntry(GraphSearchAclType.Group, salesGroupObjectId, GraphSearchAccessType.Grant),
    }));

ACLs decide who sees an item in search results, and Graph requires at least one entry, so the service refuses an item with none. Type is User, Group or Everyone; Value is the Entra object id of the user or group, or the tenant id for Everyone. AccessType is Grant or Deny, and a deny wins over a grant.

Permission: the app needs ExternalItem.ReadWrite.OwnedBy (application), which lets it write items only into connections it created. Each customer tenant must grant admin consent to the app for that permission before items can be written there.

Converting Graph times

Graph sends a time as a wall clock plus a zone name, and the zone may be a Windows id (India Standard Time) or an IANA id (Asia/Kolkata). Use GraphTimeZones rather than TimeZoneInfo.FindSystemTimeZoneById or appending Z, so a product reads Graph times exactly as the package does.

if (GraphTimeZones.TryResolve(options.BusinessTimeZone, out var businessTimeZone))
{
    // businessTimeZone is a TimeZoneInfo, whichever family the id was named in.
}

if (!GraphTimeZones.TryConvertToUtc(graphEvent.Start, out var startDateTimeUtc))
{
    // Graph sent a time that cannot be converted: unknown zone, unreadable time, or a time inside a
    // daylight-saving gap. startDateTimeUtc is null; do not guess.
}

TryConvertToUtc returns true with a null time when Graph sent no time at all, so "absent" and "unconvertible" stay distinguishable. It never treats the wall clock as UTC.

No environment switch

The package always registers the real service. A host that must not reach Microsoft 365 outside Production registers its own stand-in implementation of the interface and decides when.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Webority.Graph:

Package Downloads
Webority.Email.Graph

Microsoft 365 for Webority.Email over Webority.Graph: inbound mail (messages, attachments, sent items, subscriptions, change notifications) shared by outreach and support, and the backup sender for Critical mail through a shared mailbox.

Webority.Email.Outreach.Graph

Microsoft 365 adapter for Webority.Email.Outreach over Webority.Graph: sends from a mailbox, reads replies and sent items, creates and renews mail subscriptions, parses the change notifications, and checks a send-as address against the mailbox's aliases.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.32.0 0 11/3/2026
0.31.1 64 11/3/2026
0.31.0 4 11/3/2026
0.30.0 532 11/2/2026
0.29.1 602 11/1/2026
0.29.0 141 11/1/2026
0.28.4 62 11/1/2026
0.28.3 63 11/1/2026
0.28.2 99 11/1/2026
0.28.1 239 10/30/2026
0.28.0 81 10/29/2026
0.27.2 65 10/29/2026
0.27.1 65 10/29/2026
0.27.0 62 10/29/2026
0.26.0 66 10/29/2026
0.25.2 80 10/28/2026
0.25.1 93 10/28/2026
0.25.0 103 10/28/2026
0.24.0 241 10/25/2026
0.23.0 224 10/20/2026
Loading failed