基于Microsoft.Graph.Communications SDK的Teams呼叫操作及代码评审
Hey there! Let's tackle your two technical requests with clear explanations and actionable fixes:
Here's a straightforward breakdown of how to implement call creation, answering, and transfer:
Creating a Teams Call
First, ensure your StatefulClient is initialized correctly (like in your Bot constructor). Then define the target participant and media settings to place the call:
// Initialize your stateful client (reuse your existing builder logic) var client = new StatefulClientBuilder("YourBotName", "YourAppId", graphLogger) .SetAuthenticationProvider(authProvider) .SetNotificationUri(notificationUri) .Build(); // Define the target user for the call var targetParticipant = new InvitationParticipantInfo { Identity = new IdentitySet { User = new Identity { Id = "target-user-object-id", AdditionalData = new Dictionary<string, object> { { "tenantId", "your-tenant-id" } } } }, EndpointType = EndpointType.Default }; // Configure media (audio-only for IVR scenarios) var mediaConfig = new List<MediaInfo>(); // Place the call var newCall = await client.Calls().PlaceCallAsync( new[] { targetParticipant }, mediaConfig, new[] { Modality.Audio });
Answering an Incoming Call
Handle the OnIncoming event to accept the call asynchronously, with proper error handling:
private void CallsOnIncoming(ICallCollection sender, CollectionEventArgs<ICall> args) { var incomingCall = args.AddedResources.First(); var mediaConfig = new List<MediaInfo>(); Task.Run(async () => { try { await incomingCall.AnswerAsync(mediaConfig, new[] { Modality.Audio }).ConfigureAwait(false); graphLogger.Info("Incoming call answered successfully"); // Attach your call handler logic here } catch (Exception ex) { graphLogger.Error(ex, "Failed to answer incoming call"); } }); }
Transferring an Active Call
Critical Note: You can only transfer a call after it has been successfully answered and is in an active state. Here's how to execute it properly:
// Assuming you have an active call instance (from OnIncoming or your call handler) var transferTarget = new InvitationParticipantInfo { Identity = new IdentitySet { User = new Identity { Id = "transfer-target-user-id", AdditionalData = new Dictionary<string, object> { { "tenantId", "your-tenant-id" } } } }, EndpointType = EndpointType.Default }; // Execute transfer only after the call is connected await activeCall.TransferAsync(transferTarget).ConfigureAwait(false);
Looking at your modified CallsOnIncoming method, the core issue is you're trying to transfer the call before it's been fully answered. The Graph API won't process a transfer request for a call that's still in the incoming state. Here's the breakdown and fix:
What Was Broken
- You commented out the
await answerTaskcall, so the call never completes the answer process before you attempt transfer. - Even if you hadn't commented it out, you were triggering both
answerTaskandtransferTaskin parallel, meaning the transfer might execute before the call is active.
Fixed Code
Adjust the logic to wait for the call to be answered first, then initiate the transfer:
private void CallsOnIncoming(ICallCollection sender, CollectionEventArgs<ICall> args) { var call = args.AddedResources.First(); // Keep your existing target setup Dictionary<string, object> _additionalData = new Dictionary<string, object>(); _additionalData.Add("tenantId", "72f988bf-86f1-41af-91ab-2d7cd011db47"); var target = new InvitationParticipantInfo { LanguageId = "en-US", Region = "westus", EndpointType = EndpointType.Default, Identity = new IdentitySet { User = new Identity { AdditionalData = _additionalData, Id = "77b1cacb-c38b-4b57-a4a5-f5919360bc24" } } }; var callee = call.Resource.Targets.First(); var callType = callee?.Identity?.GetApplicationInstance() == null ? CallType.BotIncoming : CallType.BotEndpointIncoming; var callHandler = new IncomingCallHandler(this, call); this.callHandlers[call.Resource.Id] = callHandler; var mediaConfig = new List<MediaInfo>(); // Async workflow: Answer first, THEN transfer Task.Run(async () => { try { // Wait for the call to be fully answered await call.AnswerAsync(mediaConfig, new[] { Modality.Audio }).ConfigureAwait(false); this.graphLogger.Info("Incoming call answered successfully"); // Now transfer the active call await call.TransferAsync(target).ConfigureAwait(false); this.graphLogger.Info("Call transferred successfully to target user"); } catch (Exception ex) { this.graphLogger.Error(ex, $"Error handling call: {ex.Message}"); } }); }
Additional Checks to Ensure Success
- Permissions: Verify your app has the required Graph API permissions:
Calls.Initiate.AllandCalls.AccessMedia.All(make sure admin consent is granted). - Target Validation: Double-check the target user's ID and tenant ID are correct—ensure the user is active in Teams.
- Logging: Check your Application Insights or graph logs for specific error messages if the transfer still fails (common issues include permission errors or invalid target identities).
内容的提问来源于stack exchange,提问作者Miguel




