diff --git a/BotBuilder-DotNet.ruleset b/BotBuilder-DotNet.ruleset index e88cf5c5d..d69b00d23 100644 --- a/BotBuilder-DotNet.ruleset +++ b/BotBuilder-DotNet.ruleset @@ -6,11 +6,16 @@ - - - + + + + + + + + + - @@ -186,15 +191,6 @@ + - - - - - - - - - - \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 425c123d9..120e4f50e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -59,7 +59,7 @@ Suppress a warning about upcoming deprecation of PackageLicenseUrl. When embedding licenses are supported, replace PackageLicenseUrl with PackageLicenseExpression. --> - NU5125 + $(NoWarn);NU5125 true true snupkg diff --git a/FunctionalTests/Directory.Build.props b/FunctionalTests/Directory.Build.props index 421e45f89..2e102e264 100644 --- a/FunctionalTests/Directory.Build.props +++ b/FunctionalTests/Directory.Build.props @@ -1,8 +1,9 @@ - - $(NoWarn);SA0001;CS1573,CS1591,CS1712 + + + $(NoWarn);SA0001;CS1573;CS1591;CS1712;SX1309 diff --git a/libraries/Adapters/Directory.Build.props b/libraries/Adapters/Directory.Build.props index c8c790106..2f68009aa 100644 --- a/libraries/Adapters/Directory.Build.props +++ b/libraries/Adapters/Directory.Build.props @@ -1,7 +1,11 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioAdapter.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioAdapter.cs index 32aa1fda1..a3b1a0d4f 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioAdapter.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioAdapter.cs @@ -81,7 +81,7 @@ namespace Microsoft.Bot.Builder.Adapters.Twilio { var messageOptions = TwilioHelper.ActivityToTwilio(activity, _twilioClient.Options.TwilioNumber); - var res = await _twilioClient.SendMessage(messageOptions, cancellationToken).ConfigureAwait(false); + var res = await _twilioClient.SendMessageAsync(messageOptions, cancellationToken).ConfigureAwait(false); var response = new ResourceResponse() { Id = res, }; diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioClientWrapper.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioClientWrapper.cs index 231374d66..8e95f4627 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioClientWrapper.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioClientWrapper.cs @@ -60,7 +60,7 @@ namespace Microsoft.Bot.Builder.Adapters.Twilio /// An object containing the parameters for the message to send. /// A cancellation token for the task. /// The SID of the Twilio message sent. - public virtual async Task SendMessage(CreateMessageOptions messageOptions, CancellationToken cancellationToken) + public virtual async Task SendMessageAsync(CreateMessageOptions messageOptions, CancellationToken cancellationToken) { var messageResource = await MessageResource.CreateAsync(messageOptions).ConfigureAwait(false); return messageResource.Sid; diff --git a/libraries/AdaptiveExpressions/AdaptiveExpressions.csproj b/libraries/AdaptiveExpressions/AdaptiveExpressions.csproj index 397e67e8d..f73da99a2 100644 --- a/libraries/AdaptiveExpressions/AdaptiveExpressions.csproj +++ b/libraries/AdaptiveExpressions/AdaptiveExpressions.csproj @@ -18,7 +18,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.AI.LUIS/Microsoft.Bot.Builder.AI.Luis.csproj b/libraries/Microsoft.Bot.Builder.AI.LUIS/Microsoft.Bot.Builder.AI.Luis.csproj index cfaf78569..7998866d9 100644 --- a/libraries/Microsoft.Bot.Builder.AI.LUIS/Microsoft.Bot.Builder.AI.Luis.csproj +++ b/libraries/Microsoft.Bot.Builder.AI.LUIS/Microsoft.Bot.Builder.AI.Luis.csproj @@ -25,7 +25,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.AI.QnA/Microsoft.Bot.Builder.AI.QnA.csproj b/libraries/Microsoft.Bot.Builder.AI.QnA/Microsoft.Bot.Builder.AI.QnA.csproj index 1406c9056..3ba2cef6d 100644 --- a/libraries/Microsoft.Bot.Builder.AI.QnA/Microsoft.Bot.Builder.AI.QnA.csproj +++ b/libraries/Microsoft.Bot.Builder.AI.QnA/Microsoft.Bot.Builder.AI.QnA.csproj @@ -24,7 +24,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.Azure/Microsoft.Bot.Builder.Azure.csproj b/libraries/Microsoft.Bot.Builder.Azure/Microsoft.Bot.Builder.Azure.csproj index 84e4a6c68..aade4c08f 100644 --- a/libraries/Microsoft.Bot.Builder.Azure/Microsoft.Bot.Builder.Azure.csproj +++ b/libraries/Microsoft.Bot.Builder.Azure/Microsoft.Bot.Builder.Azure.csproj @@ -23,7 +23,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive.Testing/Microsoft.Bot.Builder.Dialogs.Adaptive.Testing.csproj b/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive.Testing/Microsoft.Bot.Builder.Dialogs.Adaptive.Testing.csproj index 70b7cc1f2..314e2cf89 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive.Testing/Microsoft.Bot.Builder.Dialogs.Adaptive.Testing.csproj +++ b/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive.Testing/Microsoft.Bot.Builder.Dialogs.Adaptive.Testing.csproj @@ -29,7 +29,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Microsoft.Bot.Builder.Dialogs.Adaptive.csproj b/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Microsoft.Bot.Builder.Dialogs.Adaptive.csproj index a09740340..04ddad9ce 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Microsoft.Bot.Builder.Dialogs.Adaptive.csproj +++ b/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Microsoft.Bot.Builder.Dialogs.Adaptive.csproj @@ -26,7 +26,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.Dialogs.Debugging/Microsoft.Bot.Builder.Dialogs.Debugging.csproj b/libraries/Microsoft.Bot.Builder.Dialogs.Debugging/Microsoft.Bot.Builder.Dialogs.Debugging.csproj index fb80edfa8..24bdb72af 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs.Debugging/Microsoft.Bot.Builder.Dialogs.Debugging.csproj +++ b/libraries/Microsoft.Bot.Builder.Dialogs.Debugging/Microsoft.Bot.Builder.Dialogs.Debugging.csproj @@ -26,7 +26,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.Dialogs.Declarative/Microsoft.Bot.Builder.Dialogs.Declarative.csproj b/libraries/Microsoft.Bot.Builder.Dialogs.Declarative/Microsoft.Bot.Builder.Dialogs.Declarative.csproj index b847442f2..bad5152e8 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs.Declarative/Microsoft.Bot.Builder.Dialogs.Declarative.csproj +++ b/libraries/Microsoft.Bot.Builder.Dialogs.Declarative/Microsoft.Bot.Builder.Dialogs.Declarative.csproj @@ -25,7 +25,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.Dialogs/Microsoft.Bot.Builder.Dialogs.csproj b/libraries/Microsoft.Bot.Builder.Dialogs/Microsoft.Bot.Builder.Dialogs.csproj index 5c8e57f41..38124191f 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs/Microsoft.Bot.Builder.Dialogs.csproj +++ b/libraries/Microsoft.Bot.Builder.Dialogs/Microsoft.Bot.Builder.Dialogs.csproj @@ -27,7 +27,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.LanguageGeneration/Microsoft.Bot.Builder.LanguageGeneration.csproj b/libraries/Microsoft.Bot.Builder.LanguageGeneration/Microsoft.Bot.Builder.LanguageGeneration.csproj index 82be0776a..18472dc8b 100644 --- a/libraries/Microsoft.Bot.Builder.LanguageGeneration/Microsoft.Bot.Builder.LanguageGeneration.csproj +++ b/libraries/Microsoft.Bot.Builder.LanguageGeneration/Microsoft.Bot.Builder.LanguageGeneration.csproj @@ -25,7 +25,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder.TemplateManager/Microsoft.Bot.Builder.TemplateManager.csproj b/libraries/Microsoft.Bot.Builder.TemplateManager/Microsoft.Bot.Builder.TemplateManager.csproj index 749733e77..617898d7c 100644 --- a/libraries/Microsoft.Bot.Builder.TemplateManager/Microsoft.Bot.Builder.TemplateManager.csproj +++ b/libraries/Microsoft.Bot.Builder.TemplateManager/Microsoft.Bot.Builder.TemplateManager.csproj @@ -23,7 +23,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Builder/ActivityFactory.cs b/libraries/Microsoft.Bot.Builder/ActivityFactory.cs index bcc829ce3..7e0cbf65e 100644 --- a/libraries/Microsoft.Bot.Builder/ActivityFactory.cs +++ b/libraries/Microsoft.Bot.Builder/ActivityFactory.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Reflection; using Microsoft.Bot.Schema; @@ -14,11 +15,15 @@ namespace Microsoft.Bot.Builder /// The ActivityFactory /// to generate text and then uses simple markdown semantics like chatdown to create Activity. /// +#pragma warning disable CA1052 // Static holder types should be Static or NotInheritable (we can't change this without breaking binary compat) public class ActivityFactory +#pragma warning restore CA1052 // Static holder types should be Static or NotInheritable { +#pragma warning disable CA1308 // Normalize strings to uppercase (LG is heavily invested in lowercase, ignoring this rule in this class) private const string LGType = "lgType"; - private static readonly string ErrorPrefix = "[ERROR]"; - private static readonly string WarningPrefix = "[WARNING]"; + private const string ErrorPrefix = "[ERROR]"; + private const string WarningPrefix = "[WARNING]"; + private const string AdaptiveCardType = "application/vnd.microsoft.card.adaptive"; private static readonly IList AllActivityTypes = GetAllPublicConstantValues(typeof(ActivityTypes)); private static readonly IList AllActivityProperties = GetAllProperties(typeof(Activity)); @@ -36,8 +41,6 @@ namespace Microsoft.Bot.Builder { nameof(ReceiptCard).ToLowerInvariant(), ReceiptCard.ContentType }, }; - private static readonly string AdaptiveCardType = "application/vnd.microsoft.card.adaptive"; - /// /// Generate the activity. /// Support Both string LG result and structured LG result. @@ -47,7 +50,7 @@ namespace Microsoft.Bot.Builder public static Activity FromObject(object lgResult) { var diagnostics = CheckLGResult(lgResult); - var errors = diagnostics.Where(u => u.StartsWith(ErrorPrefix)); + var errors = diagnostics.Where(u => u.StartsWith(ErrorPrefix, StringComparison.Ordinal)); if (errors.Any()) { @@ -64,7 +67,9 @@ namespace Microsoft.Bot.Builder var lgJsonResult = JObject.FromObject(lgResult); return BuildActivityFromLGStructuredResult(lgJsonResult); } +#pragma warning disable CA1031 // Do not catch general exception types (we should narrow down the exception being caught but for now we just attempt to build the activity from the text property) catch +#pragma warning restore CA1031 // Do not catch general exception types { return BuildActivityFromText(lgResult?.ToString()?.Trim()); } @@ -84,7 +89,7 @@ namespace Microsoft.Bot.Builder return new List { BuildDiagnostic("LG output is empty", false) }; } - if (!lgStringResult.StartsWith("{") || !lgStringResult.EndsWith("}")) + if (!lgStringResult.StartsWith("{", StringComparison.Ordinal) || !lgStringResult.EndsWith("}", StringComparison.Ordinal)) { return new List { BuildDiagnostic("LG output is not a json object, and will fallback to string format.", false) }; } @@ -94,7 +99,9 @@ namespace Microsoft.Bot.Builder { lgStructuredResult = JObject.Parse(lgStringResult); } +#pragma warning disable CA1031 // Do not catch general exception types (we should narrow down the exception being caught but for now we just show an error message) catch +#pragma warning restore CA1031 // Do not catch general exception types { return new List { BuildDiagnostic("LG output is not a json object, and will fallback to string format.", false) }; } @@ -108,7 +115,9 @@ namespace Microsoft.Bot.Builder { lgStructuredResult = JObject.FromObject(lgResult); } +#pragma warning disable CA1031 // Do not catch general exception types (we should narrow down the exception being caught but for now we just show an error message) catch +#pragma warning restore CA1031 // Do not catch general exception types { return new List { BuildDiagnostic("LG output is not a json object, and will fallback to string format.", false) }; } @@ -414,7 +423,8 @@ namespace Microsoft.Bot.Builder boolResult = true; return true; } - else if (boolValue.ToLowerInvariant() == "false") + + if (boolValue.ToLowerInvariant() == "false") { boolResult = false; return true; @@ -757,5 +767,6 @@ namespace Microsoft.Bot.Builder { return type.GetProperties().Select(u => u.Name.ToLowerInvariant()).ToList(); } +#pragma warning restore CA1308 // Normalize strings to uppercase } } diff --git a/libraries/Microsoft.Bot.Builder/ActivityHandler.cs b/libraries/Microsoft.Bot.Builder/ActivityHandler.cs index baa2d129d..1ac6e18be 100644 --- a/libraries/Microsoft.Bot.Builder/ActivityHandler.cs +++ b/libraries/Microsoft.Bot.Builder/ActivityHandler.cs @@ -563,10 +563,12 @@ namespace Microsoft.Bot.Builder return Task.CompletedTask; } +#pragma warning disable CA1064 // Exceptions should be public (we can't change this without breaking binary compat, we may consider making this type public in the future) protected class InvokeResponseException : Exception +#pragma warning restore CA1064 // Exceptions should be public { - private HttpStatusCode _statusCode; - private object _body; + private readonly HttpStatusCode _statusCode; + private readonly object _body; public InvokeResponseException(HttpStatusCode statusCode, object body = null) { @@ -574,9 +576,27 @@ namespace Microsoft.Bot.Builder _body = body; } + public InvokeResponseException() + { + } + + public InvokeResponseException(string message) + : base(message) + { + } + + public InvokeResponseException(string message, Exception innerException) + : base(message, innerException) + { + } + public InvokeResponse CreateInvokeResponse() { - return new InvokeResponse { Status = (int)_statusCode, Body = _body }; + return new InvokeResponse + { + Status = (int)_statusCode, + Body = _body + }; } } } diff --git a/libraries/Microsoft.Bot.Builder/Adapters/TestAdapter.cs b/libraries/Microsoft.Bot.Builder/Adapters/TestAdapter.cs index efd2465c9..af8219728 100644 --- a/libraries/Microsoft.Bot.Builder/Adapters/TestAdapter.cs +++ b/libraries/Microsoft.Bot.Builder/Adapters/TestAdapter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Security.Claims; using System.Threading; @@ -125,8 +126,10 @@ namespace Microsoft.Bot.Builder.Adapters ChannelId = "test", ServiceUrl = "https://test.com", Conversation = new ConversationAccount(false, name, name), - User = new ChannelAccount(id: user.ToLower(), name: user), - Bot = new ChannelAccount(id: bot.ToLower(), name: bot), +#pragma warning disable CA1308 // Normalize strings to uppercase (it is safe to use lowercase here, this is just for display purposes) + User = new ChannelAccount(id: user.ToLowerInvariant(), name: user), + Bot = new ChannelAccount(id: bot.ToLowerInvariant(), name: bot), +#pragma warning restore CA1308 // Normalize strings to uppercase Locale = "en-us" }; } @@ -174,7 +177,7 @@ namespace Microsoft.Bot.Builder.Adapters activity.Conversation = Conversation.Conversation; activity.ServiceUrl = Conversation.ServiceUrl; - var id = activity.Id = (_nextId++).ToString(); + var id = activity.Id = (_nextId++).ToString(CultureInfo.InvariantCulture); } if (activity.Timestamp == null || activity.Timestamp == default(DateTimeOffset)) @@ -370,9 +373,12 @@ namespace Microsoft.Bot.Builder.Adapters { ActiveQueue.Clear(); var update = Activity.CreateConversationUpdateActivity(); - update.Conversation = new ConversationAccount() { Id = Guid.NewGuid().ToString("n") }; - var context = new TurnContext(this, (Activity)update); - return callback(context, cancellationToken); + update.ChannelId = channelId; + update.Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("n") }; + using (var context = new TurnContext(this, (Activity)update)) + { + return callback(context, cancellationToken); + } } /// @@ -435,7 +441,7 @@ namespace Microsoft.Bot.Builder.Adapters Recipient = Conversation.Bot, Conversation = Conversation.Conversation, ServiceUrl = Conversation.ServiceUrl, - Id = (_nextId++).ToString(), + Id = (_nextId++).ToString(CultureInfo.InvariantCulture), Text = text, }; @@ -892,9 +898,9 @@ namespace Microsoft.Bot.Builder.Adapters var rhs = obj as UserTokenKey; if (rhs != null) { - return string.Equals(this.ConnectionName, rhs.ConnectionName) && - string.Equals(this.UserId, rhs.UserId) && - string.Equals(this.ChannelId, rhs.ChannelId); + return string.Equals(this.ConnectionName, rhs.ConnectionName, StringComparison.Ordinal) && + string.Equals(this.UserId, rhs.UserId, StringComparison.Ordinal) && + string.Equals(this.ChannelId, rhs.ChannelId, StringComparison.Ordinal); } return base.Equals(obj); @@ -917,7 +923,7 @@ namespace Microsoft.Bot.Builder.Adapters var rhs = obj as ExchangableTokenKey; if (rhs != null) { - return string.Equals(this.ExchangableItem, rhs.ExchangableItem) && + return string.Equals(this.ExchangableItem, rhs.ExchangableItem, StringComparison.Ordinal) && base.Equals(obj); } diff --git a/libraries/Microsoft.Bot.Builder/Adapters/TestFlow.cs b/libraries/Microsoft.Bot.Builder/Adapters/TestFlow.cs index 603198b5d..cebbadedd 100644 --- a/libraries/Microsoft.Bot.Builder/Adapters/TestFlow.cs +++ b/libraries/Microsoft.Bot.Builder/Adapters/TestFlow.cs @@ -97,7 +97,7 @@ namespace Microsoft.Bot.Builder.Adapters { if (userSays == null) { - throw new ArgumentNullException("You have to pass a userSays parameter"); + throw new ArgumentNullException(nameof(userSays), "You have to pass a userSays parameter"); } return new TestFlow( @@ -145,7 +145,7 @@ namespace Microsoft.Bot.Builder.Adapters { if (userActivity == null) { - throw new ArgumentNullException("You have to pass an Activity"); + throw new ArgumentNullException(nameof(userActivity), "You have to pass an Activity"); } return new TestFlow( @@ -314,7 +314,9 @@ namespace Microsoft.Bot.Builder.Adapters /// The amount of time in milliseconds within which a response is expected. /// A new object that appends this assertion to the modeled exchange. /// This method does not modify the original object. +#pragma warning disable CA1801 // Review unused parameters (we can't remove this withouth breaking binary compat) public TestFlow AssertReply(Action validateActivity, [CallerMemberName] string description = null, uint timeout = 3000) +#pragma warning restore CA1801 // Review unused parameters { return new TestFlow( async () => @@ -530,9 +532,9 @@ namespace Microsoft.Bot.Builder.Adapters timeout); } - private bool IsReply(IActivity activity) + private static bool IsReply(IActivity activity) { - return string.Equals("bot", activity.From?.Role, StringComparison.InvariantCultureIgnoreCase); + return string.Equals("bot", activity.From?.Role, StringComparison.OrdinalIgnoreCase); } } } diff --git a/libraries/Microsoft.Bot.Builder/BotAssert.cs b/libraries/Microsoft.Bot.Builder/BotAssert.cs index 4814a1a3f..cf2b422f1 100644 --- a/libraries/Microsoft.Bot.Builder/BotAssert.cs +++ b/libraries/Microsoft.Bot.Builder/BotAssert.cs @@ -10,7 +10,9 @@ namespace Microsoft.Bot.Builder /// /// Provides methods for debugging Bot Builder code. /// +#pragma warning disable CA1052 // Static holder types should be Static or NotInheritable (we can't change this without breaking binary compat) public class BotAssert +#pragma warning restore CA1052 // Static holder types should be Static or NotInheritable { /// /// Checks that an activity object is not null. diff --git a/libraries/Microsoft.Bot.Builder/BotFrameworkAdapter.cs b/libraries/Microsoft.Bot.Builder/BotFrameworkAdapter.cs index 7119751e0..4e944aff0 100644 --- a/libraries/Microsoft.Bot.Builder/BotFrameworkAdapter.cs +++ b/libraries/Microsoft.Bot.Builder/BotFrameworkAdapter.cs @@ -363,7 +363,7 @@ namespace Microsoft.Bot.Builder } } - var connectorClient = await CreateConnectorClientAsync(reference.ServiceUrl, claimsIdentity, audience, cancellationToken).ConfigureAwait(false); + var connectorClient = await CreateConnectorClientAsync(reference.ServiceUrl, claimsIdentity, audience).ConfigureAwait(false); context.TurnState.Add(connectorClient); await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false); @@ -441,7 +441,7 @@ namespace Microsoft.Bot.Builder // The OAuthScope is also stored on the TurnState to get the correct AppCredentials if fetching a token is required. var scope = SkillValidation.IsSkillClaim(claimsIdentity.Claims) ? JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims) : GetBotFrameworkOAuthScope(); context.TurnState.Add(OAuthScopeKey, scope); - var connectorClient = await CreateConnectorClientAsync(activity.ServiceUrl, claimsIdentity, scope, cancellationToken).ConfigureAwait(false); + var connectorClient = await CreateConnectorClientAsync(activity.ServiceUrl, claimsIdentity, scope).ConfigureAwait(false); context.TurnState.Add(connectorClient); context.TurnState.Add(callback); @@ -552,7 +552,9 @@ namespace Microsoft.Bot.Builder var oAuthScope = turnContext.TurnState.Get(OAuthScopeKey); _ = (await GetAppCredentialsAsync(appId, oAuthScope).ConfigureAwait(false)).GetTokenAsync(); } +#pragma warning disable CA1031 // Do not catch general exception types (we just catch and log the exception here) catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { Logger.LogError("Failed to fetch token before processing outgoing activity. " + ex.Message); } @@ -1153,28 +1155,28 @@ namespace Microsoft.Bot.Builder /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// If the task completes, the exchanged token is returned. - public virtual async Task ExchangeTokenAsync(ITurnContext turnContext, AppCredentials oAuthAppCredentials, string connectionName, string userId, TokenExchangeRequest exchangeRequest, CancellationToken cancellationToken = default(CancellationToken)) + public virtual async Task ExchangeTokenAsync(ITurnContext turnContext, AppCredentials oAuthAppCredentials, string connectionName, string userId, TokenExchangeRequest exchangeRequest, CancellationToken cancellationToken = default) { BotAssert.ContextNotNull(turnContext); if (string.IsNullOrWhiteSpace(connectionName)) { - LogAndThrowException(new ArgumentException(nameof(connectionName))); + LogAndThrowException(new ArgumentException($"{nameof(connectionName)} is null or empty", nameof(connectionName))); } if (string.IsNullOrWhiteSpace(userId)) { - LogAndThrowException(new ArgumentException(nameof(userId))); + LogAndThrowException(new ArgumentException($"{nameof(userId)} is null or empty", nameof(userId))); } if (exchangeRequest == null) { - LogAndThrowException(new ArgumentException(nameof(exchangeRequest))); + LogAndThrowException(new ArgumentException($"{nameof(exchangeRequest)} is null or empty", nameof(exchangeRequest))); } if (string.IsNullOrWhiteSpace(exchangeRequest.Token) && string.IsNullOrWhiteSpace(exchangeRequest.Uri)) { - LogAndThrowException(new ArgumentException(nameof(exchangeRequest), "Either a Token or Uri property is required on the TokenExchangeRequest")); + LogAndThrowException(new ArgumentException("Either a Token or Uri property is required on the TokenExchangeRequest", nameof(exchangeRequest))); } var activity = turnContext.Activity; @@ -1191,13 +1193,11 @@ namespace Microsoft.Bot.Builder { return tokenResponse; } - else - { - LogAndThrowException(new InvalidOperationException($"ExchangeAsyncAsync returned improper result: {result.GetType()}")); - // even though LogAndThrowException always throws, compiler gives an error about not all code paths returning a value. - return null; - } + LogAndThrowException(new InvalidOperationException($"ExchangeAsyncAsync returned improper result: {result.GetType()}")); + + // even though LogAndThrowException always throws, compiler gives an error about not all code paths returning a value. + return null; } /// @@ -1309,7 +1309,7 @@ namespace Microsoft.Bot.Builder Task IConnectorClientBuilder.CreateConnectorClientAsync(string serviceUrl, ClaimsIdentity claimsIdentity, string audience, CancellationToken cancellationToken) { - return CreateConnectorClientAsync(serviceUrl, claimsIdentity, audience, cancellationToken); + return CreateConnectorClientAsync(serviceUrl, claimsIdentity, audience); } /// @@ -1321,7 +1321,7 @@ namespace Microsoft.Bot.Builder protected virtual async Task CreateOAuthApiClientAsync(ITurnContext turnContext, AppCredentials oAuthAppCredentials) { if (!OAuthClientConfig.EmulateOAuthCards && - string.Equals(turnContext.Activity.ChannelId, Channels.Emulator, StringComparison.InvariantCultureIgnoreCase) && + string.Equals(turnContext.Activity.ChannelId, Channels.Emulator, StringComparison.OrdinalIgnoreCase) && (await CredentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false))) { OAuthClientConfig.EmulateOAuthCards = true; @@ -1335,7 +1335,7 @@ namespace Microsoft.Bot.Builder var appCredentials = oAuthAppCredentials ?? await GetAppCredentialsAsync(appId, oAuthScope).ConfigureAwait(false); if (!OAuthClientConfig.EmulateOAuthCards && - string.Equals(turnContext.Activity.ChannelId, Channels.Emulator, StringComparison.InvariantCultureIgnoreCase) && + string.Equals(turnContext.Activity.ChannelId, Channels.Emulator, StringComparison.OrdinalIgnoreCase) && (await CredentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false))) { OAuthClientConfig.EmulateOAuthCards = true; @@ -1421,6 +1421,28 @@ namespace Microsoft.Bot.Builder return ChannelProvider != null && ChannelProvider.IsGovernment() ? new MicrosoftGovernmentAppCredentials(appId, appPassword, HttpClient, Logger, oAuthScope) : new MicrosoftAppCredentials(appId, appPassword, HttpClient, Logger, oAuthScope); } + /// + /// Gets the AppId of the Bot out of the TurnState. + /// + /// The context object for the turn. + /// Bot's AppId. + private static string GetBotAppId(ITurnContext turnContext) + { + var botIdentity = (ClaimsIdentity)turnContext.TurnState.Get(BotIdentityKey); + if (botIdentity == null) + { + throw new InvalidOperationException("An IIdentity is required in TurnState for this operation."); + } + + var appId = botIdentity.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim)?.Value; + if (string.IsNullOrWhiteSpace(appId)) + { + throw new InvalidOperationException("Unable to get the bot AppId from the audience claim."); + } + + return appId; + } + /// /// Generates the CallerId property for the activity based on /// https://github.com/microsoft/botframework-obi/blob/master/protocols/botframework-activity/botframework-activity.md#appendix-v---caller-id-values. @@ -1463,10 +1485,9 @@ namespace Microsoft.Bot.Builder /// The service URL. /// The claims claimsIdentity. /// The target audience for the connector. - /// Cancellation token. /// ConnectorClient instance. /// ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off. - private async Task CreateConnectorClientAsync(string serviceUrl, ClaimsIdentity claimsIdentity, string audience, CancellationToken cancellationToken = default) + private async Task CreateConnectorClientAsync(string serviceUrl, ClaimsIdentity claimsIdentity, string audience) { if (claimsIdentity == null) { @@ -1497,7 +1518,7 @@ namespace Microsoft.Bot.Builder GetBotFrameworkOAuthScope(); } - appCredentials = await GetAppCredentialsAsync(botId, scope, cancellationToken).ConfigureAwait(false); + appCredentials = await GetAppCredentialsAsync(botId, scope).ConfigureAwait(false); } return CreateConnectorClient(serviceUrl, appCredentials); @@ -1544,9 +1565,8 @@ namespace Microsoft.Bot.Builder /// /// The application identifier (AAD ID for the bot). /// The scope for the token. Skills use the skill's app ID. - /// Cancellation token. /// App credentials. - private async Task GetAppCredentialsAsync(string appId, string oAuthScope, CancellationToken cancellationToken = default) + private async Task GetAppCredentialsAsync(string appId, string oAuthScope) { if (string.IsNullOrWhiteSpace(appId)) { @@ -1575,28 +1595,6 @@ namespace Microsoft.Bot.Builder return appCredentials; } - /// - /// Gets the AppId of the Bot out of the TurnState. - /// - /// The context object for the turn. - /// Bot's AppId. - private string GetBotAppId(ITurnContext turnContext) - { - var botIdentity = (ClaimsIdentity)turnContext.TurnState.Get(BotIdentityKey); - if (botIdentity == null) - { - throw new InvalidOperationException("An IIdentity is required in TurnState for this operation."); - } - - var appId = botIdentity.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim)?.Value; - if (string.IsNullOrWhiteSpace(appId)) - { - throw new InvalidOperationException("Unable to get the bot AppId from the audience claim."); - } - - return appId; - } - /// /// This method returns the correct Bot Framework OAuthScope for AppCredentials. /// @@ -1608,7 +1606,7 @@ namespace Microsoft.Bot.Builder } /// - /// Logs and throws an expcetion. + /// Logs and throws an exception. /// /// Exception instance to throw. /// Source method for the exception. @@ -1630,7 +1628,7 @@ namespace Microsoft.Bot.Builder { public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default) { - if (Channels.Msteams.Equals(turnContext.Activity.ChannelId, StringComparison.InvariantCultureIgnoreCase) && turnContext.Activity.Conversation != null && string.IsNullOrEmpty(turnContext.Activity.Conversation.TenantId) && turnContext.Activity.ChannelData != null) + if (Channels.Msteams.Equals(turnContext.Activity.ChannelId, StringComparison.OrdinalIgnoreCase) && turnContext.Activity.Conversation != null && string.IsNullOrEmpty(turnContext.Activity.Conversation.TenantId) && turnContext.Activity.ChannelData != null) { var teamsChannelData = JObject.FromObject(turnContext.Activity.ChannelData); if (teamsChannelData["tenant"]?["id"] != null) diff --git a/libraries/Microsoft.Bot.Builder/BotState.cs b/libraries/Microsoft.Bot.Builder/BotState.cs index 918c49345..078db02b9 100644 --- a/libraries/Microsoft.Bot.Builder/BotState.cs +++ b/libraries/Microsoft.Bot.Builder/BotState.cs @@ -134,7 +134,7 @@ namespace Microsoft.Bot.Builder { key, cachedState.State }, }; await _storage.WriteAsync(changes).ConfigureAwait(false); - cachedState.Hash = cachedState.ComputeHash(cachedState.State); + cachedState.Hash = CachedBotState.ComputeHash(cachedState.State); return; } } @@ -230,7 +230,9 @@ namespace Microsoft.Bot.Builder /// If the task is successful, the result contains the property value, otherwise it will be default(T). /// or /// is null. +#pragma warning disable CA1801 // Review unused parameters (we can't change this without breaking binary compat) protected Task GetPropertyValueAsync(ITurnContext turnContext, string propertyName, CancellationToken cancellationToken = default(CancellationToken)) +#pragma warning restore CA1801 // Review unused parameters { BotAssert.ContextNotNull(turnContext); @@ -287,7 +289,9 @@ namespace Microsoft.Bot.Builder /// A task that represents the work queued to execute. /// or /// is null. +#pragma warning disable CA1801 // Review unused parameters (we can't change this without breaking binary compat) protected Task DeletePropertyValueAsync(ITurnContext turnContext, string propertyName, CancellationToken cancellationToken = default(CancellationToken)) +#pragma warning restore CA1801 // Review unused parameters { BotAssert.ContextNotNull(turnContext); @@ -312,7 +316,9 @@ namespace Microsoft.Bot.Builder /// A task that represents the work queued to execute. /// or /// is null. +#pragma warning disable CA1801 // Review unused parameters (we can't change this without breaking binary compat) protected Task SetPropertyValueAsync(ITurnContext turnContext, string propertyName, object value, CancellationToken cancellationToken = default(CancellationToken)) +#pragma warning restore CA1801 // Review unused parameters { BotAssert.ContextNotNull(turnContext); @@ -329,7 +335,9 @@ namespace Microsoft.Bot.Builder /// /// Internal cached bot state. /// +#pragma warning disable CA1034 // Nested types should not be visible (we can't change this without breaking binary compat) public class CachedBotState +#pragma warning restore CA1034 // Nested types should not be visible { internal CachedBotState(IDictionary state = null) { @@ -337,19 +345,21 @@ namespace Microsoft.Bot.Builder Hash = ComputeHash(State); } +#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) public IDictionary State { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only internal string Hash { get; set; } + internal static string ComputeHash(object obj) + { + return JsonConvert.SerializeObject(obj); + } + internal bool IsChanged() { return Hash != ComputeHash(State); } - - internal string ComputeHash(object obj) - { - return JsonConvert.SerializeObject(obj); - } } /// diff --git a/libraries/Microsoft.Bot.Builder/BotStateSet.cs b/libraries/Microsoft.Bot.Builder/BotStateSet.cs index eb3167904..d571c80dd 100644 --- a/libraries/Microsoft.Bot.Builder/BotStateSet.cs +++ b/libraries/Microsoft.Bot.Builder/BotStateSet.cs @@ -27,7 +27,9 @@ namespace Microsoft.Bot.Builder /// Gets or sets the BotStates list for the BotStateSet. /// /// The BotState objects managed by this class. +#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) public List BotStates { get; set; } = new List(); +#pragma warning restore CA2227 // Collection properties should be read only /// /// Adds a bot state object to the set. diff --git a/libraries/Microsoft.Bot.Builder/ComponentRegistration.cs b/libraries/Microsoft.Bot.Builder/ComponentRegistration.cs index be8271f6a..ed6f13593 100644 --- a/libraries/Microsoft.Bot.Builder/ComponentRegistration.cs +++ b/libraries/Microsoft.Bot.Builder/ComponentRegistration.cs @@ -4,10 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Reflection; namespace Microsoft.Bot.Builder { @@ -33,9 +29,11 @@ namespace Microsoft.Bot.Builder /// ComponentRegistration.Add(new MyComponentRegistration()); /// /// +#pragma warning disable CA1052 // Static holder types should be Static or NotInheritable (we can't change this without breaking binary compat) public class ComponentRegistration +#pragma warning restore CA1052 // Static holder types should be Static or NotInheritable { - private static ConcurrentDictionary components = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _components = new ConcurrentDictionary(); /// /// Gets list of all ComponentRegistration objects registered. @@ -43,7 +41,7 @@ namespace Microsoft.Bot.Builder /// /// A numeration of ComponentRegistration objects. /// - public static IEnumerable Components => components.Values; + public static IEnumerable Components => _components.Values; /// /// Add a component which implements registration methods. @@ -52,7 +50,7 @@ namespace Microsoft.Bot.Builder /// componentRegistration. public static void Add(ComponentRegistration componentRegistration) { - components[componentRegistration.GetType()] = componentRegistration; + _components[componentRegistration.GetType()] = componentRegistration; } } } diff --git a/libraries/Microsoft.Bot.Builder/ConversationState.cs b/libraries/Microsoft.Bot.Builder/ConversationState.cs index c5db659b9..370bd0c49 100644 --- a/libraries/Microsoft.Bot.Builder/ConversationState.cs +++ b/libraries/Microsoft.Bot.Builder/ConversationState.cs @@ -37,8 +37,9 @@ namespace Microsoft.Bot.Builder /// is missing. protected override string GetStorageKey(ITurnContext turnContext) { - var channelId = turnContext.Activity.ChannelId ?? throw new ArgumentNullException("invalid activity-missing channelId"); - var conversationId = turnContext.Activity.Conversation?.Id ?? throw new ArgumentNullException("invalid activity-missing Conversation.Id"); + var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); + var conversationId = turnContext.Activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id"); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly return $"{channelId}/conversations/{conversationId}"; } } diff --git a/libraries/Microsoft.Bot.Builder/FileTranscriptLogger.cs b/libraries/Microsoft.Bot.Builder/FileTranscriptLogger.cs index f064ddd8b..9c8d44d5f 100644 --- a/libraries/Microsoft.Bot.Builder/FileTranscriptLogger.cs +++ b/libraries/Microsoft.Bot.Builder/FileTranscriptLogger.cs @@ -20,15 +20,15 @@ namespace Microsoft.Bot.Builder /// public class FileTranscriptLogger : ITranscriptStore { - private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings() + private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings() { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, }; - private string folder; - private bool unitTestMode; - private HashSet started = new HashSet(); + private readonly string _folder; + private readonly bool _unitTestMode; + private readonly HashSet _started = new HashSet(); /// /// Initializes a new instance of the class. @@ -49,8 +49,8 @@ namespace Microsoft.Bot.Builder Directory.CreateDirectory(folder); } - this.folder = folder; - this.unitTestMode = unitTestMode; + this._folder = folder; + this._unitTestMode = unitTestMode; } /// @@ -62,7 +62,7 @@ namespace Microsoft.Bot.Builder { if (activity == null) { - throw new ArgumentNullException(nameof(Activity)); + throw new ArgumentNullException(nameof(activity)); } var transcriptFile = GetTranscriptFile(activity.ChannelId, activity.Conversation.Id); @@ -81,40 +81,40 @@ namespace Microsoft.Bot.Builder { try { - if ((this.unitTestMode == true && !started.Contains(transcriptFile)) || !File.Exists(transcriptFile)) + if ((this._unitTestMode == true && !_started.Contains(transcriptFile)) || !File.Exists(transcriptFile)) { - System.Diagnostics.Trace.TraceInformation($"file://{transcriptFile.Replace("\\", "/")}"); - started.Add(transcriptFile); - List transcript = new List() { (Activity)activity }; + Trace.TraceInformation($"file://{transcriptFile.Replace("\\", "/")}"); + _started.Add(transcriptFile); + using (var stream = File.OpenWrite(transcriptFile)) { using (var writer = new StreamWriter(stream) as TextWriter) { - await writer.WriteAsync($"[{JsonConvert.SerializeObject(activity, jsonSettings)}]").ConfigureAwait(false); + await writer.WriteAsync($"[{JsonConvert.SerializeObject(activity, _jsonSettings)}]").ConfigureAwait(false); return; } } } - else + + switch (activity.Type) { - switch (activity.Type) - { - case ActivityTypes.MessageDelete: - await MessageDeleteAsync(activity, transcriptFile).ConfigureAwait(false); - return; + case ActivityTypes.MessageDelete: + await MessageDeleteAsync(activity, transcriptFile).ConfigureAwait(false); + return; - case ActivityTypes.MessageUpdate: - await MessageUpdateAsync(activity, transcriptFile).ConfigureAwait(false); - return; + case ActivityTypes.MessageUpdate: + await MessageUpdateAsync(activity, transcriptFile).ConfigureAwait(false); + return; - default: - // append - await LogActivityAsync(activity, transcriptFile).ConfigureAwait(false); - return; - } + default: + // append + await LogActivityAsync(activity, transcriptFile).ConfigureAwait(false); + return; } } +#pragma warning disable CA1031 // Do not catch general exception types (we ignore the exception and we retry) catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types { // try again } @@ -178,42 +178,9 @@ namespace Microsoft.Bot.Builder return Array.Empty(); } - private string GetTranscriptFile(string channelId, string conversationId) + private static async Task LogActivityAsync(IActivity activity, string transcriptFile) { - if (channelId == null) - { - throw new ArgumentNullException(channelId); - } - - if (conversationId == null) - { - throw new ArgumentNullException(nameof(conversationId)); - } - - var channelFolder = GetChannelFolder(channelId); - string transcriptFile = Path.Combine(channelFolder, conversationId + ".transcript"); - return transcriptFile; - } - - private string GetChannelFolder(string channelId) - { - if (channelId == null) - { - throw new ArgumentNullException(channelId); - } - - var channelFolder = Path.Combine(folder, channelId); - if (!Directory.Exists(channelFolder)) - { - Directory.CreateDirectory(channelFolder); - } - - return channelFolder; - } - - private async Task LogActivityAsync(IActivity activity, string transcriptFile) - { - var json = $",\n{JsonConvert.SerializeObject(activity, jsonSettings)}]"; + var json = $",\n{JsonConvert.SerializeObject(activity, _jsonSettings)}]"; using (var stream = File.Open(transcriptFile, FileMode.OpenOrCreate)) { @@ -229,7 +196,7 @@ namespace Microsoft.Bot.Builder } } - private async Task MessageUpdateAsync(IActivity activity, string transcriptFile) + private static async Task MessageUpdateAsync(IActivity activity, string transcriptFile) { // load all activities var transcript = await LoadTranscriptAsync(transcriptFile).ConfigureAwait(false); @@ -244,7 +211,7 @@ namespace Microsoft.Bot.Builder updatedActivity.LocalTimestamp = originalActivity.LocalTimestamp; updatedActivity.Timestamp = originalActivity.Timestamp; transcript[i] = updatedActivity; - var json = JsonConvert.SerializeObject(transcript, jsonSettings); + var json = JsonConvert.SerializeObject(transcript, _jsonSettings); using (var stream = File.OpenWrite(transcriptFile)) { using (var writer = new StreamWriter(stream) as TextWriter) @@ -257,7 +224,7 @@ namespace Microsoft.Bot.Builder } } - private async Task MessageDeleteAsync(IActivity activity, string transcriptFile) + private static async Task MessageDeleteAsync(IActivity activity, string transcriptFile) { // load all activities var transcript = await LoadTranscriptAsync(transcriptFile).ConfigureAwait(false); @@ -283,7 +250,7 @@ namespace Microsoft.Bot.Builder ServiceUrl = originalActivity.ServiceUrl, ReplyToId = originalActivity.ReplyToId, }; - var json = JsonConvert.SerializeObject(transcript, jsonSettings); + var json = JsonConvert.SerializeObject(transcript, _jsonSettings); using (var stream = File.OpenWrite(transcriptFile)) { using (var writer = new StreamWriter(stream) as TextWriter) @@ -295,5 +262,38 @@ namespace Microsoft.Bot.Builder } } } + + private string GetTranscriptFile(string channelId, string conversationId) + { + if (channelId == null) + { + throw new ArgumentNullException(channelId); + } + + if (conversationId == null) + { + throw new ArgumentNullException(nameof(conversationId)); + } + + var channelFolder = GetChannelFolder(channelId); + string transcriptFile = Path.Combine(channelFolder, conversationId + ".transcript"); + return transcriptFile; + } + + private string GetChannelFolder(string channelId) + { + if (channelId == null) + { + throw new ArgumentNullException(channelId); + } + + var channelFolder = Path.Combine(_folder, channelId); + if (!Directory.Exists(channelFolder)) + { + Directory.CreateDirectory(channelFolder); + } + + return channelFolder; + } } } diff --git a/libraries/Microsoft.Bot.Builder/HealthCheck.cs b/libraries/Microsoft.Bot.Builder/HealthCheck.cs index 26b2618c8..dcf1e985b 100644 --- a/libraries/Microsoft.Bot.Builder/HealthCheck.cs +++ b/libraries/Microsoft.Bot.Builder/HealthCheck.cs @@ -24,24 +24,39 @@ namespace Microsoft.Bot.Builder { // This is a mock secure SendToConversation to grab the exact HTTP headers. // If you have no appId and no secret this code will run but not produce an Authorization header. - var captureHandler = new CaptureRequestHandler(); - var client = new ConnectorClient(connector.BaseUri, connector.Credentials, captureHandler); - var activity = new Activity { Type = ActivityTypes.Message, Conversation = new ConversationAccount { Id = "capture" } }; - client.Conversations.SendToConversation(activity); - var headers = captureHandler.Request.Headers; - healthResults.Authorization = headers.Authorization?.ToString(); - healthResults.UserAgent = headers.UserAgent?.ToString(); + using (var captureHandler = new CaptureRequestHandler()) + { + using (var client = new ConnectorClient(connector.BaseUri, connector.Credentials, captureHandler)) + { + var activity = new Activity + { + Type = ActivityTypes.Message, + Conversation = new ConversationAccount { Id = "capture" } + }; + client.Conversations.SendToConversation(activity); + var headers = captureHandler.Request.Headers; + healthResults.Authorization = headers.Authorization?.ToString(); + healthResults.UserAgent = headers.UserAgent?.ToString(); + } + } } +#pragma warning disable CA1031 // Do not catch general exception types (ignoring, see comment in catch block) catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types { // This exception happens when you have a valid appId but invalid or blank secret. - // No callbacks will be possible, although the bot maybe healthy in other respects. } } var successMessage = "Health check succeeded."; - healthResults.Messages = healthResults.Authorization != null ? new[] { successMessage } : new[] { successMessage, "Callbacks are not authorized." }; + healthResults.Messages = healthResults.Authorization != null + ? new[] { successMessage } + : new[] + { + successMessage, + "Callbacks are not authorized." + }; return new HealthCheckResponse { HealthResults = healthResults }; } diff --git a/libraries/Microsoft.Bot.Builder/IMiddleware.cs b/libraries/Microsoft.Bot.Builder/IMiddleware.cs index 612d4b8a5..fb63fc48c 100644 --- a/libraries/Microsoft.Bot.Builder/IMiddleware.cs +++ b/libraries/Microsoft.Bot.Builder/IMiddleware.cs @@ -50,6 +50,8 @@ namespace Microsoft.Bot.Builder /// /// /// +#pragma warning disable CA1716 // Identifiers should not match keywords (we can't change this without breaking binary compat) Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken)); +#pragma warning restore CA1716 // Identifiers should not match keywords } } diff --git a/libraries/Microsoft.Bot.Builder/Inspection/InspectionMiddleware.cs b/libraries/Microsoft.Bot.Builder/Inspection/InspectionMiddleware.cs index e0f6c161e..de6c37b16 100644 --- a/libraries/Microsoft.Bot.Builder/Inspection/InspectionMiddleware.cs +++ b/libraries/Microsoft.Bot.Builder/Inspection/InspectionMiddleware.cs @@ -133,6 +133,32 @@ namespace Microsoft.Bot.Builder } } + private static string OpenCommand(InspectionSessionsByStatus sessions, ConversationReference conversationReference) + { + var sessionId = Guid.NewGuid().ToString(); + sessions.OpenedSessions.Add(sessionId, conversationReference); + return sessionId; + } + + private static bool AttachCommand(string attachId, InspectionSessionsByStatus sessions, string sessionId) + { + if (sessions.OpenedSessions.TryGetValue(sessionId, out var inspectionSessionState)) + { + sessions.AttachedSessions[attachId] = inspectionSessionState; + sessions.OpenedSessions.Remove(sessionId); + return true; + } + + return false; + } + + private static string GetAttachId(Activity activity) + { + // If we are running in a Microsoft Teams Team the conversation Id will reflect a particular thread the bot is in. + // So if we are in a Team then we will associate the "attach" with the Team Id rather than the more restrictive conversation Id. + return activity.TeamsGetTeamInfo()?.Id ?? activity.Conversation.Id; + } + private async Task ProcessOpenCommandAsync(ITurnContext turnContext, CancellationToken cancellationToken) { var accessor = _inspectionState.CreateProperty(nameof(InspectionSessionsByStatus)); @@ -166,25 +192,6 @@ namespace Microsoft.Bot.Builder await _inspectionState.SaveChangesAsync(turnContext, false, cancellationToken).ConfigureAwait(false); } - private string OpenCommand(InspectionSessionsByStatus sessions, ConversationReference conversationReference) - { - var sessionId = Guid.NewGuid().ToString(); - sessions.OpenedSessions.Add(sessionId, conversationReference); - return sessionId; - } - - private bool AttachCommand(string attachId, InspectionSessionsByStatus sessions, string sessionId) - { - if (sessions.OpenedSessions.TryGetValue(sessionId, out var inspectionSessionState)) - { - sessions.AttachedSessions[attachId] = inspectionSessionState; - sessions.OpenedSessions.Remove(sessionId); - return true; - } - - return false; - } - private async Task FindSessionAsync(ITurnContext turnContext, CancellationToken cancellationToken) { var accessor = _inspectionState.CreateProperty(nameof(InspectionSessionsByStatus)); @@ -218,12 +225,5 @@ namespace Microsoft.Bot.Builder openSessions.AttachedSessions.Remove(GetAttachId(turnContext.Activity)); await _inspectionState.SaveChangesAsync(turnContext, false, cancellationToken).ConfigureAwait(false); } - - private string GetAttachId(Activity activity) - { - // If we are running in a Microsoft Teams Team the conversation Id will reflect a particular thread the bot is in. - // So if we are in a Team then we will associate the "attach" with the Team Id rather than the more restrictive conversation Id. - return activity.TeamsGetTeamInfo()?.Id ?? activity.Conversation.Id; - } } } diff --git a/libraries/Microsoft.Bot.Builder/Inspection/InspectionSession.cs b/libraries/Microsoft.Bot.Builder/Inspection/InspectionSession.cs index 2d2cd25f7..9bb0697c5 100644 --- a/libraries/Microsoft.Bot.Builder/Inspection/InspectionSession.cs +++ b/libraries/Microsoft.Bot.Builder/Inspection/InspectionSession.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using System; using System.Net.Http; @@ -12,11 +12,11 @@ using Microsoft.Extensions.Logging; namespace Microsoft.Bot.Builder { - internal class InspectionSession + internal class InspectionSession : IDisposable { + private readonly ConnectorClient _connectorClient; private readonly ConversationReference _conversationReference; private readonly ILogger _logger; - private readonly ConnectorClient _connectorClient; public InspectionSession(ConversationReference conversationReference, MicrosoftAppCredentials credentials, HttpClient httpClient, ILogger logger) { @@ -31,15 +31,22 @@ namespace Microsoft.Bot.Builder try { - var resourceResponse = await _connectorClient.Conversations.SendToConversationAsync(activity, cancellationToken).ConfigureAwait(false); + await _connectorClient.Conversations.SendToConversationAsync(activity, cancellationToken).ConfigureAwait(false); } - catch (Exception err) +#pragma warning disable CA1031 // Do not catch general exception types (we just log the exception in this case and return false) + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - _logger.LogWarning($"Exception '{err.ToString()}' while attempting to call Emulator for inspection, check it is running, and you have correct credentials in the Emulator and the InspectionMiddleware."); + _logger.LogWarning($"Exception '{ex}' while attempting to call Emulator for inspection, check it is running, and you have correct credentials in the Emulator and the InspectionMiddleware."); return false; } return true; } + + public void Dispose() + { + _connectorClient?.Dispose(); + } } } diff --git a/libraries/Microsoft.Bot.Builder/Inspection/InterceptionMiddleware.cs b/libraries/Microsoft.Bot.Builder/Inspection/InterceptionMiddleware.cs index 866e8debf..8f1aeb050 100644 --- a/libraries/Microsoft.Bot.Builder/Inspection/InterceptionMiddleware.cs +++ b/libraries/Microsoft.Bot.Builder/Inspection/InterceptionMiddleware.cs @@ -19,7 +19,7 @@ namespace Microsoft.Bot.Builder Logger = logger ?? NullLogger.Instance; } - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; } async Task IMiddleware.OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken) { @@ -58,7 +58,7 @@ namespace Microsoft.Bot.Builder catch (Exception e) { await InvokeTraceExceptionAsync(turnContext, e.TraceActivity(), cancellationToken).ConfigureAwait(false); - throw e; + throw; } } @@ -80,9 +80,11 @@ namespace Microsoft.Bot.Builder { return await InboundAsync(turnContext, traceActivity, cancellationToken).ConfigureAwait(false); } - catch (Exception e) +#pragma warning disable CA1031 // Do not catch general exception types (exception are logged and ignored) + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - Logger.LogWarning($"Exception in inbound interception {e.Message}"); + Logger.LogWarning($"Exception in inbound interception {ex.Message}"); return (true, false); } } @@ -93,9 +95,11 @@ namespace Microsoft.Bot.Builder { await OutboundAsync(turnContext, traceActivities, cancellationToken).ConfigureAwait(false); } - catch (Exception e) +#pragma warning disable CA1031 // Do not catch general exception types (exception are logged and ignored) + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - Logger.LogWarning($"Exception in outbound interception {e.Message}"); + Logger.LogWarning($"Exception in outbound interception {ex.Message}"); } } @@ -110,9 +114,11 @@ namespace Microsoft.Bot.Builder { await TraceStateAsync(turnContext, cancellationToken).ConfigureAwait(false); } - catch (Exception e) +#pragma warning disable CA1031 // Do not catch general exception types (exception are logged and ignored) + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - Logger.LogWarning($"Exception in state interception {e.Message}"); + Logger.LogWarning($"Exception in state interception {ex.Message}"); } } @@ -122,9 +128,11 @@ namespace Microsoft.Bot.Builder { await OutboundAsync(turnContext, new Activity[] { traceActivity }, cancellationToken).ConfigureAwait(false); } - catch (Exception e) +#pragma warning disable CA1031 // Do not catch general exception types (exception are logged and ignored) + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - Logger.LogWarning($"Exception in exception interception {e.Message}"); + Logger.LogWarning($"Exception in exception interception {ex.Message}"); } } } diff --git a/libraries/Microsoft.Bot.Builder/Integration/MessageSerializerSettings.cs b/libraries/Microsoft.Bot.Builder/Integration/MessageSerializerSettings.cs index c0dde1085..4d636c696 100644 --- a/libraries/Microsoft.Bot.Builder/Integration/MessageSerializerSettings.cs +++ b/libraries/Microsoft.Bot.Builder/Integration/MessageSerializerSettings.cs @@ -11,8 +11,10 @@ namespace Microsoft.Bot.Builder.Integration { public static JsonSerializerSettings Create() { - var connector = new ConnectorClient(new Uri("http://localhost/")); - return connector.DeserializationSettings; + using (var connector = new ConnectorClient(new Uri("http://localhost/"))) + { + return connector.DeserializationSettings; + } } } } diff --git a/libraries/Microsoft.Bot.Builder/IntentScore.cs b/libraries/Microsoft.Bot.Builder/IntentScore.cs index 14b33c9e4..ad7cd1503 100644 --- a/libraries/Microsoft.Bot.Builder/IntentScore.cs +++ b/libraries/Microsoft.Bot.Builder/IntentScore.cs @@ -26,6 +26,8 @@ namespace Microsoft.Bot.Builder /// Any extra properties to include in the results. /// [JsonExtensionData(ReadData = true, WriteData = true)] +#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) public IDictionary Properties { get; set; } = new Dictionary(); +#pragma warning restore CA2227 // Collection properties should be read only } } diff --git a/libraries/Microsoft.Bot.Builder/MemoryStorage.cs b/libraries/Microsoft.Bot.Builder/MemoryStorage.cs index 35331ed70..97597ff21 100644 --- a/libraries/Microsoft.Bot.Builder/MemoryStorage.cs +++ b/libraries/Microsoft.Bot.Builder/MemoryStorage.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; @@ -160,7 +161,7 @@ namespace Microsoft.Bot.Builder throw new Exception($"Etag conflict.\r\n\r\nOriginal: {newStoreItem.ETag}\r\nCurrent: {oldStateETag}"); } - newState["eTag"] = (_eTag++).ToString(); + newState["eTag"] = (_eTag++).ToString(CultureInfo.InvariantCulture); } _memory[change.Key] = newState; diff --git a/libraries/Microsoft.Bot.Builder/MemoryTranscriptStore.cs b/libraries/Microsoft.Bot.Builder/MemoryTranscriptStore.cs index fcdce872e..a6cedc59d 100644 --- a/libraries/Microsoft.Bot.Builder/MemoryTranscriptStore.cs +++ b/libraries/Microsoft.Bot.Builder/MemoryTranscriptStore.cs @@ -29,7 +29,7 @@ namespace Microsoft.Bot.Builder { if (activity == null) { - throw new ArgumentNullException("activity cannot be null for LogActivity()"); + throw new ArgumentNullException(nameof(activity), "activity cannot be null for LogActivity()"); } lock (_channels) @@ -142,7 +142,7 @@ namespace Microsoft.Bot.Builder .Take(20) .ToArray(); - if (pagedResult.Items.Count() == 20) + if (pagedResult.Items.Length == 20) { pagedResult.ContinuationToken = pagedResult.Items.Last().Id; } @@ -155,7 +155,7 @@ namespace Microsoft.Bot.Builder .Take(20) .ToArray(); - if (pagedResult.Items.Count() == 20) + if (pagedResult.Items.Length == 20) { pagedResult.ContinuationToken = pagedResult.Items.Last().Id; } @@ -232,7 +232,7 @@ namespace Microsoft.Bot.Builder .Take(20) .ToArray(); - if (pagedResult.Items.Count() == 20) + if (pagedResult.Items.Length == 20) { pagedResult.ContinuationToken = pagedResult.Items.Last().Id; } @@ -250,7 +250,7 @@ namespace Microsoft.Bot.Builder .Take(20) .ToArray(); - if (pagedResult.Items.Count() == 20) + if (pagedResult.Items.Length == 20) { pagedResult.ContinuationToken = pagedResult.Items.Last().Id; } diff --git a/libraries/Microsoft.Bot.Builder/Microsoft.Bot.Builder.csproj b/libraries/Microsoft.Bot.Builder/Microsoft.Bot.Builder.csproj index 06847afa1..7b813736c 100644 --- a/libraries/Microsoft.Bot.Builder/Microsoft.Bot.Builder.csproj +++ b/libraries/Microsoft.Bot.Builder/Microsoft.Bot.Builder.csproj @@ -34,6 +34,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/libraries/Microsoft.Bot.Builder/PagedResult.cs b/libraries/Microsoft.Bot.Builder/PagedResult.cs index 5b1651c88..786864bc6 100644 --- a/libraries/Microsoft.Bot.Builder/PagedResult.cs +++ b/libraries/Microsoft.Bot.Builder/PagedResult.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; + namespace Microsoft.Bot.Builder { /// @@ -15,7 +17,9 @@ namespace Microsoft.Bot.Builder /// /// The array of items. /// - public T[] Items { get; set; } = new T[0]; +#pragma warning disable CA1819 // Properties should not return arrays (can't change this without breaking binary compat) + public T[] Items { get; set; } = Array.Empty(); +#pragma warning restore CA1819 // Properties should not return arrays /// /// Gets or sets a token for retrieving the next page of results. diff --git a/libraries/Microsoft.Bot.Builder/PrivateConversationState.cs b/libraries/Microsoft.Bot.Builder/PrivateConversationState.cs index 12731128c..03fc959e9 100644 --- a/libraries/Microsoft.Bot.Builder/PrivateConversationState.cs +++ b/libraries/Microsoft.Bot.Builder/PrivateConversationState.cs @@ -39,9 +39,9 @@ namespace Microsoft.Bot.Builder /// protected override string GetStorageKey(ITurnContext turnContext) { - var channelId = turnContext.Activity.ChannelId ?? throw new ArgumentNullException("invalid activity-missing channelId"); - var conversationId = turnContext.Activity.Conversation?.Id ?? throw new ArgumentNullException("invalid activity-missing Conversation.Id"); - var userId = turnContext.Activity.From?.Id ?? throw new ArgumentNullException("invalid activity-missing From.Id"); + var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); + var conversationId = turnContext.Activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id"); + var userId = turnContext.Activity.From?.Id ?? throw new InvalidOperationException("invalid activity-missing From.Id"); return $"{channelId}/conversations/{conversationId}/users/{userId}"; } } diff --git a/libraries/Microsoft.Bot.Builder/RecognizerResult.cs b/libraries/Microsoft.Bot.Builder/RecognizerResult.cs index d482c7e5c..75785836c 100644 --- a/libraries/Microsoft.Bot.Builder/RecognizerResult.cs +++ b/libraries/Microsoft.Bot.Builder/RecognizerResult.cs @@ -38,7 +38,9 @@ namespace Microsoft.Bot.Builder /// Mapping from intent to information about the intent. /// [JsonProperty("intents")] +#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) public IDictionary Intents { get; set; } = new Dictionary(); +#pragma warning restore CA2227 // Collection properties should be read only /// /// Gets or sets the recognized top-level entities. @@ -47,7 +49,9 @@ namespace Microsoft.Bot.Builder /// Object with each top-level recognized entity as a key. /// [JsonProperty("entities")] +#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) public JObject Entities { get; set; } = new JObject(); +#pragma warning restore CA2227 // Collection properties should be read only /// /// Gets or sets properties that are not otherwise defined by the type but that @@ -58,7 +62,9 @@ namespace Microsoft.Bot.Builder /// the JSON object is deserialized, but are instead stored in this property. Such properties /// will be written to a JSON object when the instance is serialized. [JsonExtensionData(ReadData = true, WriteData = true)] +#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) public IDictionary Properties { get; set; } = new Dictionary(); +#pragma warning restore CA2227 // Collection properties should be read only /// public void Convert(dynamic result) diff --git a/libraries/Microsoft.Bot.Builder/RecognizerResultExtensions.cs b/libraries/Microsoft.Bot.Builder/RecognizerResultExtensions.cs index 0a0978930..597f2d4a4 100644 --- a/libraries/Microsoft.Bot.Builder/RecognizerResultExtensions.cs +++ b/libraries/Microsoft.Bot.Builder/RecognizerResultExtensions.cs @@ -24,7 +24,7 @@ namespace Microsoft.Bot.Builder if (result.Intents == null) { - throw new ArgumentNullException(nameof(result.Intents)); + throw new InvalidOperationException(nameof(result.Intents)); } var topIntent = (string.Empty, 0.0d); diff --git a/libraries/Microsoft.Bot.Builder/RegisterClassMiddleware.cs b/libraries/Microsoft.Bot.Builder/RegisterClassMiddleware.cs index 5fec5c935..b049c787b 100644 --- a/libraries/Microsoft.Bot.Builder/RegisterClassMiddleware.cs +++ b/libraries/Microsoft.Bot.Builder/RegisterClassMiddleware.cs @@ -10,7 +10,7 @@ namespace Microsoft.Bot.Builder public class RegisterClassMiddleware : IMiddleware where T : class { - private string key; + private readonly string _key; /// /// Initializes a new instance of the class. @@ -29,7 +29,7 @@ namespace Microsoft.Bot.Builder public RegisterClassMiddleware(T service, string key) { this.Service = service; - this.key = key; + this._key = key; } /// @@ -53,9 +53,9 @@ namespace Microsoft.Bot.Builder public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate nextTurn, CancellationToken cancellationToken) { // Register service - if (this.key != null) + if (this._key != null) { - turnContext.TurnState.Add(this.key, this.Service); + turnContext.TurnState.Add(this._key, this.Service); } else { diff --git a/libraries/Microsoft.Bot.Builder/ShowTypingMiddleware.cs b/libraries/Microsoft.Bot.Builder/ShowTypingMiddleware.cs index 94b7903e1..09a80dedd 100644 --- a/libraries/Microsoft.Bot.Builder/ShowTypingMiddleware.cs +++ b/libraries/Microsoft.Bot.Builder/ShowTypingMiddleware.cs @@ -55,26 +55,27 @@ namespace Microsoft.Bot.Builder /// public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken) { - CancellationTokenSource cts = null; - try + using (var cts = new CancellationTokenSource()) { - // If the incoming activity is a MessageActivity, start a timer to periodically send the typing activity - if (turnContext.Activity.Type == ActivityTypes.Message) + Task typingTask = null; + try { - cts = new CancellationTokenSource(); - cancellationToken.Register(() => cts.Cancel()); + // If the incoming activity is a MessageActivity, start a timer to periodically send the typing activity. + if (turnContext.Activity.Type == ActivityTypes.Message) + { + // do not await task - we want this to run in the background and we will cancel it when its done + typingTask = SendTypingAsync(turnContext, _delay, _period, cts.Token); + } - // do not await task - we want this to run in the background and we will cancel it when its done - var task = Task.Run(() => SendTypingAsync(turnContext, _delay, _period, cts.Token), cancellationToken); + await next(cancellationToken).ConfigureAwait(false); } - - await next(cancellationToken).ConfigureAwait(false); - } - finally - { - if (cts != null) + finally { - cts.Cancel(); + if (typingTask != null && !typingTask.IsCanceled) + { + // Cancel the typing loop. + cts.Cancel(); + } } } } @@ -96,7 +97,7 @@ namespace Microsoft.Bot.Builder await Task.Delay(period, cancellationToken).ConfigureAwait(false); } } - catch (TaskCanceledException) + catch (OperationCanceledException) { // do nothing } diff --git a/libraries/Microsoft.Bot.Builder/Streaming/BotFrameworkHttpAdapterBase.cs b/libraries/Microsoft.Bot.Builder/Streaming/BotFrameworkHttpAdapterBase.cs index d872909f8..d72d699be 100644 --- a/libraries/Microsoft.Bot.Builder/Streaming/BotFrameworkHttpAdapterBase.cs +++ b/libraries/Microsoft.Bot.Builder/Streaming/BotFrameworkHttpAdapterBase.cs @@ -62,7 +62,9 @@ namespace Microsoft.Bot.Builder.Streaming /// /// The request handlers for this adapter. /// +#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) protected IList RequestHandlers { get; set; } = new List(); +#pragma warning restore CA2227 // Collection properties should be read only /// /// Primary adapter method for processing activities sent from streaming channel. @@ -128,7 +130,7 @@ namespace Microsoft.Bot.Builder.Streaming // Check to see if any of this adapter's StreamingRequestHandlers is associated with this conversation. var possibleHandlers = RequestHandlers.Where(x => x.ServiceUrl == activity.ServiceUrl).Where(y => y.HasConversation(activity.Conversation.Id)); - if (possibleHandlers.Count() > 0) + if (possibleHandlers.Any()) { if (possibleHandlers.Count() > 1) { @@ -147,32 +149,32 @@ namespace Microsoft.Bot.Builder.Streaming return await possibleHandlers.First().SendActivityAsync(activity, cancellationToken).ConfigureAwait(false); } - else + + if (ConnectedBot != null) { - if (ConnectedBot != null) + // This is a proactive message that will need a new streaming connection opened. + // The ServiceUrl of a streaming connection follows the pattern "urn:[ChannelName]:[Protocol]:[Host]". +#pragma warning disable CA2000 // Dispose objects before losing scope (we can't fix this without closing the socket connection, this should be addressed after we make StreamingRequestHandler disposable and we dispose the connector ) + var connection = new ClientWebSocket(); +#pragma warning restore CA2000 // Dispose objects before losing scope + var uri = activity.ServiceUrl.Split(':'); + var protocol = uri[uri.Length - 2]; + var host = uri[uri.Length - 1]; + await connection.ConnectAsync(new Uri(protocol + host + "/api/messages"), cancellationToken).ConfigureAwait(false); + + var handler = new StreamingRequestHandler(ConnectedBot, this, connection, Logger); + + if (RequestHandlers == null) { - // This is a proactive message that will need a new streaming connection opened. - // The ServiceUrl of a streaming connection follows the pattern "urn:[ChannelName]:[Protocol]:[Host]". - var connection = new ClientWebSocket(); - var uri = activity.ServiceUrl.Split(':'); - var protocol = uri[uri.Length - 2]; - var host = uri[uri.Length - 1]; - await connection.ConnectAsync(new Uri(protocol + host + "/api/messages"), cancellationToken).ConfigureAwait(false); - - var handler = new StreamingRequestHandler(ConnectedBot, this, connection, Logger); - - if (RequestHandlers == null) - { - RequestHandlers = new List(); - } - - RequestHandlers.Add(handler); - - return await handler.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false); + RequestHandlers = new List(); } - return null; + RequestHandlers.Add(handler); + + return await handler.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false); } + + return null; } /// @@ -228,7 +230,7 @@ namespace Microsoft.Bot.Builder.Streaming // information unique to streaming connections. Now that we know that this is a streaming // activity, process it in the streaming pipeline. // Process streaming activity. - return await SendStreamingActivityAsync(activity).ConfigureAwait(false); + return await SendStreamingActivityAsync(activity, cancellationToken).ConfigureAwait(false); } /// @@ -239,7 +241,9 @@ namespace Microsoft.Bot.Builder.Streaming var emptyCredentials = (ChannelProvider != null && ChannelProvider.IsGovernment()) ? MicrosoftGovernmentAppCredentials.Empty : MicrosoftAppCredentials.Empty; +#pragma warning disable CA2000 // Dispose objects before losing scope (We need to make ConnectorClient disposable to fix this, ignoring it for now) var streamingClient = new StreamingHttpClient(requestHandler, Logger); +#pragma warning restore CA2000 // Dispose objects before losing scope var connectorClient = new ConnectorClient(new Uri(activity.ServiceUrl), emptyCredentials, customHttpClient: streamingClient); return connectorClient; } diff --git a/libraries/Microsoft.Bot.Builder/Streaming/StreamingHttpClient.cs b/libraries/Microsoft.Bot.Builder/Streaming/StreamingHttpClient.cs index cfb151fda..0bd42dcbb 100644 --- a/libraries/Microsoft.Bot.Builder/Streaming/StreamingHttpClient.cs +++ b/libraries/Microsoft.Bot.Builder/Streaming/StreamingHttpClient.cs @@ -14,7 +14,7 @@ namespace Microsoft.Bot.Builder.Streaming { internal class StreamingHttpClient : HttpClient { - private StreamingRequestHandler _requestHandler; + private readonly StreamingRequestHandler _requestHandler; private readonly ILogger _logger; /// @@ -29,19 +29,19 @@ namespace Microsoft.Bot.Builder.Streaming _logger = logger ?? NullLogger.Instance; } - public override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) + public override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var streamingRequest = new StreamingRequest { - Path = request.RequestUri.OriginalString.Substring(request.RequestUri.OriginalString.IndexOf("/v3")), + Path = request.RequestUri.OriginalString.Substring(request.RequestUri.OriginalString.IndexOf("/v3", StringComparison.Ordinal)), Verb = request.Method.ToString(), }; streamingRequest.SetBody(request.Content); - return await this.SendRequestAsync(streamingRequest, cancellationToken).ConfigureAwait(false); + return await SendRequestAsync(streamingRequest, cancellationToken).ConfigureAwait(false); } - public async Task SendAsync(StreamingRequest streamingRequest, CancellationToken cancellationToken = default) => await this._requestHandler.SendStreamingRequestAsync(streamingRequest, cancellationToken).ConfigureAwait(false); + public async Task SendAsync(StreamingRequest streamingRequest, CancellationToken cancellationToken = default) => await _requestHandler.SendStreamingRequestAsync(streamingRequest, cancellationToken).ConfigureAwait(false); private async Task SendRequestAsync(StreamingRequest request, CancellationToken cancellation = default) { @@ -59,9 +59,11 @@ namespace Microsoft.Bot.Builder.Streaming return serverResponse.ReadBodyAsJson(); } } +#pragma warning disable CA1031 // Do not catch general exception types (we just log the exception and continue) catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - this._logger.LogError(ex.ToString()); + _logger.LogError(ex.ToString()); } return default; diff --git a/libraries/Microsoft.Bot.Builder/Streaming/StreamingRequestHandler.cs b/libraries/Microsoft.Bot.Builder/Streaming/StreamingRequestHandler.cs index eebf4e8d2..82086ac6d 100644 --- a/libraries/Microsoft.Bot.Builder/Streaming/StreamingRequestHandler.cs +++ b/libraries/Microsoft.Bot.Builder/Streaming/StreamingRequestHandler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -30,8 +31,8 @@ namespace Microsoft.Bot.Builder.Streaming private readonly IStreamingActivityProcessor _activityProcessor; private readonly string _userAgent; private readonly IDictionary _conversations; + private readonly IStreamingTransportServer _server; - private IStreamingTransportServer _server; private bool _serverIsConnected; /// @@ -92,7 +93,9 @@ namespace Microsoft.Bot.Builder.Streaming /// /// The URL of the channel endpoint this StreamingRequestHandler receives requests from. /// +#pragma warning disable CA1056 // Uri properties should not be strings (we can't change this without breaking binary compat) public string ServiceUrl { get; private set; } +#pragma warning restore CA1056 // Uri properties should not be strings /// /// Begins listening for incoming requests over this StreamingRequestHandler's server. @@ -123,7 +126,7 @@ namespace Microsoft.Bot.Builder.Streaming /// the conversation was added to this . public DateTime ConversationAddedTime(string conversationId) { - if (!_conversations.TryGetValue(conversationId, out DateTime addedTime)) + if (!_conversations.TryGetValue(conversationId, out var addedTime)) { addedTime = DateTime.MinValue; } @@ -146,7 +149,7 @@ namespace Microsoft.Bot.Builder.Streaming var response = new StreamingResponse(); // We accept all POSTs regardless of path, but anything else requires special treatment. - if (!string.Equals(request?.Verb, StreamingRequest.POST, StringComparison.InvariantCultureIgnoreCase)) + if (!string.Equals(request?.Verb, StreamingRequest.POST, StringComparison.OrdinalIgnoreCase)) { return HandleCustomPaths(request, response); } @@ -157,7 +160,9 @@ namespace Microsoft.Bot.Builder.Streaming { body = request.ReadBodyAsString(); } +#pragma warning disable CA1031 // Do not catch general exception types (we log the exception and continue execution) catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { response.StatusCode = (int)HttpStatusCode.BadRequest; _logger.LogError("Request body missing or malformed: " + ex.Message); @@ -225,7 +230,9 @@ namespace Microsoft.Bot.Builder.Streaming } } } +#pragma warning disable CA1031 // Do not catch general exception types (we logging the error and we send it back in the body of the response) catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { response.StatusCode = (int)HttpStatusCode.InternalServerError; response.SetBody(ex.ToString()); @@ -279,7 +286,9 @@ namespace Microsoft.Bot.Builder.Streaming return serverResponse.ReadBodyAsJson(); } } +#pragma warning disable CA1031 // Do not catch general exception types (this should probably be addressed later, but for now we just log the error and continue the execution) catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { _logger.LogError(ex.Message); } @@ -309,7 +318,9 @@ namespace Microsoft.Bot.Builder.Streaming return serverResponse.ReadBodyAsJson(); } } +#pragma warning disable CA1031 // Do not catch general exception types (this should probably be addressed later, but for now we just log the error and continue the execution) catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { _logger.LogError(ex.Message); } @@ -327,15 +338,21 @@ namespace Microsoft.Bot.Builder.Streaming /// https://github.com/Microsoft/botbuilder-dotnet/blob/d342cd66d159a023ac435aec0fdf791f93118f5f/doc/UserAgents.md. /// /// A string containing versioning information. - private static string GetUserAgent() => - string.Format( - "Microsoft-BotFramework/3.1 Streaming-Extensions/1.0 BotBuilder/{0} ({1}; {2}; {3})", - ConnectorClient.GetClientVersion(new ConnectorClient(new Uri("http://localhost"))), - ConnectorClient.GetASPNetVersion(), - ConnectorClient.GetOsVersion(), - ConnectorClient.GetArchitecture()); + private static string GetUserAgent() + { + using (var connectorClient = new ConnectorClient(new Uri("http://localhost"))) + { + return string.Format( + CultureInfo.InvariantCulture, + "Microsoft-BotFramework/3.1 Streaming-Extensions/1.0 BotBuilder/{0} ({1}; {2}; {3})", + ConnectorClient.GetClientVersion(connectorClient), + ConnectorClient.GetASPNetVersion(), + ConnectorClient.GetOsVersion(), + ConnectorClient.GetArchitecture()); + } + } - private IEnumerable UpdateAttachmentStreams(Activity activity) + private static IEnumerable UpdateAttachmentStreams(Activity activity) { if (activity == null || activity.Attachments == null) { @@ -379,8 +396,8 @@ namespace Microsoft.Bot.Builder.Streaming return response; } - if (string.Equals(request.Verb, StreamingRequest.GET, StringComparison.InvariantCultureIgnoreCase) && - string.Equals(request.Path, "/api/version", StringComparison.InvariantCultureIgnoreCase)) + if (string.Equals(request.Verb, StreamingRequest.GET, StringComparison.OrdinalIgnoreCase) && + string.Equals(request.Path, "/api/version", StringComparison.OrdinalIgnoreCase)) { response.StatusCode = (int)HttpStatusCode.OK; response.SetBody(new VersionInfo() { UserAgent = _userAgent }); diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs index b362f3b93..72836e47b 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs @@ -20,13 +20,17 @@ namespace Microsoft.Bot.Builder.Teams public static async Task GetTeamDetailsAsync(ITurnContext turnContext, string teamId = null, CancellationToken cancellationToken = default) { var t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team."); +#pragma warning disable CA2000 // Dispose objects before losing scope (we need to review this, disposing the connectorClient may have unintended consequences) return await GetTeamsConnectorClient(turnContext).Teams.FetchTeamDetailsAsync(t, cancellationToken).ConfigureAwait(false); +#pragma warning restore CA2000 // Dispose objects before losing scope } public static async Task> GetTeamChannelsAsync(ITurnContext turnContext, string teamId = null, CancellationToken cancellationToken = default) { var t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team."); +#pragma warning disable CA2000 // Dispose objects before losing scope (we need to review this, disposing the connectorClient may have unintended consequences) var channelList = await GetTeamsConnectorClient(turnContext).Teams.FetchChannelListAsync(t, cancellationToken).ConfigureAwait(false); +#pragma warning restore CA2000 // Dispose objects before losing scope return channelList.Conversations; } @@ -102,7 +106,7 @@ namespace Microsoft.Bot.Builder.Teams if (turnContext.Activity == null) { - throw new ArgumentNullException(nameof(turnContext.Activity)); + throw new InvalidOperationException(nameof(turnContext.Activity)); } if (string.IsNullOrEmpty(teamsChannelId)) diff --git a/libraries/Microsoft.Bot.Builder/TelemetryLoggerMiddleware.cs b/libraries/Microsoft.Bot.Builder/TelemetryLoggerMiddleware.cs index e10020c33..584b879f4 100644 --- a/libraries/Microsoft.Bot.Builder/TelemetryLoggerMiddleware.cs +++ b/libraries/Microsoft.Bot.Builder/TelemetryLoggerMiddleware.cs @@ -324,7 +324,9 @@ namespace Microsoft.Bot.Builder /// The Activity object deleted by bot. /// Additional properties to add to the event. /// The properties and their values to log when the bot deletes a message it sent previously. +#pragma warning disable CA1822 // Mark members as static (can't change this without breaking binary compat) protected Task> FillDeleteEventPropertiesAsync(IMessageDeleteActivity activity, Dictionary additionalProperties = null) +#pragma warning restore CA1822 // Mark members as static { var properties = new Dictionary() { diff --git a/libraries/Microsoft.Bot.Builder/TokenResolver.cs b/libraries/Microsoft.Bot.Builder/TokenResolver.cs index 23ea3a280..9f62c9fbf 100644 --- a/libraries/Microsoft.Bot.Builder/TokenResolver.cs +++ b/libraries/Microsoft.Bot.Builder/TokenResolver.cs @@ -13,7 +13,6 @@ using Microsoft.Bot.Connector; using Microsoft.Bot.Connector.Authentication; using Microsoft.Bot.Schema; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; namespace Microsoft.Bot.Builder { @@ -23,69 +22,63 @@ namespace Microsoft.Bot.Builder public static void CheckForOAuthCards(BotFrameworkAdapter adapter, ILogger logger, ITurnContext turnContext, Activity activity, CancellationToken cancellationToken) { - if (activity == null || activity.Attachments == null) + if (activity?.Attachments == null) { return; } + var pollTokenTasks = new List(); foreach (var attachment in activity.Attachments.Where(a => a.ContentType == OAuthCard.ContentType)) { - OAuthCard oauthCard = attachment.Content as OAuthCard; - if (oauthCard != null) + if (attachment.Content is OAuthCard oauthCard) { if (string.IsNullOrWhiteSpace(oauthCard.ConnectionName)) { throw new InvalidOperationException("The OAuthPrompt's ConnectionName property is missing a value."); } - // Poll as a background task - Task.Run(() => PollForTokenAsync(adapter, logger, turnContext, activity, oauthCard.ConnectionName, cancellationToken)) - .ContinueWith(t => - { - if (t.Exception != null) - { - logger.LogError(t.Exception.InnerException ?? t.Exception, "PollForTokenAsync threw an exception", oauthCard.ConnectionName); - } - }); + // Poll as a background task and add to the list (we don't call await here, we await all the calls together later). + pollTokenTasks.Add(PollForTokenAsync(adapter, logger, turnContext, oauthCard.ConnectionName, cancellationToken)); } } + + if (pollTokenTasks.Any()) + { + // Wait for all the poll operations to complete. + Task.WaitAll(pollTokenTasks.ToArray()); + } } - private static async Task PollForTokenAsync(BotFrameworkAdapter adapter, ILogger logger, ITurnContext turnContext, Activity activity, string connectionName, CancellationToken cancellationToken) + private static async Task PollForTokenAsync(BotFrameworkAdapter adapter, ILogger logger, ITurnContext turnContext, string connectionName, CancellationToken cancellationToken) { - TokenResponse tokenResponse = null; - bool shouldEndPolling = false; - var pollingTimeout = TurnStateConstants.OAuthLoginTimeoutValue; - var pollingRequestsInterval = PollingInterval; - var loginTimeout = turnContext.TurnState.Get(TurnStateConstants.OAuthLoginTimeoutKey); - bool sentToken = false; - - // Override login timeout with value set from the OAuthPrompt or by the developer - if (turnContext.TurnState.ContainsKey(TurnStateConstants.OAuthLoginTimeoutKey)) + try { - pollingTimeout = (TimeSpan)turnContext.TurnState.Get(TurnStateConstants.OAuthLoginTimeoutKey); - } + var shouldEndPolling = false; + var pollingTimeout = TurnStateConstants.OAuthLoginTimeoutValue; + var pollingRequestsInterval = PollingInterval; + var sentToken = false; - var stopwatch = Stopwatch.StartNew(); - var oauthClient = turnContext.TurnState.Get(); - - while (stopwatch.Elapsed < pollingTimeout && !shouldEndPolling) - { - tokenResponse = await adapter.GetUserTokenAsync(turnContext, oauthClient?.Credentials as AppCredentials, connectionName, null, cancellationToken).ConfigureAwait(false); - - if (tokenResponse != null) + // Override login timeout with value set from the OAuthPrompt or by the developer + if (turnContext.TurnState.ContainsKey(TurnStateConstants.OAuthLoginTimeoutKey)) { - // This can be used to short-circuit the polling loop. - if (tokenResponse.Properties != null) + pollingTimeout = (TimeSpan)turnContext.TurnState.Get(TurnStateConstants.OAuthLoginTimeoutKey); + } + + var stopwatch = Stopwatch.StartNew(); + var oauthClient = turnContext.TurnState.Get(); + + while (stopwatch.Elapsed < pollingTimeout && !shouldEndPolling) + { + var tokenResponse = await adapter.GetUserTokenAsync(turnContext, oauthClient?.Credentials as AppCredentials, connectionName, null, cancellationToken).ConfigureAwait(false); + + if (tokenResponse != null) { - JToken tokenPollingSettingsToken = null; - TokenPollingSettings tokenPollingSettings = null; - tokenResponse.Properties.TryGetValue(TurnStateConstants.TokenPollingSettingsKey, out tokenPollingSettingsToken); - - if (tokenPollingSettingsToken != null) + // This can be used to short-circuit the polling loop. + if (tokenResponse.Properties != null) { - tokenPollingSettings = tokenPollingSettingsToken.ToObject(); + tokenResponse.Properties.TryGetValue(TurnStateConstants.TokenPollingSettingsKey, out var tokenPollingSettingsToken); + var tokenPollingSettings = tokenPollingSettingsToken?.ToObject(); if (tokenPollingSettings != null) { logger.LogInformation($"PollForTokenAsync received new polling settings: timeout={tokenPollingSettings.Timeout}, interval={tokenPollingSettings.Interval}", tokenPollingSettings); @@ -93,34 +86,40 @@ namespace Microsoft.Bot.Builder pollingRequestsInterval = tokenPollingSettings.Interval > 0 ? TimeSpan.FromMilliseconds(tokenPollingSettings.Interval) : pollingRequestsInterval; // Only overrides if it is set. } } + + // once there is a token, send it to the bot and stop polling + if (tokenResponse.Token != null) + { + var tokenResponseActivityEvent = CreateTokenResponse(turnContext.Activity.GetConversationReference(), tokenResponse.Token, connectionName); + var identity = turnContext.TurnState.Get(BotAdapter.BotIdentityKey) as ClaimsIdentity; + var callback = turnContext.TurnState.Get(); + await adapter.ProcessActivityAsync(identity, tokenResponseActivityEvent, callback, cancellationToken).ConfigureAwait(false); + shouldEndPolling = true; + sentToken = true; + + logger.LogInformation("PollForTokenAsync completed with a token", turnContext.Activity); + } } - // once there is a token, send it to the bot and stop polling - if (tokenResponse.Token != null) + if (!shouldEndPolling) { - var tokenResponseActivityEvent = CreateTokenResponse(turnContext.Activity.GetConversationReference(), tokenResponse.Token, connectionName); - var identity = turnContext.TurnState.Get(BotFrameworkAdapter.BotIdentityKey) as ClaimsIdentity; - var callback = turnContext.TurnState.Get(); - await adapter.ProcessActivityAsync(identity, tokenResponseActivityEvent, callback, cancellationToken).ConfigureAwait(false); - shouldEndPolling = true; - sentToken = true; - - logger.LogInformation("PollForTokenAsync completed with a token", turnContext.Activity); + await Task.Delay(pollingRequestsInterval, cancellationToken).ConfigureAwait(false); } } - if (!shouldEndPolling) + if (!sentToken) { - await Task.Delay(pollingRequestsInterval).ConfigureAwait(false); + logger.LogInformation("PollForTokenAsync completed without receiving a token", turnContext.Activity); } - } - if (!sentToken) + stopwatch.Stop(); + } +#pragma warning disable CA1031 // Do not catch general exception types (for new we just log the exception and continue) + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - logger.LogInformation("PollForTokenAsync completed without receiving a token", turnContext.Activity); + logger.LogError(ex, "PollForTokenAsync threw an exception", connectionName); } - - stopwatch.Stop(); } private static Activity CreateTokenResponse(ConversationReference relatesTo, string token, string connectionName) diff --git a/libraries/Microsoft.Bot.Builder/TraceTranscriptLogger.cs b/libraries/Microsoft.Bot.Builder/TraceTranscriptLogger.cs index 7a2d6188d..ea857ee82 100644 --- a/libraries/Microsoft.Bot.Builder/TraceTranscriptLogger.cs +++ b/libraries/Microsoft.Bot.Builder/TraceTranscriptLogger.cs @@ -13,9 +13,9 @@ namespace Microsoft.Bot.Builder /// public class TraceTranscriptLogger : ITranscriptLogger { - private static JsonSerializerSettings serializationSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.Indented }; + private static readonly JsonSerializerSettings _serializationSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.Indented }; - private bool traceActivity; + private readonly bool _traceActivity; public TraceTranscriptLogger() : this(true) @@ -24,7 +24,7 @@ namespace Microsoft.Bot.Builder public TraceTranscriptLogger(bool traceActivity) { - this.traceActivity = traceActivity; + this._traceActivity = traceActivity; } /// @@ -35,9 +35,9 @@ namespace Microsoft.Bot.Builder public Task LogActivityAsync(IActivity activity) { BotAssert.ActivityNotNull(activity); - if (traceActivity) + if (_traceActivity) { - Trace.TraceInformation(JsonConvert.SerializeObject(activity, serializationSettings)); + Trace.TraceInformation(JsonConvert.SerializeObject(activity, _serializationSettings)); } else { diff --git a/libraries/Microsoft.Bot.Builder/TranscriptLoggerMiddleware.cs b/libraries/Microsoft.Bot.Builder/TranscriptLoggerMiddleware.cs index b853d6485..dc9184d42 100644 --- a/libraries/Microsoft.Bot.Builder/TranscriptLoggerMiddleware.cs +++ b/libraries/Microsoft.Bot.Builder/TranscriptLoggerMiddleware.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Schema; @@ -16,8 +17,8 @@ namespace Microsoft.Bot.Builder /// public class TranscriptLoggerMiddleware : IMiddleware { - private static JsonSerializerSettings _jsonSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }; - private ITranscriptLogger logger; + private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }; + private readonly ITranscriptLogger _logger; /// /// Initializes a new instance of the class. @@ -25,7 +26,7 @@ namespace Microsoft.Bot.Builder /// The conversation store to use. public TranscriptLoggerMiddleware(ITranscriptLogger transcriptLogger) { - logger = transcriptLogger ?? throw new ArgumentNullException("TranscriptLoggerMiddleware requires a ITranscriptLogger implementation. "); + _logger = transcriptLogger ?? throw new ArgumentNullException(nameof(transcriptLogger), "TranscriptLoggerMiddleware requires a ITranscriptLogger implementation. "); } /// @@ -108,26 +109,34 @@ namespace Microsoft.Bot.Builder await nextTurn(cancellationToken).ConfigureAwait(false); // flush transcript at end of turn + var logTasks = new List(); while (transcript.Count > 0) { + // Process the queue and log all the activities in parallel. var activity = transcript.Dequeue(); - // As we are deliberately not using await, disable the associated warning. -#pragma warning disable 4014 - logger.LogActivityAsync(activity).ContinueWith( - task => - { - try - { - task.Wait(); - } - catch (Exception err) - { - Trace.TraceError($"Transcript logActivity failed with {err}"); - } - }, - cancellationToken); -#pragma warning restore 4014 + // Add the logging task to the list (we don't call await here, we await all the calls together later). + logTasks.Add(TryLogActivityAsync(_logger, activity)); + } + + if (logTasks.Any()) + { + // Wait for all the activities to be logged before continuing. + await Task.WhenAll(logTasks).ConfigureAwait(false); + } + } + + private static async Task TryLogActivityAsync(ITranscriptLogger logger, IActivity activity) + { + try + { + await logger.LogActivityAsync(activity).ConfigureAwait(false); + } +#pragma warning disable CA1031 // Do not catch general exception types (this should probably be addressed later, but for now we just log the error and continue the execution) + catch (Exception ex) +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler + { + Trace.TraceError($"Transcript logActivity failed with {ex}"); } } @@ -145,7 +154,7 @@ namespace Microsoft.Bot.Builder if (activity == null) { - throw new ArgumentNullException("Cannot check or add Id on a null Activity."); + throw new ArgumentNullException(nameof(activity), "Cannot check or add Id on a null Activity."); } if (activity.Id == null) @@ -157,7 +166,7 @@ namespace Microsoft.Bot.Builder return activityWithId; } - private void LogActivity(Queue transcript, IActivity activity) + private static void LogActivity(Queue transcript, IActivity activity) { if (activity.Timestamp == null) { diff --git a/libraries/Microsoft.Bot.Builder/TurnContext.cs b/libraries/Microsoft.Bot.Builder/TurnContext.cs index f4709d567..b353e8596 100644 --- a/libraries/Microsoft.Bot.Builder/TurnContext.cs +++ b/libraries/Microsoft.Bot.Builder/TurnContext.cs @@ -401,6 +401,16 @@ namespace Microsoft.Bot.Builder /// public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Dispose any disposable objects owned by the class here. + } } private async Task UpdateActivityInternalAsync( @@ -412,11 +422,11 @@ namespace Microsoft.Bot.Builder BotAssert.ActivityNotNull(activity); if (updateHandlers == null) { - throw new ArgumentException(nameof(updateHandlers)); + throw new ArgumentException($"{nameof(updateHandlers)} is null.", nameof(updateHandlers)); } // No middleware to run. - if (updateHandlers.Count() == 0) + if (!updateHandlers.Any()) { if (callAtBottom != null) { @@ -452,11 +462,11 @@ namespace Microsoft.Bot.Builder if (deleteHandlers == null) { - throw new ArgumentException(nameof(deleteHandlers)); + throw new ArgumentException($"{nameof(deleteHandlers)} is null", nameof(deleteHandlers)); } // No middleware to run. - if (deleteHandlers.Count() == 0) + if (!deleteHandlers.Any()) { if (callAtBottom != null) { diff --git a/libraries/Microsoft.Bot.Builder/TurnContextStateCollection.cs b/libraries/Microsoft.Bot.Builder/TurnContextStateCollection.cs index e977803b4..e69b3d1bc 100644 --- a/libraries/Microsoft.Bot.Builder/TurnContextStateCollection.cs +++ b/libraries/Microsoft.Bot.Builder/TurnContextStateCollection.cs @@ -133,6 +133,16 @@ namespace Microsoft.Bot.Builder public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Dispose any disposable objects owned by the class here. + } } } } diff --git a/libraries/Microsoft.Bot.Builder/UserState.cs b/libraries/Microsoft.Bot.Builder/UserState.cs index 156c698fc..6733a89e2 100644 --- a/libraries/Microsoft.Bot.Builder/UserState.cs +++ b/libraries/Microsoft.Bot.Builder/UserState.cs @@ -37,8 +37,8 @@ namespace Microsoft.Bot.Builder /// is missing. protected override string GetStorageKey(ITurnContext turnContext) { - var channelId = turnContext.Activity.ChannelId ?? throw new ArgumentNullException("invalid activity-missing channelId"); - var userId = turnContext.Activity.From?.Id ?? throw new ArgumentNullException("invalid activity-missing From.Id"); + var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); + var userId = turnContext.Activity.From?.Id ?? throw new InvalidOperationException("invalid activity-missing From.Id"); return $"{channelId}/users/{userId}"; } } diff --git a/libraries/Microsoft.Bot.Configuration/Microsoft.Bot.Configuration.csproj b/libraries/Microsoft.Bot.Configuration/Microsoft.Bot.Configuration.csproj index 08f2f8c32..32fe7a885 100644 --- a/libraries/Microsoft.Bot.Configuration/Microsoft.Bot.Configuration.csproj +++ b/libraries/Microsoft.Bot.Configuration/Microsoft.Bot.Configuration.csproj @@ -23,7 +23,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Connector/Authentication/ChannelValidation.cs b/libraries/Microsoft.Bot.Connector/Authentication/ChannelValidation.cs index 138ed4c9c..d8f3b4079 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/ChannelValidation.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/ChannelValidation.cs @@ -54,7 +54,9 @@ namespace Microsoft.Bot.Connector.Authentication /// /// A valid ClaimsIdentity. /// +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, HttpClient httpClient, string channelId) +#pragma warning restore UseAsyncSuffix // Use Async suffix { return await AuthenticateChannelToken(authHeader, credentials, httpClient, channelId, new AuthenticationConfiguration()).ConfigureAwait(false); } @@ -75,7 +77,9 @@ namespace Microsoft.Bot.Connector.Authentication /// /// A valid ClaimsIdentity. /// +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { @@ -145,7 +149,9 @@ namespace Microsoft.Bot.Connector.Authentication /// setup and teardown, so a shared HttpClient is recommended. /// The ID of the channel to validate. /// ClaimsIdentity. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient, string channelId) +#pragma warning restore UseAsyncSuffix // Use Async suffix { return await AuthenticateChannelToken(authHeader, credentials, serviceUrl, httpClient, channelId, new AuthenticationConfiguration()).ConfigureAwait(false); } @@ -162,7 +168,9 @@ namespace Microsoft.Bot.Connector.Authentication /// The ID of the channel to validate. /// The authentication configuration. /// ClaimsIdentity. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { diff --git a/libraries/Microsoft.Bot.Connector/Authentication/EmulatorValidation.cs b/libraries/Microsoft.Bot.Connector/Authentication/EmulatorValidation.cs index 32adad58b..56063886d 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/EmulatorValidation.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/EmulatorValidation.cs @@ -92,7 +92,9 @@ namespace Microsoft.Bot.Connector.Authentication /// /// A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass. /// +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateEmulatorToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, HttpClient httpClient, string channelId) +#pragma warning restore UseAsyncSuffix // Use Async suffix { return await AuthenticateEmulatorToken(authHeader, credentials, channelProvider, httpClient, channelId, new AuthenticationConfiguration()).ConfigureAwait(false); } @@ -114,7 +116,9 @@ namespace Microsoft.Bot.Connector.Authentication /// /// A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass. /// +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateEmulatorToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { diff --git a/libraries/Microsoft.Bot.Connector/Authentication/EnterpriseChannelValidation.cs b/libraries/Microsoft.Bot.Connector/Authentication/EnterpriseChannelValidation.cs index b6492a0c4..1beb9bd5d 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/EnterpriseChannelValidation.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/EnterpriseChannelValidation.cs @@ -42,7 +42,9 @@ namespace Microsoft.Bot.Connector.Authentication /// setup and teardown, so a shared HttpClient is recommended. /// The ID of the channel to validate. /// ClaimsIdentity. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string serviceUrl, HttpClient httpClient, string channelId) +#pragma warning restore UseAsyncSuffix // Use Async suffix { return await AuthenticateChannelToken(authHeader, credentials, channelProvider, serviceUrl, httpClient, channelId, new AuthenticationConfiguration()).ConfigureAwait(false); } @@ -60,7 +62,9 @@ namespace Microsoft.Bot.Connector.Authentication /// The ID of the channel to validate. /// The authentication configuration. /// ClaimsIdentity. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string serviceUrl, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { @@ -82,7 +86,9 @@ namespace Microsoft.Bot.Connector.Authentication return identity; } +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task ValidateIdentity(ClaimsIdentity identity, ICredentialProvider credentials, string serviceUrl) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (identity == null) { diff --git a/libraries/Microsoft.Bot.Connector/Authentication/GovernmentChannelValidation.cs b/libraries/Microsoft.Bot.Connector/Authentication/GovernmentChannelValidation.cs index a7e0da377..0fc9fe921 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/GovernmentChannelValidation.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/GovernmentChannelValidation.cs @@ -44,7 +44,9 @@ namespace Microsoft.Bot.Connector.Authentication /// setup and teardown, so a shared HttpClient is recommended. /// The ID of the channel to validate. /// ClaimsIdentity. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient, string channelId) +#pragma warning restore UseAsyncSuffix // Use Async suffix { return await AuthenticateChannelToken(authHeader, credentials, serviceUrl, httpClient, channelId, new AuthenticationConfiguration()).ConfigureAwait(false); } @@ -61,7 +63,9 @@ namespace Microsoft.Bot.Connector.Authentication /// The ID of the channel to validate. /// The authentication configuration. /// ClaimsIdentity. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { @@ -88,7 +92,9 @@ namespace Microsoft.Bot.Connector.Authentication /// The user defined set of valid credentials, such as the AppId. /// The service url from the request. /// A representing the asynchronous operation. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task ValidateIdentity(ClaimsIdentity identity, ICredentialProvider credentials, string serviceUrl) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (identity == null) { diff --git a/libraries/Microsoft.Bot.Connector/Authentication/JwtTokenValidation.cs b/libraries/Microsoft.Bot.Connector/Authentication/JwtTokenValidation.cs index fa22135d9..4793db2a2 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/JwtTokenValidation.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/JwtTokenValidation.cs @@ -30,7 +30,9 @@ namespace Microsoft.Bot.Connector.Authentication /// A task that represents the work queued to execute. /// If the task completes successfully, the result contains the claims-based /// identity for the request. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateRequest(IActivity activity, string authHeader, ICredentialProvider credentials, IChannelProvider provider, HttpClient httpClient = null) +#pragma warning restore UseAsyncSuffix // Use Async suffix { return await AuthenticateRequest(activity, authHeader, credentials, provider, new AuthenticationConfiguration(), httpClient).ConfigureAwait(false); } @@ -48,7 +50,9 @@ namespace Microsoft.Bot.Connector.Authentication /// A task that represents the work queued to execute. /// If the task completes successfully, the result contains the claims-based /// identity for the request. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateRequest(IActivity activity, string authHeader, ICredentialProvider credentials, IChannelProvider provider, AuthenticationConfiguration authConfig, HttpClient httpClient = null) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { @@ -89,7 +93,9 @@ namespace Microsoft.Bot.Connector.Authentication /// A task that represents the work queued to execute. /// If the task completes successfully, the result contains the claims-based /// identity for the request. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task ValidateAuthHeader(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string channelId, string serviceUrl = null, HttpClient httpClient = null) +#pragma warning restore UseAsyncSuffix // Use Async suffix { return await ValidateAuthHeader(authHeader, credentials, channelProvider, channelId, new AuthenticationConfiguration(), serviceUrl, httpClient).ConfigureAwait(false); } @@ -107,7 +113,9 @@ namespace Microsoft.Bot.Connector.Authentication /// A task that represents the work queued to execute. /// If the task completes successfully, the result contains the claims-based /// identity for the request. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task ValidateAuthHeader(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string channelId, AuthenticationConfiguration authConfig, string serviceUrl = null, HttpClient httpClient = null) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (string.IsNullOrEmpty(authHeader)) { @@ -121,7 +129,7 @@ namespace Microsoft.Bot.Connector.Authentication httpClient = httpClient ?? _httpClient; - var identity = await AuthenticateToken(authHeader, credentials, channelProvider, channelId, authConfig, serviceUrl, httpClient).ConfigureAwait(false); + var identity = await AuthenticateTokenAsync(authHeader, credentials, channelProvider, channelId, authConfig, serviceUrl, httpClient).ConfigureAwait(false); await ValidateClaimsAsync(authConfig, identity.Claims).ConfigureAwait(false); @@ -210,7 +218,7 @@ namespace Microsoft.Bot.Connector.Authentication // [0] = "Bearer" // [1] = "[Big Long String]" var authScheme = parts[0]; - if (!authScheme.Equals("Bearer", StringComparison.InvariantCultureIgnoreCase)) + if (!authScheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase)) { // The scheme MUST be "Bearer" return false; @@ -222,7 +230,7 @@ namespace Microsoft.Bot.Connector.Authentication /// /// Authenticates the auth header token from the request. /// - private static async Task AuthenticateToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string channelId, AuthenticationConfiguration authConfig, string serviceUrl, HttpClient httpClient) + private static async Task AuthenticateTokenAsync(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string channelId, AuthenticationConfiguration authConfig, string serviceUrl, HttpClient httpClient) { if (SkillValidation.IsSkillToken(authHeader)) { diff --git a/libraries/Microsoft.Bot.Connector/Authentication/Retry.cs b/libraries/Microsoft.Bot.Connector/Authentication/Retry.cs index 30d0a2931..c008e2783 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/Retry.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/Retry.cs @@ -9,7 +9,9 @@ namespace Microsoft.Bot.Connector.Authentication { public static class Retry { +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task Run(Func> task, Func retryExceptionHandler) +#pragma warning restore UseAsyncSuffix // Use Async suffix { RetryParams retry; var exceptions = new List(); diff --git a/libraries/Microsoft.Bot.Connector/Authentication/SimpleChannelProvider.cs b/libraries/Microsoft.Bot.Connector/Authentication/SimpleChannelProvider.cs index 146972f31..dfd93956f 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/SimpleChannelProvider.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/SimpleChannelProvider.cs @@ -42,7 +42,7 @@ namespace Microsoft.Bot.Connector.Authentication /// True if this channel provider represents a channel on US Government Azure. public bool IsGovernment() { - return string.Equals(GovernmentAuthenticationConstants.ChannelService, ChannelService, StringComparison.InvariantCultureIgnoreCase); + return string.Equals(GovernmentAuthenticationConstants.ChannelService, ChannelService, StringComparison.OrdinalIgnoreCase); } /// diff --git a/libraries/Microsoft.Bot.Connector/Authentication/SkillValidation.cs b/libraries/Microsoft.Bot.Connector/Authentication/SkillValidation.cs index 01b5e1c95..0554bc4a0 100644 --- a/libraries/Microsoft.Bot.Connector/Authentication/SkillValidation.cs +++ b/libraries/Microsoft.Bot.Connector/Authentication/SkillValidation.cs @@ -94,7 +94,7 @@ namespace Microsoft.Bot.Connector.Authentication } var audience = claimsList.FirstOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim)?.Value; - if (string.IsNullOrWhiteSpace(audience) || AuthenticationConstants.ToBotFromChannelTokenIssuer.Equals(audience, StringComparison.InvariantCulture)) + if (string.IsNullOrWhiteSpace(audience) || AuthenticationConstants.ToBotFromChannelTokenIssuer.Equals(audience, StringComparison.OrdinalIgnoreCase)) { // The audience is https://api.botframework.com and not an appId. return false; @@ -124,7 +124,9 @@ namespace Microsoft.Bot.Connector.Authentication /// The ID of the channel to validate. /// The authentication configuration. /// A instance if the validation is successful. +#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) +#pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { diff --git a/libraries/Microsoft.Bot.Connector/Microsoft.Bot.Connector.csproj b/libraries/Microsoft.Bot.Connector/Microsoft.Bot.Connector.csproj index 80c84348c..99d47a20b 100644 --- a/libraries/Microsoft.Bot.Connector/Microsoft.Bot.Connector.csproj +++ b/libraries/Microsoft.Bot.Connector/Microsoft.Bot.Connector.csproj @@ -24,7 +24,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 @@ -38,6 +39,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/libraries/Microsoft.Bot.Schema/Microsoft.Bot.Schema.csproj b/libraries/Microsoft.Bot.Schema/Microsoft.Bot.Schema.csproj index 524545ffc..9766586e1 100644 --- a/libraries/Microsoft.Bot.Schema/Microsoft.Bot.Schema.csproj +++ b/libraries/Microsoft.Bot.Schema/Microsoft.Bot.Schema.csproj @@ -23,10 +23,15 @@ - $(NoWarn);CS1573;CS1591 + + $(NoWarn);CS1573;CS1591;SX1309 + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/libraries/Microsoft.Bot.Streaming/Microsoft.Bot.Streaming.csproj b/libraries/Microsoft.Bot.Streaming/Microsoft.Bot.Streaming.csproj index d3396b097..041ca2d0c 100644 --- a/libraries/Microsoft.Bot.Streaming/Microsoft.Bot.Streaming.csproj +++ b/libraries/Microsoft.Bot.Streaming/Microsoft.Bot.Streaming.csproj @@ -25,8 +25,9 @@ - - $(NoWarn);CS1591 + + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/Microsoft.Bot.Streaming/RequestHandler.cs b/libraries/Microsoft.Bot.Streaming/RequestHandler.cs index 05cfdd40f..cd7ab7023 100644 --- a/libraries/Microsoft.Bot.Streaming/RequestHandler.cs +++ b/libraries/Microsoft.Bot.Streaming/RequestHandler.cs @@ -12,7 +12,6 @@ namespace Microsoft.Bot.Streaming /// public abstract class RequestHandler { - #pragma warning disable IDE0034 /// /// The method that must be implemented in order to handle incoming requests. /// @@ -22,6 +21,5 @@ namespace Microsoft.Bot.Streaming /// Cancellation token. /// A that will produce a on successful completion. public abstract Task ProcessRequestAsync(ReceiveRequest request, ILogger logger, object context = null, CancellationToken cancellationToken = default(CancellationToken)); - #pragma warning restore IDE0034 } } diff --git a/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core.csproj b/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core.csproj index 3c99ce50b..1e741543f 100644 --- a/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core.csproj +++ b/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core.csproj @@ -24,7 +24,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.WebApi/Microsoft.Bot.Builder.Integration.ApplicationInsights.WebApi.csproj b/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.WebApi/Microsoft.Bot.Builder.Integration.ApplicationInsights.WebApi.csproj index e8db339f9..e023234f2 100644 --- a/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.WebApi/Microsoft.Bot.Builder.Integration.ApplicationInsights.WebApi.csproj +++ b/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.WebApi/Microsoft.Bot.Builder.Integration.ApplicationInsights.WebApi.csproj @@ -25,7 +25,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.Core/Microsoft.Bot.Builder.Integration.AspNet.Core.csproj b/libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.Core/Microsoft.Bot.Builder.Integration.AspNet.Core.csproj index ee8b690fb..62e821b66 100644 --- a/libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.Core/Microsoft.Bot.Builder.Integration.AspNet.Core.csproj +++ b/libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.Core/Microsoft.Bot.Builder.Integration.AspNet.Core.csproj @@ -23,7 +23,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.WebApi/Microsoft.Bot.Builder.Integration.AspNet.WebApi.csproj b/libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.WebApi/Microsoft.Bot.Builder.Integration.AspNet.WebApi.csproj index c44948c1c..ca240aba6 100644 --- a/libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.WebApi/Microsoft.Bot.Builder.Integration.AspNet.WebApi.csproj +++ b/libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.WebApi/Microsoft.Bot.Builder.Integration.AspNet.WebApi.csproj @@ -20,7 +20,8 @@ - $(NoWarn);CS1591 + + $(NoWarn);CS1591;SX1309 diff --git a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Twilio.Tests/TwilioAdapterTests.cs b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Twilio.Tests/TwilioAdapterTests.cs index ea19b73cd..a8b0a05c3 100644 --- a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Twilio.Tests/TwilioAdapterTests.cs +++ b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Twilio.Tests/TwilioAdapterTests.cs @@ -42,7 +42,7 @@ namespace Microsoft.Bot.Builder.Adapters.Twilio.Tests const string resourceIdentifier = "Mocked Resource Identifier"; var twilioApi = new Mock(_testOptions); - twilioApi.Setup(x => x.SendMessage(It.IsAny(), default)).Returns(Task.FromResult(resourceIdentifier)); + twilioApi.Setup(x => x.SendMessageAsync(It.IsAny(), default)).Returns(Task.FromResult(resourceIdentifier)); var twilioAdapter = new TwilioAdapter(twilioApi.Object); var resourceResponses = await twilioAdapter.SendActivitiesAsync(null, new Activity[] { activity.Object }, default).ConfigureAwait(false); @@ -61,7 +61,7 @@ namespace Microsoft.Bot.Builder.Adapters.Twilio.Tests const string resourceIdentifier = "Mocked Resource Identifier"; var twilioApi = new Mock(_testOptions); - twilioApi.Setup(x => x.SendMessage(It.IsAny(), default)).Returns(Task.FromResult(resourceIdentifier)); + twilioApi.Setup(x => x.SendMessageAsync(It.IsAny(), default)).Returns(Task.FromResult(resourceIdentifier)); var twilioAdapter = new TwilioAdapter(twilioApi.Object); var resourceResponses = await twilioAdapter.SendActivitiesAsync(null, new Activity[] { activity.Object }, default).ConfigureAwait(false); diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index c431bb412..305212613 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -1,8 +1,9 @@ - - $(NoWarn);SA0001;CS1573,CS1591,CS1712 + + + $(NoWarn);SA0001;CS1573;CS1591;CS1712;SX1309 diff --git a/tests/Microsoft.Bot.Builder.Tests/Adapters/TestAdapterTests.cs b/tests/Microsoft.Bot.Builder.Tests/Adapters/TestAdapterTests.cs index 04691f013..f7beb1447 100644 --- a/tests/Microsoft.Bot.Builder.Tests/Adapters/TestAdapterTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/Adapters/TestAdapterTests.cs @@ -16,24 +16,6 @@ namespace Microsoft.Bot.Builder.Tests.Adapters { public class TestAdapterTests { - public async Task MyBotLogic(ITurnContext turnContext, CancellationToken cancellationToken) - { - switch (turnContext.Activity.AsMessageActivity().Text) - { - case "count": - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("one")); - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("two")); - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("three")); - break; - case "ignore": - break; - default: - await turnContext.SendActivityAsync( - turnContext.Activity.CreateReply($"echo:{turnContext.Activity.AsMessageActivity().Text}")); - break; - } - } - [Fact] public async Task TestAdapter_ExceptionTypesOnTest() { @@ -579,5 +561,23 @@ namespace Microsoft.Bot.Builder.Tests.Adapters Assert.Equal(targetChannel, receivedChannelId); Assert.Equal(targetChannel, reply.ChannelId); } + + private async Task MyBotLogic(ITurnContext turnContext, CancellationToken cancellationToken) + { + switch (turnContext.Activity.AsMessageActivity().Text) + { + case "count": + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("one")); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("two")); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("three")); + break; + case "ignore": + break; + default: + await turnContext.SendActivityAsync( + turnContext.Activity.CreateReply($"echo:{turnContext.Activity.AsMessageActivity().Text}")); + break; + } + } } } diff --git a/tests/Microsoft.Bot.Builder.Tests/BotStateTests.cs b/tests/Microsoft.Bot.Builder.Tests/BotStateTests.cs index 67f9c9fbb..7609cc4c9 100644 --- a/tests/Microsoft.Bot.Builder.Tests/BotStateTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/BotStateTests.cs @@ -610,7 +610,7 @@ namespace Microsoft.Bot.Builder.Tests var context = TestUtilities.CreateEmptyContext(); context.Activity.From = null; var testProperty = userState.CreateProperty("test"); - await Assert.ThrowsAsync(() => testProperty.GetAsync(context)); + await Assert.ThrowsAsync(() => testProperty.GetAsync(context)); } [Fact] @@ -621,7 +621,7 @@ namespace Microsoft.Bot.Builder.Tests var context = TestUtilities.CreateEmptyContext(); context.Activity.Conversation = null; var testProperty = userState.CreateProperty("test"); - await Assert.ThrowsAsync(() => testProperty.GetAsync(context)); + await Assert.ThrowsAsync(() => testProperty.GetAsync(context)); } [Fact] @@ -633,7 +633,7 @@ namespace Microsoft.Bot.Builder.Tests context.Activity.Conversation = null; context.Activity.From = null; var testProperty = userState.CreateProperty("test"); - await Assert.ThrowsAsync(() => testProperty.GetAsync(context)); + await Assert.ThrowsAsync(() => testProperty.GetAsync(context)); } [Fact] @@ -644,7 +644,7 @@ namespace Microsoft.Bot.Builder.Tests var context = TestUtilities.CreateEmptyContext(); context.Activity.Conversation = null; var testProperty = userState.CreateProperty("test"); - await Assert.ThrowsAsync(() => testProperty.GetAsync(context)); + await Assert.ThrowsAsync(() => testProperty.GetAsync(context)); } [Fact]