This commit is contained in:
Tom Laird-McConnell 2019-09-17 18:03:41 -07:00
Родитель 10ab9b5bc4
Коммит 02e7a48813
493 изменённых файлов: 36092 добавлений и 7564 удалений

Просмотреть файл

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for StyleCop.Analyzers" Description="Source analysis rules treat as errors for BotBuilder-DotNet solution." ToolsVersion="16.0">
<Rules AnalyzerId="AsyncUsageAnalyzers" RuleNamespace="AsyncUsageAnalyzers">
<Rule Id="AvoidAsyncVoid" Action="Warning" />
<Rule Id="AvoidAsyncVoid" Action="Error" />
<Rule Id="UseConfigureAwait" Action="Error" />
<Rule Id="UseAsyncSuffix" Action="None" />
<Rule Id="UseConfigureAwait" Action="Warning" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.Features" RuleNamespace="Microsoft.CodeAnalysis.CSharp.Features">
<Rule Id="IDE0003" Action="None" />
@ -29,4 +29,15 @@
<Rule Id="SA1629" Action="None" />
<Rule Id="SA1633" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
<Rule Id="CA1054" Action="None" /> <!-- UriParametersShouldNotBeStrings -->
<Rule Id="CA1056" Action="None" /> <!-- UriPropertiesShouldNotBeStrings -->
<Rule Id="CA1062" Action="None" /> <!-- ValidateArgumentsOfPublicMethods -->
<Rule Id="CA2227" Action="None" /> <!-- CollectionPropertiesShouldBeReadOnly -->
</Rules>
<Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
<Rule Id="CA1303" Action="None" /> <!-- DoNotPassLiteralsAsLocalizedParameters -->
</Rules>
</RuleSet>

Просмотреть файл

@ -9,12 +9,15 @@
<ModulePaths>
<Include>
<ModulePath>.*\Microsoft.Bot.Builder.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Adapters.Twilio.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Teams.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.AI.Luis.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.AI.QnA.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.ApplicationInsights.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Azure.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Configuration.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Connector.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Connector.Teams.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Dialogs.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Integration.ApplicationInsights.Core.dll$</ModulePath>
<ModulePath>.*\Microsoft.Bot.Builder.Integration.AspNet.Core.dll$</ModulePath>

Просмотреть файл

@ -39,7 +39,7 @@
<PropertyGroup>
<!-- Rules found at: https://aka.ms/Microsoft-NuGet-Compliance -->
<PackageProjectUrl>https://github.com/Microsoft/botbuilder-dotnet</PackageProjectUrl>
<PackageIconUrl>http://docs.botframework.com/images/bot_icon.png</PackageIconUrl>
<PackageIconUrl>https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png</PackageIconUrl>
<PackageLicenseUrl>https://github.com/Microsoft/BotBuilder/blob/master/LICENSE</PackageLicenseUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/Microsoft/botbuilder-dotnet</RepositoryUrl>
@ -52,7 +52,6 @@
replace PackageLicenseUrl with PackageLicenseExpression.
-->
<NoWarn>$(NoWarn);NU5125</NoWarn>
<LangVersion>7</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="$(Configuration) == 'Debug'">
<!-- For debug builds, we don't generate documentation. Supress the StyleCop rule that warns about this. -->

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Bot.Builder.FunctionalTests.Configuration
{
internal static class EnvironmentConfig
{
public static string TestAppId()
{
var testAppId = Environment.GetEnvironmentVariable("TESTAPPID");
if (string.IsNullOrWhiteSpace(testAppId))
{
throw new Exception("Environment variable 'TestAppId' not found.");
}
return testAppId;
}
public static string TestAppPassword()
{
var testPassword = Environment.GetEnvironmentVariable("TESTPASSWORD");
if (string.IsNullOrWhiteSpace(testPassword))
{
throw new Exception("Environment variable 'TestPassword' not found.");
}
return testPassword;
}
}
}

Просмотреть файл

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.FunctionalTests.Configuration;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -20,12 +21,13 @@ namespace Microsoft.Bot.Builder.FunctionalTests
public GetTokenRefreshTests()
{
testAppId = EnvironmentConfig.TestAppId();
testPassword = EnvironmentConfig.TestAppPassword();
}
[TestMethod]
public async Task TokenTests_GetCredentialsWorks()
{
GetEnvironmentVarsTestAppIdPassword();
MicrosoftAppCredentials credentials = new MicrosoftAppCredentials(testAppId, testPassword);
var result = await credentials.GetTokenAsync();
Assert.IsNotNull(result);
@ -34,7 +36,6 @@ namespace Microsoft.Bot.Builder.FunctionalTests
[TestMethod]
public async Task TokenTests_RefreshTokenWorks()
{
GetEnvironmentVarsTestAppIdPassword();
MicrosoftAppCredentials credentials = new MicrosoftAppCredentials(testAppId, testPassword);
var result = await credentials.GetTokenAsync();
Assert.IsNotNull(result);
@ -48,7 +49,6 @@ namespace Microsoft.Bot.Builder.FunctionalTests
[TestMethod]
public async Task TokenTests_RefreshTestLoad()
{
GetEnvironmentVarsTestAppIdPassword();
MicrosoftAppCredentials credentials = new MicrosoftAppCredentials(testAppId, testPassword);
List<Task<string>> tasks = new List<Task<string>>();
for (int i = 0; i < 1000; i++)
@ -107,24 +107,5 @@ namespace Microsoft.Bot.Builder.FunctionalTests
}
}
}
private void GetEnvironmentVarsTestAppIdPassword()
{
if (string.IsNullOrWhiteSpace(testAppId) || string.IsNullOrWhiteSpace(testPassword))
{
testAppId = Environment.GetEnvironmentVariable("TESTAPPID");
if (string.IsNullOrWhiteSpace(testAppId))
{
throw new Exception("Environment variable 'TestAppId' not found.");
}
testPassword = Environment.GetEnvironmentVariable("TESTPASSWORD");
if (string.IsNullOrWhiteSpace(testPassword))
{
throw new Exception("Environment variable 'TestPassword' not found.");
}
}
}
}
}

Просмотреть файл

@ -6,12 +6,17 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.FunctionalTests.Configuration;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.IdentityModel.Protocols;
using Xunit;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Bot.Connector.Tests.Authentication
namespace Microsoft.Bot.Builder.FunctionalTests
{
[TestClass]
#if !FUNCTIONALTESTS
[Ignore("These integration tests run only when FUNCTIONALTESTS is defined")]
#endif
public class JwtTokenExtractorTests
{
private const string KeyId = "CtfQC8Le-8NsC7oC2zQkZpcrfOc";
@ -34,46 +39,36 @@ namespace Microsoft.Bot.Connector.Tests.Authentication
emptyClient = new HttpClient();
}
[Fact]
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task Connector_TokenExtractor_NullRequiredEndorsements_ShouldFail()
{
var configRetriever = new TestConfigurationRetriever();
configRetriever.EndorsementTable.Add(KeyId, new HashSet<string>() { RandomEndorsement, ComplianceEndorsement, TestChannelName });
await Assert.ThrowsAsync<ArgumentNullException>(async () => await RunTestCase(configRetriever));
await RunTestCase(configRetriever);
}
[Fact]
[TestMethod]
public async Task Connector_TokenExtractor_EmptyRequireEndorsements_ShouldValidate()
{
var configRetriever = new TestConfigurationRetriever();
configRetriever.EndorsementTable.Add(KeyId, new HashSet<string>() { RandomEndorsement, ComplianceEndorsement, TestChannelName });
var claimsIdentity = await RunTestCase(configRetriever, new string[] { });
Assert.True(claimsIdentity.IsAuthenticated);
Assert.IsTrue(claimsIdentity.IsAuthenticated);
}
[Fact]
[TestMethod]
public async Task Connector_TokenExtractor_RequiredEndorsementsPresent_ShouldValidate()
{
var configRetriever = new TestConfigurationRetriever();
configRetriever.EndorsementTable.Add(KeyId, new HashSet<string>() { RandomEndorsement, ComplianceEndorsement, TestChannelName });
var claimsIdentity = await RunTestCase(configRetriever, new string[] { ComplianceEndorsement });
Assert.True(claimsIdentity.IsAuthenticated);
Assert.IsTrue(claimsIdentity.IsAuthenticated);
}
#if broken
[Fact]
public async Task Connector_TokenExtractor_RequiredEndorsementsPartiallyPresent_ShouldNotValidate()
{
var configRetriever = new TestConfigurationRetriever();
configRetriever.EndorsementTable.Add(KeyId, new HashSet<string>() { RandomEndorsement, ComplianceEndorsement, TestChannelName });
await Assert.ThrowsAsync<UnauthorizedAccessException>(async () => await RunTestCase(configRetriever, new string[] { ComplianceEndorsement, "notSatisfiedEndorsement" }));
}
#endif
private async Task<ClaimsIdentity> RunTestCase(IConfigurationRetriever<IDictionary<string, HashSet<string>>> configRetriever, string[] requiredEndorsements = null)
{
var tokenExtractor = new JwtTokenExtractor(
@ -83,7 +78,7 @@ namespace Microsoft.Bot.Connector.Tests.Authentication
AuthenticationConstants.AllowedSigningAlgorithms,
new ConfigurationManager<IDictionary<string, HashSet<string>>>("http://test", configRetriever));
string header = $"Bearer {await new MicrosoftAppCredentials("2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F").GetTokenAsync()}";
string header = $"Bearer {await new MicrosoftAppCredentials(EnvironmentConfig.TestAppId(), EnvironmentConfig.TestAppPassword()).GetTokenAsync()}";
return await tokenExtractor.GetIdentityAsync(header, "testChannel", requiredEndorsements);
}

Просмотреть файл

@ -1,12 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.FunctionalTests.Configuration;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.IdentityModel.Protocols;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Bot.Connector.Tests.Authentication
namespace Microsoft.Bot.Builder.FunctionalTests
{
public class TestConfigurationRetriever : IConfigurationRetriever<IDictionary<string, HashSet<string>>>
{

Просмотреть файл

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29001.49
VisualStudioVersion = 16.0.29123.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{4269F3C3-6B42-419B-B64A-3E6DC0F1574A}"
EndProject
@ -136,6 +136,32 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schemas", "Schemas", "{EE56
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.TestBot.Json", "tests\Microsoft.Bot.Builder.TestBot.Json\Microsoft.Bot.Builder.TestBot.Json.csproj", "{2454BBCD-77BC-4E3D-B5A6-3562BED898D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.AI.Luis.TestUtils", "tests\Microsoft.Bot.Builder.AI.Luis.TestUtils\Microsoft.Bot.Builder.AI.Luis.TestUtils.csproj", "{685271A8-6C69-46E4-9B11-89AF9761CE0A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.AI.LuisV3", "libraries\Microsoft.Bot.Builder.AI.LuisV3\Microsoft.Bot.Builder.AI.LuisV3.csproj", "{EF46ABF9-0405-4EAA-BC1E-A2BC48DD1A69}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.AI.LuisV3.Tests", "tests\Microsoft.Bot.Builder.Ai.LUISV3.tests\Microsoft.Bot.Builder.AI.LuisV3.Tests.csproj", "{474C57B1-C9FC-4B71-A92B-B25BA27FAFA7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Adapters", "Adapters", "{6230B915-B238-4E57-AAC4-06B4498F540F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapters.Twilio", "libraries\Adapters\Microsoft.Bot.Builder.Adapters.Twilio\Microsoft.Bot.Builder.Adapters.Twilio.csproj", "{1D1AD39B-EBCF-4960-930E-84246DEF6AAE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Adapters", "Adapters", "{E8CD434A-306F-41D9-B67D-BFFF3287354D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapters.Twilio.Tests", "tests\Adapters\Microsoft.Bot.Builder.Adapters.Twilio.Tests\Microsoft.Bot.Builder.Adapters.Twilio.Tests.csproj", "{6B51F54F-86E8-4FBC-8FDD-3C386E97D0E1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Schema.Teams", "libraries\Microsoft.Bot.Schema.Teams\Microsoft.Bot.Schema.Teams.csproj", "{1F8ACA9B-7721-4D83-8545-6EE449B3A100}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Connector.Teams", "libraries\Microsoft.Bot.Connector.Teams\Microsoft.Bot.Connector.Teams.csproj", "{630CF216-BA97-4128-8563-214660B153DC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Teams", "libraries\Microsoft.Bot.Builder.Teams\Microsoft.Bot.Builder.Teams.csproj", "{56230F58-02EF-4C88-9C28-BE37B8A4D074}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Teams.Tests", "tests\Microsoft.Bot.Builder.Teams.Tests\Microsoft.Bot.Builder.Teams.Tests.csproj", "{6EB0FEBB-DB84-44C8-9C5B-FD3D3D187A4F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Connector.Teams.Tests", "tests\Microsoft.Bot.Connector.Teams.Tests\Microsoft.Bot.Connector.Teams.Tests.csproj", "{D9C6FA68-340F-4338-8158-3BCBF10F320D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Schema.Teams.Tests", "tests\Microsoft.Bot.Schema.Teams.Tests\Microsoft.Bot.Schema.Teams.Tests.csproj", "{A67CCC9B-3B0D-4E3D-A111-6D8A85FAA35B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU
@ -461,6 +487,72 @@ Global
{2454BBCD-77BC-4E3D-B5A6-3562BED898D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2454BBCD-77BC-4E3D-B5A6-3562BED898D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2454BBCD-77BC-4E3D-B5A6-3562BED898D6}.Release|Any CPU.Build.0 = Release|Any CPU
{685271A8-6C69-46E4-9B11-89AF9761CE0A}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{685271A8-6C69-46E4-9B11-89AF9761CE0A}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{685271A8-6C69-46E4-9B11-89AF9761CE0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{685271A8-6C69-46E4-9B11-89AF9761CE0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{685271A8-6C69-46E4-9B11-89AF9761CE0A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{685271A8-6C69-46E4-9B11-89AF9761CE0A}.Release|Any CPU.Build.0 = Release|Any CPU
{EF46ABF9-0405-4EAA-BC1E-A2BC48DD1A69}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{EF46ABF9-0405-4EAA-BC1E-A2BC48DD1A69}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{EF46ABF9-0405-4EAA-BC1E-A2BC48DD1A69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF46ABF9-0405-4EAA-BC1E-A2BC48DD1A69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF46ABF9-0405-4EAA-BC1E-A2BC48DD1A69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF46ABF9-0405-4EAA-BC1E-A2BC48DD1A69}.Release|Any CPU.Build.0 = Release|Any CPU
{474C57B1-C9FC-4B71-A92B-B25BA27FAFA7}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{474C57B1-C9FC-4B71-A92B-B25BA27FAFA7}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{474C57B1-C9FC-4B71-A92B-B25BA27FAFA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{474C57B1-C9FC-4B71-A92B-B25BA27FAFA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{474C57B1-C9FC-4B71-A92B-B25BA27FAFA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{474C57B1-C9FC-4B71-A92B-B25BA27FAFA7}.Release|Any CPU.Build.0 = Release|Any CPU
{1D1AD39B-EBCF-4960-930E-84246DEF6AAE}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug - NuGet Packages|Any CPU
{1D1AD39B-EBCF-4960-930E-84246DEF6AAE}.Debug - NuGet Packages|Any CPU.Build.0 = Debug - NuGet Packages|Any CPU
{1D1AD39B-EBCF-4960-930E-84246DEF6AAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D1AD39B-EBCF-4960-930E-84246DEF6AAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D1AD39B-EBCF-4960-930E-84246DEF6AAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D1AD39B-EBCF-4960-930E-84246DEF6AAE}.Release|Any CPU.Build.0 = Release|Any CPU
{6B51F54F-86E8-4FBC-8FDD-3C386E97D0E1}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{6B51F54F-86E8-4FBC-8FDD-3C386E97D0E1}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{6B51F54F-86E8-4FBC-8FDD-3C386E97D0E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B51F54F-86E8-4FBC-8FDD-3C386E97D0E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B51F54F-86E8-4FBC-8FDD-3C386E97D0E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B51F54F-86E8-4FBC-8FDD-3C386E97D0E1}.Release|Any CPU.Build.0 = Release|Any CPU
{1F8ACA9B-7721-4D83-8545-6EE449B3A100}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{1F8ACA9B-7721-4D83-8545-6EE449B3A100}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{1F8ACA9B-7721-4D83-8545-6EE449B3A100}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F8ACA9B-7721-4D83-8545-6EE449B3A100}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F8ACA9B-7721-4D83-8545-6EE449B3A100}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F8ACA9B-7721-4D83-8545-6EE449B3A100}.Release|Any CPU.Build.0 = Release|Any CPU
{630CF216-BA97-4128-8563-214660B153DC}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{630CF216-BA97-4128-8563-214660B153DC}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{630CF216-BA97-4128-8563-214660B153DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{630CF216-BA97-4128-8563-214660B153DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{630CF216-BA97-4128-8563-214660B153DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{630CF216-BA97-4128-8563-214660B153DC}.Release|Any CPU.Build.0 = Release|Any CPU
{56230F58-02EF-4C88-9C28-BE37B8A4D074}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{56230F58-02EF-4C88-9C28-BE37B8A4D074}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{56230F58-02EF-4C88-9C28-BE37B8A4D074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{56230F58-02EF-4C88-9C28-BE37B8A4D074}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56230F58-02EF-4C88-9C28-BE37B8A4D074}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56230F58-02EF-4C88-9C28-BE37B8A4D074}.Release|Any CPU.Build.0 = Release|Any CPU
{6EB0FEBB-DB84-44C8-9C5B-FD3D3D187A4F}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{6EB0FEBB-DB84-44C8-9C5B-FD3D3D187A4F}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{6EB0FEBB-DB84-44C8-9C5B-FD3D3D187A4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6EB0FEBB-DB84-44C8-9C5B-FD3D3D187A4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EB0FEBB-DB84-44C8-9C5B-FD3D3D187A4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EB0FEBB-DB84-44C8-9C5B-FD3D3D187A4F}.Release|Any CPU.Build.0 = Release|Any CPU
{D9C6FA68-340F-4338-8158-3BCBF10F320D}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{D9C6FA68-340F-4338-8158-3BCBF10F320D}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{D9C6FA68-340F-4338-8158-3BCBF10F320D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9C6FA68-340F-4338-8158-3BCBF10F320D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9C6FA68-340F-4338-8158-3BCBF10F320D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9C6FA68-340F-4338-8158-3BCBF10F320D}.Release|Any CPU.Build.0 = Release|Any CPU
{A67CCC9B-3B0D-4E3D-A111-6D8A85FAA35B}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{A67CCC9B-3B0D-4E3D-A111-6D8A85FAA35B}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{A67CCC9B-3B0D-4E3D-A111-6D8A85FAA35B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A67CCC9B-3B0D-4E3D-A111-6D8A85FAA35B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A67CCC9B-3B0D-4E3D-A111-6D8A85FAA35B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A67CCC9B-3B0D-4E3D-A111-6D8A85FAA35B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -522,6 +614,19 @@ Global
{B7DB6561-7D83-4706-9DE0-02C5520DE2B7} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{BDCAC491-8518-4CE6-B56A-9E36936C7E70} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{2454BBCD-77BC-4E3D-B5A6-3562BED898D6} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{685271A8-6C69-46E4-9B11-89AF9761CE0A} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{EF46ABF9-0405-4EAA-BC1E-A2BC48DD1A69} = {763168FA-A590-482C-84D8-2922F7ADB1A2}
{474C57B1-C9FC-4B71-A92B-B25BA27FAFA7} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{6230B915-B238-4E57-AAC4-06B4498F540F} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{1D1AD39B-EBCF-4960-930E-84246DEF6AAE} = {6230B915-B238-4E57-AAC4-06B4498F540F}
{E8CD434A-306F-41D9-B67D-BFFF3287354D} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{6B51F54F-86E8-4FBC-8FDD-3C386E97D0E1} = {E8CD434A-306F-41D9-B67D-BFFF3287354D}
{1F8ACA9B-7721-4D83-8545-6EE449B3A100} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{630CF216-BA97-4128-8563-214660B153DC} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{56230F58-02EF-4C88-9C28-BE37B8A4D074} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{6EB0FEBB-DB84-44C8-9C5B-FD3D3D187A4F} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{D9C6FA68-340F-4338-8158-3BCBF10F320D} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{A67CCC9B-3B0D-4E3D-A111-6D8A85FAA35B} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7173C9F3-A7F9-496E-9078-9156E35D6E16}

Просмотреть файл

@ -0,0 +1,10 @@
<Project>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
</Project>

Просмотреть файл

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version Condition=" '$(PackageVersion)' == '' ">4.0.0-local</Version>
<Version Condition=" '$(PackageVersion)' != '' ">$(PackageVersion)</Version>
<PackageVersion Condition=" '$(PackageVersion)' == '' ">4.0.0-local</PackageVersion>
<PackageVersion Condition=" '$(PackageVersion)' != '' ">$(PackageVersion)</PackageVersion>
<Configurations>Debug;Release;Debug - NuGet Packages</Configurations>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<!-- The Twilio package isn't signed, so supress the warning. There seems to not be a way to supress this for ONLY Twilio. -->
<NoWarn>$(NoWarn),CS8002</NoWarn>
<DefineConstants></DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU' ">
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>..\..\..\build\35MSSharedLib1024.snk</AssemblyOriginatorKeyFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<DocumentationFile>bin\$(Configuration)\netstandard2.0\Microsoft.Bot.Builder.Adapters.Twilio.xml</DocumentationFile>
<DefineConstants>SIGNASSEMBLY</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Twilio" Version="5.31.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,209 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Adapters.Twilio
{
/// <summary>
/// A <see cref="BotAdapter"/> that can connect to Twilio's SMS service.
/// </summary>
public class TwilioAdapter : BotAdapter
{
private readonly TwilioAdapterOptions _options;
private readonly TwilioClientWrapper _twilioClient;
/// <summary>
/// Initializes a new instance of the <see cref="TwilioAdapter"/> class.
/// </summary>
/// <param name="options">The options to use to authenticate the bot with the Twilio service.</param>
/// <param name="twilioClient">The Twilio client to connect to.</param>
/// <exception cref="ArgumentNullException"><paramref name="options"/> is <c>null</c>.</exception>
public TwilioAdapter(TwilioAdapterOptions options, TwilioClientWrapper twilioClient)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (string.IsNullOrWhiteSpace(options.TwilioNumber))
{
throw new ArgumentException("TwilioNumber is a required part of the configuration.", nameof(options));
}
if (string.IsNullOrWhiteSpace(options.AccountSid))
{
throw new ArgumentException("AccountSid is a required part of the configuration.", nameof(options));
}
if (string.IsNullOrWhiteSpace(options.AuthToken))
{
throw new ArgumentException("AuthToken is a required part of the configuration.", nameof(options));
}
_twilioClient = twilioClient ?? throw new ArgumentNullException(nameof(twilioClient));
_options = options;
_twilioClient.LogIn(_options.AccountSid, _options.AuthToken);
}
/// <summary>
/// Sends activities to the conversation.
/// </summary>
/// <param name="turnContext">The context object for the turn.</param>
/// <param name="activities">The activities to send.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the activities are successfully sent, the task result contains
/// an array of <see cref="ResourceResponse"/> objects containing the SIDs that
/// Twilio assigned to the activities.</remarks>
/// <seealso cref="ITurnContext.OnSendActivities(SendActivitiesHandler)"/>
public override async Task<ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken)
{
var responses = new List<ResourceResponse>();
foreach (var activity in activities)
{
if (activity.Type == ActivityTypes.Message)
{
var messageOptions = TwilioHelper.ActivityToTwilio(activity, _options.TwilioNumber);
var res = await _twilioClient.SendMessage(messageOptions).ConfigureAwait(false);
var response = new ResourceResponse()
{
Id = res,
};
responses.Add(response);
}
else
{
throw new ArgumentException("Unknown message type of Activity.", nameof(activities));
}
}
return responses.ToArray();
}
/// <summary>
/// Creates a turn context and runs the middleware pipeline for an incoming activity.
/// </summary>
/// <param name="httpRequest">The incoming HTTP request.</param>
/// <param name="httpResponse">When this method completes, the HTTP response to send.</param>
/// <param name="bot">The bot that will handle the incoming activity.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <exception cref="ArgumentNullException"><paramref name="httpRequest"/>,
/// <paramref name="httpResponse"/>, or <paramref name="bot"/> is <c>null</c>.</exception>
public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpResponse, IBot bot, CancellationToken cancellationToken = default)
{
if (httpRequest == null)
{
throw new ArgumentNullException(nameof(httpRequest));
}
if (httpResponse == null)
{
throw new ArgumentNullException(nameof(httpResponse));
}
if (bot == null)
{
throw new ArgumentNullException(nameof(bot));
}
var activity = TwilioHelper.RequestToActivity(httpRequest, _options.ValidationUrl, _options.AuthToken);
// create a conversation reference
using (var context = new TurnContext(this, activity))
{
context.TurnState.Add("httpStatus", HttpStatusCode.OK.ToString("D"));
await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false);
httpResponse.StatusCode = Convert.ToInt32(context.TurnState.Get<string>("httpStatus"), CultureInfo.InvariantCulture);
httpResponse.ContentType = "text/plain";
var text = context.TurnState.Get<object>("httpBody") != null ? context.TurnState.Get<object>("httpBody").ToString() : string.Empty;
await httpResponse.WriteAsync(text, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Replaces an existing activity in the conversation.
/// Twilio SMS does not support this operation.
/// </summary>
/// <param name="turnContext">The context object for the turn.</param>
/// <param name="activity">New replacement activity.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>This method always returns a faulted task.</remarks>
/// <seealso cref="ITurnContext.OnUpdateActivity(UpdateActivityHandler)"/>
public override Task<ResourceResponse> UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken)
{
// Twilio adapter does not support updateActivity.
return Task.FromException<ResourceResponse>(new NotSupportedException("Twilio SMS does not support updating activities."));
}
/// <summary>
/// Deletes an existing activity in the conversation.
/// Twilio SMS does not support this operation.
/// </summary>
/// <param name="turnContext">The context object for the turn.</param>
/// <param name="reference">Conversation reference for the activity to delete.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>This method always returns a faulted task.</remarks>
/// <seealso cref="ITurnContext.OnDeleteActivity(DeleteActivityHandler)"/>
public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken)
{
// Twilio adapter does not support deleteActivity.
return Task.FromException<ResourceResponse>(new NotSupportedException("Twilio SMS does not support deleting activities."));
}
/// <summary>
/// Sends a proactive message to a conversation.
/// </summary>
/// <param name="reference">A reference to the conversation to continue.</param>
/// <param name="logic">The method to call for the resulting bot turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>Call this method to proactively send a message to a conversation.
/// Most channels require a user to initiate a conversation with a bot
/// before the bot can send activities to the user.</remarks>
/// <seealso cref="BotAdapter.RunPipelineAsync(ITurnContext, BotCallbackHandler, CancellationToken)"/>
/// <exception cref="ArgumentNullException"><paramref name="reference"/> or
/// <paramref name="logic"/> is <c>null</c>.</exception>
public async Task ContinueConversationAsync(ConversationReference reference, BotCallbackHandler logic, CancellationToken cancellationToken)
{
if (reference == null)
{
throw new ArgumentNullException(nameof(reference));
}
if (logic == null)
{
throw new ArgumentNullException(nameof(logic));
}
var request = reference.GetContinuationActivity().ApplyConversationReference(reference, true);
using (var context = new TurnContext(this, request))
{
await RunPipelineAsync(context, logic, cancellationToken).ConfigureAwait(false);
}
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright(c) Microsoft Corporation.All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.Bot.Builder.Adapters.Twilio
{
/// <summary>
/// Defines values that a <see cref="TwilioAdapter"/> can use to connect to Twilio's SMS service.
/// </summary>
public class TwilioAdapterOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="TwilioAdapterOptions"/> class.
/// </summary>
/// <param name="twilioNumber">The twilio phone number.</param>
/// <param name="accountSid">The account id.</param>
/// <param name="authToken">The authentication token.</param>
/// <param name="validationUrl">validation URL for incoming requests.</param>
public TwilioAdapterOptions(string twilioNumber, string accountSid, string authToken, string validationUrl = null)
{
TwilioNumber = twilioNumber;
AccountSid = accountSid;
AuthToken = authToken;
ValidationUrl = validationUrl;
}
/// <summary>
/// Gets or sets the phone number associated with this Twilio app.
/// </summary>
/// <value>
/// The phone number, in the format 1XXXYYYZZZZ.
/// </value>
public string TwilioNumber { get; set; }
/// <summary>
/// Gets or sets the account SID from the Twilio account.
/// </summary>
/// <value>The account SID.</value>
public string AccountSid { get; set; }
/// <summary>
/// Gets or sets the API auth token associated with the Twilio account.
/// </summary>
/// <value>The authentication token.</value>
public string AuthToken { get; set; }
/// <summary>
/// Gets or sets an optional validation URL.
/// </summary>
/// <value>Optional validation URL to override the automatically generated URL signature used
/// to validate incoming requests. See the Twilio security documentation on
/// [validating requests](https://www.twilio.com/docs/usage/security#validating-requests).</value>
public string ValidationUrl { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.All rights reserved.
// Licensed under the MIT License.
using System.Threading.Tasks;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
namespace Microsoft.Bot.Builder.Adapters.Twilio
{
/// <summary>
/// Wrapper class for the Twilio API.
/// </summary>
public class TwilioClientWrapper
{
/// <summary>
/// Initializes the Twilio client with a user name and password.
/// </summary>
/// <param name="username">The user name for the Twilio API.</param>
/// <param name="password">The password for the Twilio API.</param>
public virtual void LogIn(string username, string password)
{
TwilioClient.Init(username, password);
}
/// <summary>
/// Sends a Twilio SMS message.
/// </summary>
/// <param name="messageOptions">An object containing the parameters for the message to send.</param>
/// <returns>The SID of the Twilio message sent.</returns>
public virtual async Task<string> SendMessage(CreateMessageOptions messageOptions)
{
var messageResource = await MessageResource.CreateAsync((CreateMessageOptions)messageOptions).ConfigureAwait(false);
return messageResource.Sid;
}
}
}

Просмотреть файл

@ -0,0 +1,187 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using Twilio.Exceptions;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Security;
using AuthenticationException = System.Security.Authentication.AuthenticationException;
#if SIGNASSEMBLY
[assembly: InternalsVisibleTo("Microsoft.Bot.Builder.Adapters.Twilio.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
#else
[assembly: InternalsVisibleTo("Microsoft.Bot.Builder.Adapters.Twilio.Tests")]
#endif
namespace Microsoft.Bot.Builder.Adapters.Twilio
{
/// <summary>
/// A helper class to create Activities and Twilio messages.
/// </summary>
internal static class TwilioHelper
{
/// <summary>
/// Creates Twilio SMS message options object from a Bot Framework <see cref="Activity"/>.
/// </summary>
/// <param name="activity">The activity.</param>
/// <param name="twilioNumber">The Twilio phone number assigned to the bot.</param>
/// <returns>The Twilio message options object.</returns>
/// <seealso cref="TwilioAdapter.SendActivitiesAsync(ITurnContext, Activity[], System.Threading.CancellationToken)"/>
public static CreateMessageOptions ActivityToTwilio(Activity activity, string twilioNumber)
{
if (activity == null || string.IsNullOrWhiteSpace(twilioNumber))
{
return null;
}
var mediaUrls = new List<Uri>();
if (activity.Attachments != null)
{
mediaUrls.AddRange(activity.Attachments.Select(attachment => new Uri(attachment.ContentUrl)));
}
var messageOptions = new CreateMessageOptions(activity.Conversation.Id)
{
ApplicationSid = activity.Conversation.Id,
From = twilioNumber,
Body = activity.Text,
MediaUrl = mediaUrls,
};
return messageOptions;
}
/// <summary>
/// Creates a Bot Framework <see cref="Activity"/> from an HTTP request that contains a Twilio message.
/// </summary>
/// <param name="httpRequest">The HTTP request.</param>
/// <param name="validationUrl">Optional validation URL to override the automatically
/// generated URL signature used to validate incoming requests.</param>
/// <param name="authToken">The authentication token for the Twilio app.</param>
/// <returns>The activity object.</returns>
/// <seealso cref="TwilioAdapter.ProcessAsync(HttpRequest, HttpResponse, IBot, System.Threading.CancellationToken)"/>
/// <seealso cref="ITwilioAdapterOptions.ValidationUrl"/>
public static Activity RequestToActivity(HttpRequest httpRequest, string validationUrl, string authToken)
{
if (httpRequest == null)
{
return null;
}
Dictionary<string, string> body;
using (var bodyStream = new StreamReader(httpRequest.Body))
{
body = QueryStringToDictionary(bodyStream.ReadToEnd());
}
ValidateRequest(httpRequest, body, validationUrl, authToken);
var twilioMessage = JsonConvert.DeserializeObject<TwilioMessage>(JsonConvert.SerializeObject(body));
return new Activity()
{
Id = twilioMessage.MessageSid,
Timestamp = DateTime.UtcNow,
ChannelId = Channels.Twilio,
Conversation = new ConversationAccount()
{
Id = twilioMessage.From ?? twilioMessage.Author,
},
From = new ChannelAccount()
{
Id = twilioMessage.From ?? twilioMessage.Author,
},
Recipient = new ChannelAccount()
{
Id = twilioMessage.To,
},
Text = twilioMessage.Body,
ChannelData = twilioMessage,
Type = ActivityTypes.Message,
Attachments = int.TryParse(twilioMessage.NumMedia, out var numMediaResult) && numMediaResult > 0 ? GetMessageAttachments(numMediaResult, body) : null,
};
}
/// <summary>
/// Validates an HTTP request as coming from Twilio.
/// </summary>
/// <param name="httpRequest">The request to validate.</param>
/// <param name="body">The request payload, as key-value pairs.</param>
/// <param name="validationUrl">Optional validation URL to override the automatically
/// generated URL signature used to validate incoming requests.</param>
/// <param name="authToken">The authentication token for the Twilio app.</param>
/// <exception cref="AuthenticationException">Validation failed.</exception>
private static void ValidateRequest(HttpRequest httpRequest, Dictionary<string, string> body, string validationUrl, string authToken)
{
var twilioSignature = httpRequest.Headers["x-twilio-signature"];
validationUrl = validationUrl ?? (httpRequest.Headers["x-forwarded-proto"][0] ?? httpRequest.Protocol + "://" + httpRequest.Host + httpRequest.Path);
var requestValidator = new RequestValidator(authToken);
if (!requestValidator.Validate(validationUrl, body, twilioSignature))
{
throw new AuthenticationException("Request does not match provided signature");
}
}
/// <summary>
/// Gets attachments from a Twilio message.
/// </summary>
/// <param name="numMedia">The number of media items to pull from the message body.</param>
/// <param name="message">A dictionary containing the Twilio message elements.</param>
/// <returns>An Attachments array with the converted attachments.</returns>
private static List<Attachment> GetMessageAttachments(int numMedia, Dictionary<string, string> message)
{
var attachments = new List<Attachment>();
for (var i = 0; i < numMedia; i++)
{
// Ensure MediaContentType and MediaUrl are present before adding the attachment
if (message.ContainsKey($"MediaContentType{i}") && message.ContainsKey($"MediaUrl{i}"))
{
var attachment = new Attachment()
{
ContentType = message[$"MediaContentType{i}"],
ContentUrl = message[$"MediaUrl{i}"],
};
attachments.Add(attachment);
}
}
return attachments;
}
/// <summary>
/// Converts a query string to a dictionary with key-value pairs.
/// </summary>
/// <param name="query">The query string to convert.</param>
/// <returns>A dictionary with the query values.</returns>
private static Dictionary<string, string> QueryStringToDictionary(string query)
{
var values = new Dictionary<string, string>();
if (string.IsNullOrWhiteSpace(query))
{
return values;
}
var pairs = query.Replace("+", "%20").Split('&');
foreach (var p in pairs)
{
var pair = p.Split('=');
var key = pair[0];
var value = Uri.UnescapeDataString(pair[1]);
values.Add(key, value);
}
return values;
}
}
}

Просмотреть файл

@ -0,0 +1,159 @@
// Copyright(c) Microsoft Corporation.All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
namespace Microsoft.Bot.Builder.Adapters.Twilio
{
/// <summary>
/// A class wrapping Twilio request parameters.
/// </summary>
/// <remarks>These parameters can be included in an HTTP request that contains a Twilio message.</remarks>
public class TwilioMessage
{
/// <summary>
/// Gets or sets the Author of the message.
/// </summary>
/// <value>The Author of the message.</value>
public string Author { get; set; }
/// <summary>
/// Gets or sets the receiver's country.
/// </summary>
/// <value>The receiver's country, such as "US".</value>
public string ToCountry { get; set; }
/// <summary>
/// Gets or sets the sender's country.
/// </summary>
/// <value>The sender's country, such as "US".</value>
public string FromCountry { get; set; }
/// <summary>
/// Gets or sets the receiver's state or province.
/// </summary>
/// <value>The receiver's state or province, such as "NY".</value>
public string ToState { get; set; }
/// <summary>
/// Gets or sets the `sms_id` found in the response of a phone verification start.
/// </summary>
/// <value>The`sms_id` found in the response of a phone verification start.</value>
public string SmsMessageSid { get; set; }
/// <summary>
/// Gets or sets the number of media files associated with the message.
/// </summary>
/// <value>The number of media files associated with the message.</value>
/// <remarks>A message can include up to 10 media files.</remarks>
public string NumMedia { get; set; }
/// <summary>
/// Gets or sets the URLs referencing the media content included with the message, if any.
/// </summary>
/// <value>URLs referencing the media content included with the message.</value>
public List<Uri> MediaUrls { get; set; }
/// <summary>
/// Gets or sets the content types for the media included with the message, if any.
/// </summary>
/// <value>The content types for the media included with the message.</value>
public List<string> MediaContentTypes { get; set; }
/// <summary>
/// Gets or sets the receiver's city.
/// </summary>
/// <value>The receiver's city, such as "FARMINGDALE".</value>
public string ToCity { get; set; }
/// <summary>
/// Gets or sets the sender's postal code.
/// </summary>
/// <value>The sender's postal code. </value>
public string FromZip { get; set; }
/// <summary>
/// Gets or sets the SMS security identifier.
/// </summary>
/// <value>The SMS message security identifier.</value>
/// <remarks>Same as the <see cref="MessageSid"/>.</remarks>
public string SmsSid { get; set; }
/// <summary>
/// Gets or sets the sender's state or province.
/// </summary>
/// <value>The sender's state or province, such as "NY".</value>
public string FromState { get; set; }
/// <summary>
/// Gets or sets the status of the message.
/// </summary>
/// <value>The status of the message, such as "received".</value>
/// <remarks>See [message status values](https://aka.ms/twilio-message-status-values)
/// for a list of the possible values.</remarks>
public string SmsStatus { get; set; }
/// <summary>
/// Gets or sets the sender's city.
/// </summary>
/// <value>The sender's city, such as "FARMINGDALE".</value>
public string FromCity { get; set; }
/// <summary>
/// Gets or sets the message text.
/// </summary>
/// <value>The message text. Can be up to 1,600 characters long.</value>
public string Body { get; set; }
/// <summary>
/// Gets or sets the phone number in E.164 format that received the message.
/// </summary>
/// <value>The phone number in E.164 format that received the message.</value>
public string To { get; set; }
/// <summary>
/// Gets or sets the recipient's postal code.
/// </summary>
/// <value>The recipient's postal code.</value>
public string ToZip { get; set; }
/// <summary>
/// Gets or sets the number of segments that make up the complete message.
/// </summary>
/// <value>The number of segments that make up the complete message.</value>
public string NumSegments { get; set; }
/// <summary>
/// Gets or sets the security identifier of the message.
/// </summary>
/// <value>The security identifier of the message.</value>
/// <remarks>For more information, see [Security Identifier (SID)](https://aka.ms/twilio-sid).
/// </remarks>
public string MessageSid { get; set; }
/// <summary>
/// Gets or sets the Sid of the Account that sent the message that created the resource.
/// </summary>
/// <value>The security identifier of the Account that sent the message.</value>
public string AccountSid { get; set; }
/// <summary>
/// Gets or sets the sender phone number.
/// </summary>
/// <value>The phone number (in E.164 format), alphanumeric sender ID, or Wireless SIM that initiated the message.</value>
public string From { get; set; }
/// <summary>
/// Gets or sets the API version used to process the message.
/// </summary>
/// <value>The API version used to process the message.</value>
public string ApiVersion { get; set; }
/// <summary>
/// Gets or sets the event type for using with Twilio Conversation API.
/// </summary>
/// <value>The type of event, e.g. "onMessageAdd", "onMessageAdded", "onConversationAdd".</value>
public string EventType { get; set; }
}
}

Просмотреть файл

@ -50,7 +50,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
/// <item>datetime -- combination of date and time like "march 23 2pm".</item>
/// <item>timerange -- a range of time like "2pm to 4pm".</item>
/// <item>daterange -- a range of dates like "march 23rd to 24th".</item>
/// <item>datetimerang -- a range of dates and times like "july 3rd 2pm to 5th 4pm".</item>
/// <item>datetimerange -- a range of dates and times like "july 3rd 2pm to 5th 4pm".</item>
/// <item>set -- a recurrence like "every monday".</item>
/// </list>
/// </remarks>

Просмотреть файл

@ -32,9 +32,9 @@ namespace Microsoft.Bot.Builder.AI.Luis
/// <param name="telemetryMetrics">Additional metrics to be logged to telemetry with the LuisResult event.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The LUIS results of the analysis of the current message text in the current turn's context activity.</returns>
Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics, CancellationToken cancellationToken = default(CancellationToken));
Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics, CancellationToken cancellationToken = default);
Task<T> RecognizeAsync<T>(ITurnContext turnContext, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics, CancellationToken cancellationToken = default(CancellationToken))
Task<T> RecognizeAsync<T>(ITurnContext turnContext, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics, CancellationToken cancellationToken = default)
where T : IRecognizerConvert, new();
/// <summary>
@ -44,7 +44,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
/// <param name="turnContext">Turn context.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Analysis of utterance.</returns>
new Task<T> RecognizeAsync<T>(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
new Task<T> RecognizeAsync<T>(ITurnContext turnContext, CancellationToken cancellationToken = default)
where T : IRecognizerConvert, new();
}
}

Просмотреть файл

@ -50,7 +50,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
{
var (applicationId, endpointKey, endpoint) = props;
if (!Guid.TryParse(applicationId, out var appGuid))
if (!Guid.TryParse(applicationId, out var _))
{
throw new ArgumentException($"\"{applicationId}\" is not a valid LUIS application id.");
}

Просмотреть файл

@ -13,7 +13,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
{
internal class LuisDelegatingHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Bot Builder Package name and version.
var assemblyName = this.GetType().Assembly.GetName();

Просмотреть файл

@ -360,7 +360,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
Text = utterance,
AlteredText = luisResult.AlteredQuery,
Intents = LuisUtil.GetIntents(luisResult),
Entities = LuisUtil.ExtractEntitiesAndMetadata(luisResult.Entities, luisResult.CompositeEntities, luisPredictionOptions.IncludeInstanceData ?? true),
Entities = LuisUtil.ExtractEntitiesAndMetadata(luisResult.Entities, luisResult.CompositeEntities, luisPredictionOptions.IncludeInstanceData ?? true, utterance),
};
LuisUtil.AddProperties(luisResult, recognizerResult);
if (_includeApiResults)
@ -428,13 +428,6 @@ namespace Microsoft.Bot.Builder.AI.Luis
return currentHandler;
}
private HttpClientHandler CreateRootHandler() =>
// Create our root handler
#if FullNetFx
return new WebRequestHandler();
#else
new HttpClientHandler();
#endif
private HttpClientHandler CreateRootHandler() => new HttpClientHandler();
}
}

Просмотреть файл

@ -35,7 +35,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
}
}
internal static JObject ExtractEntitiesAndMetadata(IList<EntityModel> entities, IList<CompositeEntityModel> compositeEntities, bool verbose)
internal static JObject ExtractEntitiesAndMetadata(IList<EntityModel> entities, IList<CompositeEntityModel> compositeEntities, bool verbose, string utterance)
{
var entitiesAndMetadata = new JObject();
if (verbose)
@ -49,7 +49,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
if (compositeEntities != null && compositeEntities.Any())
{
compositeEntityTypes = new HashSet<string>(compositeEntities.Select(ce => ce.ParentType));
entities = compositeEntities.Aggregate(entities, (current, compositeEntity) => PopulateCompositeEntityModel(compositeEntity, current, entitiesAndMetadata, verbose));
entities = compositeEntities.Aggregate(entities, (current, compositeEntity) => PopulateCompositeEntityModel(compositeEntity, current, entitiesAndMetadata, verbose, utterance));
}
foreach (var entity in entities)
@ -64,7 +64,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
if (verbose)
{
AddProperty((JObject)entitiesAndMetadata[_metadataKey], ExtractNormalizedEntityName(entity), ExtractEntityMetadata(entity));
AddProperty((JObject)entitiesAndMetadata[_metadataKey], ExtractNormalizedEntityName(entity), ExtractEntityMetadata(entity, utterance));
}
}
@ -162,15 +162,18 @@ namespace Microsoft.Bot.Builder.AI.Luis
}
}
internal static JObject ExtractEntityMetadata(EntityModel entity)
internal static JObject ExtractEntityMetadata(EntityModel entity, string utterance)
{
var start = (int)entity.StartIndex;
var end = (int)entity.EndIndex + 1;
dynamic obj = JObject.FromObject(new
{
startIndex = (int)entity.StartIndex,
endIndex = (int)entity.EndIndex + 1,
text = entity.Entity,
startIndex = start,
endIndex = end,
text = entity.Entity.Length == end - start ? entity.Entity : utterance.Substring(start, end - start),
type = entity.Type,
});
if (entity.AdditionalProperties != null)
{
if (entity.AdditionalProperties.TryGetValue("score", out var score))
@ -223,7 +226,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
return type.Replace('.', '_').Replace(' ', '_');
}
internal static IList<EntityModel> PopulateCompositeEntityModel(CompositeEntityModel compositeEntity, IList<EntityModel> entities, JObject entitiesAndMetadata, bool verbose)
internal static IList<EntityModel> PopulateCompositeEntityModel(CompositeEntityModel compositeEntity, IList<EntityModel> entities, JObject entitiesAndMetadata, bool verbose, string utterance)
{
var childrenEntites = new JObject();
var childrenEntitiesMetadata = new JObject();
@ -243,7 +246,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
if (verbose)
{
childrenEntitiesMetadata = ExtractEntityMetadata(compositeEntityMetadata);
childrenEntitiesMetadata = ExtractEntityMetadata(compositeEntityMetadata, utterance);
childrenEntites[_metadataKey] = new JObject();
}
@ -259,7 +262,7 @@ namespace Microsoft.Bot.Builder.AI.Luis
}
// This entity doesn't belong to this composite entity
if (child.Type != entity.Type || !CompositeContainsEntity(compositeEntityMetadata, entity))
if (child.Type != entity.Type || !CompositeContainsEntity(compositeEntityMetadata, entity) || child.Value != entity.Entity)
{
continue;
}
@ -270,8 +273,10 @@ namespace Microsoft.Bot.Builder.AI.Luis
if (verbose)
{
AddProperty((JObject)childrenEntites[_metadataKey], ExtractNormalizedEntityName(entity), ExtractEntityMetadata(entity));
AddProperty((JObject)childrenEntites[_metadataKey], ExtractNormalizedEntityName(entity), ExtractEntityMetadata(entity, utterance));
}
break;
}
}

Просмотреть файл

@ -42,5 +42,6 @@
<ItemGroup>
<ProjectReference Include="..\Microsoft.Bot.Builder\Microsoft.Bot.Builder.csproj" />
<ProjectReference Include="..\Microsoft.Bot.Configuration\Microsoft.Bot.Configuration.csproj" />
<ProjectReference Include="..\Microsoft.Bot.Schema\Microsoft.Bot.Schema.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Defines an extension for a list entity.
/// </summary>
public class DynamicList
{
/// <summary>
/// Initializes a new instance of the <see cref="DynamicList"/> class.
/// </summary>
public DynamicList()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DynamicList"/> class.
/// </summary>
/// <param name="entity">The name of the list entity to extend.</param>
/// <param name="requestLists">The lists to append on the extended list entity.</param>
public DynamicList(string entity, IList<ListElement> requestLists)
{
Entity = entity;
List = requestLists;
}
/// <summary>
/// Gets or sets the name of the list entity to extend.
/// </summary>
/// <value>
/// The name of the list entity to extend.
/// </value>
[JsonProperty(PropertyName = "listEntityName")]
public string Entity { get; set; }
/// <summary>
/// Gets or sets the lists to append on the extended list entity.
/// </summary>
/// <value>
/// The lists to append on the extended list entity.
/// </value>
[JsonProperty(PropertyName = "requestLists")]
public IList<ListElement> List { get; set; }
/// <summary>
/// Validate the object.
/// </summary>
/// <exception cref="Microsoft.Rest.ValidationException">
/// Thrown if validation fails.
/// </exception>
public virtual void Validate()
{
// Required: ListEntityName, RequestLists
if (Entity == null || List == null)
{
throw new Microsoft.Rest.ValidationException($"DynamicList requires Entity and List to be defined.");
}
foreach (var elt in List)
{
elt.Validate();
}
}
}
}

Просмотреть файл

@ -0,0 +1,85 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Defines a user predicted entity that extends an already existing one.
/// </summary>
public class ExternalEntity
{
/// <summary>
/// Initializes a new instance of the <see cref="ExternalEntity"/> class.
/// </summary>
public ExternalEntity()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ExternalEntity"/> class.
/// </summary>
/// <param name="entity">The name of the entity to extend.</param>
/// <param name="start">The start character index of the predicted entity.</param>
/// <param name="length">The length of the predicted entity.</param>
/// <param name="resolution">A user supplied custom resolution to return as the entity's prediction.</param>
public ExternalEntity(string entity, int start, int length, object resolution = null)
{
Entity = entity;
Start = start;
Length = length;
Resolution = resolution;
}
/// <summary>
/// Gets or sets the name of the entity to extend.
/// </summary>
/// <value>
/// The name of the entity to extend.
/// </value>
[JsonProperty(PropertyName = "entityName")]
public string Entity { get; set; }
/// <summary>
/// Gets or sets the start character index of the predicted entity.
/// </summary>
/// <value>
/// The start character index of the predicted entity.
/// </value>
[JsonProperty(PropertyName = "startIndex")]
public int Start { get; set; }
/// <summary>
/// Gets or sets the length of the predicted entity.
/// </summary>
/// <value>
/// The length of the predicted entity.
/// </value>
[JsonProperty(PropertyName = "entityLength")]
public int Length { get; set; }
/// <summary>
/// Gets or sets a user supplied custom resolution to return as the entity's prediction.
/// </summary>
/// <value>
/// A user supplied custom resolution to return as the entity's prediction.
/// </value>
[JsonProperty(PropertyName = "resolution")]
public object Resolution { get; set; }
/// <summary>
/// Validate the object.
/// </summary>
/// <exception cref="Microsoft.Rest.ValidationException">
/// Thrown if validation fails.
/// </exception>
public virtual void Validate()
{
if (Entity == null || Length == 0)
{
throw new Microsoft.Rest.ValidationException($"ExternalEntity requires an EntityName and EntityLength > 0");
}
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Recognizer with Telemetry support.
/// </summary>
public interface ITelemetryRecognizer : IRecognizer
{
/// <summary>
/// Gets a value indicating whether determines whether to log personal information that came from the user.
/// </summary>
/// <value>If true, will log personal information into the IBotTelemetryClient.TrackEvent method; otherwise the properties will be filtered.</value>
bool LogPersonalInformation { get; }
/// <summary>
/// Gets the currently configured <see cref="IBotTelemetryClient"/> that logs the LuisResult event.
/// </summary>
/// <value>The <see cref="IBotTelemetryClient"/> being used to log events.</value>
IBotTelemetryClient TelemetryClient { get; }
/// <summary>
/// Return results of the analysis (suggested intents and entities) using the turn context.
/// </summary>
/// <param name="turnContext">Context object containing information for a single turn of conversation with a user.</param>
/// <param name="telemetryProperties">Additional properties to be logged to telemetry with the LuisResult event.</param>
/// <param name="telemetryMetrics">Additional metrics to be logged to telemetry with the LuisResult event.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The LUIS results of the analysis of the current message text in the current turn's context activity.</returns>
Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics, CancellationToken cancellationToken = default);
Task<T> RecognizeAsync<T>(ITurnContext turnContext, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics, CancellationToken cancellationToken = default)
where T : IRecognizerConvert, new();
/// <summary>
/// Runs an utterance through a recognizer and returns a strongly-typed recognizer result.
/// </summary>
/// <typeparam name="T">The recognition result type.</typeparam>
/// <param name="turnContext">Turn context.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Analysis of utterance.</returns>
new Task<T> RecognizeAsync<T>(ITurnContext turnContext, CancellationToken cancellationToken = default)
where T : IRecognizerConvert, new();
}
}

Просмотреть файл

@ -0,0 +1,64 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Defines a sub-list to append to an existing list entity.
/// </summary>
public class ListElement
{
/// <summary>
/// Initializes a new instance of the <see cref="ListElement"/> class.
/// </summary>
public ListElement()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ListElement"/> class.
/// </summary>
/// <param name="canonicalForm">The canonical form of the sub-list.</param>
/// <param name="synonyms">The synonyms of the canonical form.</param>
public ListElement(string canonicalForm, IList<string> synonyms = null)
{
CanonicalForm = canonicalForm;
Synonyms = synonyms;
}
/// <summary>
/// Gets or sets the canonical form of the sub-list.
/// </summary>
/// <value>
/// The canonical form of the sub-list.
/// </value>
[JsonProperty(PropertyName = "canonicalForm")]
public string CanonicalForm { get; set; }
/// <summary>
/// Gets or sets the synonyms of the canonical form.
/// </summary>
/// <value>
/// The synonyms of the canonical form.
/// </value>
[JsonProperty(PropertyName = "synonyms")]
public IList<string> Synonyms { get; set; }
/// <summary>
/// Validate the object.
/// </summary>
/// <exception cref="Microsoft.Rest.ValidationException">
/// Thrown if parameters are invalid.
/// </exception>
public virtual void Validate()
{
if (CanonicalForm == null)
{
throw new Microsoft.Rest.ValidationException($"RequestList requires CanonicalForm to be defined.");
}
}
}
}

Просмотреть файл

@ -0,0 +1,125 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Linq;
using System.Web;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Data describing a LUIS application.
/// </summary>
public class LuisApplication
{
public LuisApplication()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LuisApplication"/> class.
/// </summary>
/// <param name="applicationId">LUIS application ID.</param>
/// <param name="endpointKey">LUIS subscription or endpoint key.</param>
/// <param name="endpoint">LUIS endpoint to use like https://westus.api.cognitive.microsoft.com.</param>
public LuisApplication(string applicationId, string endpointKey, string endpoint)
: this((applicationId, endpointKey, endpoint))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LuisApplication"/> class.
/// </summary>
/// <param name="applicationEndpoint">LUIS application endpoint.</param>
public LuisApplication(string applicationEndpoint)
: this(Parse(applicationEndpoint))
{
}
private LuisApplication(ValueTuple<string, string, string> props)
{
var (applicationId, endpointKey, endpoint) = props;
if (!Guid.TryParse(applicationId, out var _))
{
throw new ArgumentException($"\"{applicationId}\" is not a valid LUIS application id.");
}
if (!Guid.TryParse(endpointKey, out var subscriptionGuid))
{
throw new ArgumentException($"\"{subscriptionGuid}\" is not a valid LUIS subscription key.");
}
if (string.IsNullOrWhiteSpace(endpoint))
{
endpoint = "https://westus.api.cognitive.microsoft.com";
}
if (!Uri.IsWellFormedUriString(endpoint, UriKind.Absolute))
{
throw new ArgumentException($"\"{endpoint}\" is not a valid LUIS endpoint.");
}
ApplicationId = applicationId;
EndpointKey = endpointKey;
Endpoint = endpoint;
}
/// <summary>
/// Gets or sets lUIS application ID.
/// </summary>
/// <value>
/// LUIS application ID.
/// </value>
public string ApplicationId { get; set; }
/// <summary>
/// Gets or sets lUIS subscription or endpoint key.
/// </summary>
/// <value>
/// LUIS subscription or endpoint key.
/// </value>
public string EndpointKey { get; set; }
/// <summary>
/// Gets or sets lUIS endpoint like https://westus.api.cognitive.microsoft.com.
/// </summary>
/// <value>
/// LUIS endpoint where application is hosted.
/// </value>
public string Endpoint { get; set; }
private static (string applicationId, string endpointKey, string endpoint) Parse(string applicationEndpoint)
{
if (!Uri.TryCreate(applicationEndpoint, UriKind.Absolute, out var uri))
{
throw new ArgumentException(nameof(applicationEndpoint));
}
string applicationId = null;
var foundApps = false;
foreach (var segment in uri.Segments)
{
if (foundApps)
{
applicationId = segment.TrimEnd('/');
break;
}
if (segment == "apps/")
{
foundApps = true;
}
}
if (applicationId == null)
{
throw new ArgumentException($"Could not find application Id in {applicationEndpoint}");
}
var endpointKey = HttpUtility.ParseQueryString(uri.Query).Get("subscription-key");
var endpoint = uri.GetLeftPart(UriPartial.Authority);
return (applicationId, endpointKey, endpoint);
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.AI.Luis
{
internal class LuisDelegatingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Bot Builder Package name and version.
var assemblyName = this.GetType().Assembly.GetName();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(assemblyName.Name, assemblyName.Version.ToString()));
// Platform information: OS and language runtime.
var framework = Assembly
.GetEntryAssembly()?
.GetCustomAttribute<TargetFrameworkAttribute>()?
.FrameworkName;
var comment = $"({Environment.OSVersion.VersionString};{framework})";
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(comment));
// Forward the call.
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
}
}

Просмотреть файл

@ -0,0 +1,94 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using Microsoft.Bot.Builder.AI.LuisV3;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Optional parameters for a LUIS prediction request.
/// </summary>
public class LuisPredictionOptions
{
/// <summary>
/// Gets or sets a value indicating whether all intents come back or only the top one.
/// </summary>
/// <value>
/// True for returning all intents.
/// </value>
public bool IncludeAllIntents { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether or not instance data should be included in response.
/// </summary>
/// <value>
/// A value indicating whether or not instance data should be included in response.
/// </value>
public bool IncludeInstanceData { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether API results should be included.
/// </summary>
/// <value>True to include API results.</value>
/// <remarks>This is mainly useful for testing or getting access to LUIS features not yet in the SDK.</remarks>
public bool IncludeAPIResults { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether queries should be logged in LUIS.
/// </summary>
/// <value>
/// If queries should be logged in LUIS in order to help build better models through active learning.
/// </value>
/// <remarks>The default is to log queries to LUIS in order to support active learning. To default to the Luis setting set to null.</remarks>
public bool? Log { get; set; } = true;
/// <summary>
/// Gets or sets dynamic lists used to recognize entities for a particular query.
/// </summary>
/// <value>
/// Dynamic lists of things like contact names to recognize at query time.
/// </value>
public IList<DynamicList> DynamicLists { get; set; }
/// <summary>
/// Gets or sets external entities recognized in the query.
/// </summary>
/// <value>
/// External entities recognized in query.
/// </value>
public IList<ExternalEntity> ExternalEntities { get; set; }
/// <summary>
/// Gets or sets a value indicating whether external entities should override other means of recognizing entities.
/// </summary>
/// <value>
/// Boolean for if external entities should be preferred to the results from LUIS models.
/// </value>
public bool PreferExternalEntities { get; set; } = true;
/// <summary>
/// Gets or sets the LUIS slot to use for the application.
/// </summary>
/// <value>
/// The LUIS slot to use for the application.
/// </value>
/// <remarks>
/// By default this uses the production slot. You can find other standard slots in <see cref="LuisSlot"/>.
/// If you specify a Version, then a private version of the application is used instead of a slot.
/// </remarks>
public string Slot { get; set; } = LuisSlot.Production;
/// <summary>
/// Gets or sets the specific version of the application to access.
/// </summary>
/// <value>
/// Version to access.
/// </value>
/// <remarks>
/// LUIS supports versions and this is the version to use instead of a slot.
/// If this is specified, then the <see cref="Slot"/> is ignored.
/// </remarks>
public string Version { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,443 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.TraceExtensions;
using Microsoft.Bot.Schema;
using Microsoft.Rest;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <inheritdoc />
/// <summary>
/// A LUIS based implementation of <see cref="ITelemetryRecognizer"/> for the V3 endpoint.
/// </summary>
public class LuisRecognizer : ITelemetryRecognizer
{
/// <summary>
/// The value type for a LUIS trace activity.
/// </summary>
public const string LuisTraceType = "https://www.luis.ai/schemas/trace";
/// <summary>
/// The context label for a LUIS trace activity.
/// </summary>
public const string LuisTraceLabel = "LuisV3 Trace";
private readonly LuisApplication _application;
private readonly LuisPredictionOptions _predictionOptions;
/// <summary>
/// Initializes a new instance of the <see cref="LuisRecognizer"/> class.
/// </summary>
/// <param name="application">The LUIS application to use to recognize text.</param>
/// <param name="recognizerOptions">(Optional) Options for the created recognizer.</param>
/// <param name="predictionOptions">(Optional) The default LUIS prediction options to use.</param>
public LuisRecognizer(LuisApplication application, LuisRecognizerOptions recognizerOptions = null, LuisPredictionOptions predictionOptions = null)
{
recognizerOptions = recognizerOptions ?? new LuisRecognizerOptions();
_application = application ?? throw new ArgumentNullException(nameof(application));
_predictionOptions = predictionOptions ?? new LuisPredictionOptions();
TelemetryClient = recognizerOptions.TelemetryClient;
LogPersonalInformation = recognizerOptions.LogPersonalInformation;
var delegatingHandler = new LuisDelegatingHandler();
var httpClientHandler = recognizerOptions.HttpClient ?? CreateRootHandler();
var currentHandler = CreateHttpHandlerPipeline(httpClientHandler, delegatingHandler);
DefaultHttpClient = new HttpClient(currentHandler, false)
{
Timeout = recognizerOptions.Timeout,
};
DefaultHttpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _application.EndpointKey);
}
/// <summary>
/// Gets client to use for http.
/// </summary>
/// <value>
/// Client to use for http.
/// </value>
public static HttpClient DefaultHttpClient { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether to log personal information that came from the user to telemetry.
/// </summary>
/// <value>If true, personal information is logged to Telemetry; otherwise the properties will be filtered.</value>
public bool LogPersonalInformation { get; set; }
/// <summary>
/// Gets the currently configured <see cref="IBotTelemetryClient"/> that logs the LuisResult event.
/// </summary>
/// <value>The <see cref="IBotTelemetryClient"/> being used to log events.</value>
public IBotTelemetryClient TelemetryClient { get; }
/// <summary>
/// Returns the name of the top scoring intent from a set of LUIS results.
/// </summary>
/// <param name="results">Result set to be searched.</param>
/// <param name="defaultIntent">(Optional) Intent name to return should a top intent be found. Defaults to a value of "None".</param>
/// <param name="minScore">(Optional) Minimum score needed for an intent to be considered as a top intent. If all intents in the set are below this threshold then the `defaultIntent` will be returned. Defaults to a value of `0.0`.</param>
/// <returns>The top scoring intent name.</returns>
public static string TopIntent(RecognizerResult results, string defaultIntent = "None", double minScore = 0.0)
{
if (results == null)
{
throw new ArgumentNullException(nameof(results));
}
string topIntent = null;
var topScore = -1.0;
foreach (var intent in results.Intents)
{
var score = (double)intent.Value.Score;
if (score > topScore && score >= minScore)
{
topIntent = intent.Key;
topScore = score;
}
}
return !string.IsNullOrEmpty(topIntent) ? topIntent : defaultIntent;
}
/* IRecognizer */
/// <inheritdoc />
public virtual async Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken)
=> await RecognizeInternalAsync(turnContext, null, null, null, cancellationToken).ConfigureAwait(false);
/// <summary>
/// Runs an utterance through a recognizer and returns a generic recognizer result.
/// </summary>
/// <param name="turnContext">Turn context.</param>
/// <param name="predictionOptions">A <see cref="LuisPredictionOptions"/> instance to be used by the call.
/// This parameter gets merged with the default <see cref="LuisPredictionOptions"/> passed in the constructor.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Analysis of utterance.</returns>
public virtual async Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, LuisPredictionOptions predictionOptions, CancellationToken cancellationToken)
=> await RecognizeInternalAsync(turnContext, predictionOptions, null, null, cancellationToken).ConfigureAwait(false);
/// <inheritdoc />
public virtual async Task<T> RecognizeAsync<T>(ITurnContext turnContext, CancellationToken cancellationToken)
where T : IRecognizerConvert, new()
{
var result = new T();
result.Convert(await RecognizeInternalAsync(turnContext, null, null, null, cancellationToken).ConfigureAwait(false));
return result;
}
/* ITelemetryRecognizer */
/// <summary>
/// Return results of the analysis (Suggested actions and intents).
/// </summary>
/// <param name="turnContext">Context object containing information for a single turn of conversation with a user.</param>
/// <param name="telemetryProperties">Additional properties to be logged to telemetry with the LuisResult event.</param>
/// <param name="telemetryMetrics">Additional metrics to be logged to telemetry with the LuisResult event.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The LUIS results of the analysis of the current message text in the current turn's context activity.</returns>
public virtual async Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics = null, CancellationToken cancellationToken = default)
=> await RecognizeInternalAsync(turnContext, null, telemetryProperties, telemetryMetrics, cancellationToken).ConfigureAwait(false);
/// <summary>
/// Return results of the analysis (Suggested actions and intents).
/// </summary>
/// <typeparam name="T">The recognition result type.</typeparam>
/// <param name="turnContext">Context object containing information for a single turn of conversation with a user.</param>
/// <param name="telemetryProperties">Additional properties to be logged to telemetry with the LuisResult event.</param>
/// <param name="telemetryMetrics">Additional metrics to be logged to telemetry with the LuisResult event.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The LUIS results of the analysis of the current message text in the current turn's context activity.</returns>
public virtual async Task<T> RecognizeAsync<T>(ITurnContext turnContext, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics = null, CancellationToken cancellationToken = default)
where T : IRecognizerConvert, new()
{
var result = new T();
result.Convert(await RecognizeInternalAsync(turnContext, null, telemetryProperties, telemetryMetrics, cancellationToken).ConfigureAwait(false));
return result;
}
/* LUIS specific methods */
/// <summary>
/// Return results of the analysis (Suggested actions and intents).
/// </summary>
/// <param name="turnContext">Context object containing information for a single turn of conversation with a user.</param>
/// <param name="predictionOptions">A <see cref="LuisPredictionOptions"/> instance to be used by the call.
/// This parameter gets merged with the default <see cref="LuisPredictionOptions"/> passed in the constructor.</param>
/// <param name="telemetryProperties">Additional properties to be logged to telemetry with the LuisResult event.</param>
/// <param name="telemetryMetrics">Additional metrics to be logged to telemetry with the LuisResult event.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The LUIS results of the analysis of the current message text in the current turn's context activity.</returns>
public virtual async Task<RecognizerResult> RecognizeAsync(
ITurnContext turnContext,
LuisPredictionOptions predictionOptions = null,
Dictionary<string, string> telemetryProperties = null,
Dictionary<string, double> telemetryMetrics = null,
CancellationToken cancellationToken = default)
=> await RecognizeInternalAsync(turnContext, predictionOptions, telemetryProperties, telemetryMetrics, cancellationToken).ConfigureAwait(false);
/// <summary>
/// Return results of the analysis (Suggested actions and intents).
/// </summary>
/// <typeparam name="T">The recognition result type.</typeparam>
/// <param name="turnContext">Context object containing information for a single turn of conversation with a user.</param>
/// <param name="predictionOptions">A <see cref="LuisPredictionOptions"/> instance to be used by the call.
/// This parameter gets merged with the default <see cref="LuisPredictionOptions"/> passed in the constructor.</param>
/// <param name="telemetryProperties">Additional properties to be logged to telemetry with the LuisResult event.</param>
/// <param name="telemetryMetrics">Additional metrics to be logged to telemetry with the LuisResult event.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The LUIS results of the analysis of the current message text in the current turn's context activity.</returns>
public virtual async Task<T> RecognizeAsync<T>(
ITurnContext turnContext,
LuisPredictionOptions predictionOptions = null,
Dictionary<string, string> telemetryProperties = null,
Dictionary<string, double> telemetryMetrics = null,
CancellationToken cancellationToken = default)
where T : IRecognizerConvert, new()
{
var result = new T();
result.Convert(await RecognizeInternalAsync(turnContext, predictionOptions, telemetryProperties, telemetryMetrics, cancellationToken).ConfigureAwait(false));
return result;
}
/// <summary>
/// Invoked prior to a LuisResult being logged.
/// </summary>
/// <param name="recognizerResult">The Luis Results for the call.</param>
/// <param name="turnContext">Context object containing information for a single turn of conversation with a user.</param>
/// <param name="telemetryProperties">Additional properties to be logged to telemetry with the LuisResult event.</param>
/// <param name="telemetryMetrics">Additional metrics to be logged to telemetry with the LuisResult event.</param>
protected virtual void OnRecognizerResult(RecognizerResult recognizerResult, ITurnContext turnContext, Dictionary<string, string> telemetryProperties = null, Dictionary<string, double> telemetryMetrics = null)
{
var properties = FillLuisEventProperties(recognizerResult, turnContext, telemetryProperties);
// Track the event
TelemetryClient.TrackEvent(LuisTelemetryConstants.LuisResult, properties, telemetryMetrics);
}
/// <summary>
/// Fills the event properties for LuisResult event for telemetry.
/// These properties are logged when the recognizer is called.
/// </summary>
/// <param name="recognizerResult">Last activity sent from user.</param>
/// <param name="turnContext">Context object containing information for a single turn of conversation with a user.</param>
/// <param name="telemetryProperties">Additional properties to be logged to telemetry with the LuisResult event.</param>
/// <returns>A dictionary that is sent as "Properties" to IBotTelemetryClient.TrackEvent method for the BotMessageSend event.</returns>
protected Dictionary<string, string> FillLuisEventProperties(RecognizerResult recognizerResult, ITurnContext turnContext, Dictionary<string, string> telemetryProperties = null)
{
var topTwoIntents = (recognizerResult.Intents.Count > 0) ? recognizerResult.Intents.OrderByDescending(x => x.Value.Score).Take(2).ToArray() : null;
// Add the intent score and conversation id properties
var properties = new Dictionary<string, string>()
{
{ LuisTelemetryConstants.ApplicationIdProperty, _application.ApplicationId },
{ LuisTelemetryConstants.IntentProperty, topTwoIntents?[0].Key ?? string.Empty },
{ LuisTelemetryConstants.IntentScoreProperty, topTwoIntents?[0].Value.Score?.ToString("N2") ?? "0.00" },
{ LuisTelemetryConstants.Intent2Property, (topTwoIntents?.Count() > 1) ? topTwoIntents?[1].Key ?? string.Empty : string.Empty },
{ LuisTelemetryConstants.IntentScore2Property, (topTwoIntents?.Count() > 1) ? topTwoIntents?[1].Value.Score?.ToString("N2") ?? "0.00" : "0.00" },
{ LuisTelemetryConstants.FromIdProperty, turnContext.Activity.From.Id },
};
if (recognizerResult.Properties.TryGetValue("sentiment", out var sentiment) && sentiment is JObject)
{
if (((JObject)sentiment).TryGetValue("label", out var label))
{
properties.Add(LuisTelemetryConstants.SentimentLabelProperty, label.Value<string>());
}
if (((JObject)sentiment).TryGetValue("score", out var score))
{
properties.Add(LuisTelemetryConstants.SentimentScoreProperty, score.Value<string>());
}
}
var entities = recognizerResult.Entities?.ToString();
properties.Add(LuisTelemetryConstants.EntitiesProperty, entities);
// Use the LogPersonalInformation flag to toggle logging PII data, text is a common example
if (LogPersonalInformation && !string.IsNullOrEmpty(turnContext.Activity.Text))
{
properties.Add(LuisTelemetryConstants.QuestionProperty, turnContext.Activity.Text);
}
// Additional Properties can override "stock" properties.
if (telemetryProperties != null)
{
properties = telemetryProperties.Concat(properties)
.GroupBy(kv => kv.Key)
.ToDictionary(g => g.Key, g => g.First().Value);
}
return properties;
}
private string AddParam(string query, string prop, bool? val)
{
var result = query;
if (val.HasValue)
{
if (query == null)
{
result = $"{prop}={val.Value}";
}
else
{
result += $"&{prop}={val.Value}";
}
}
return result;
}
private async Task<RecognizerResult> RecognizeInternalAsync(ITurnContext turnContext, LuisPredictionOptions predictionOptions, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics, CancellationToken cancellationToken)
{
BotAssert.ContextNotNull(turnContext);
if (turnContext.Activity.Type != ActivityTypes.Message)
{
return null;
}
var options = predictionOptions ?? _predictionOptions;
var utterance = turnContext.Activity?.AsMessageActivity()?.Text;
RecognizerResult recognizerResult;
JObject luisResponse = null;
if (string.IsNullOrWhiteSpace(utterance))
{
recognizerResult = new RecognizerResult
{
Text = utterance,
Intents = new Dictionary<string, IntentScore>() { { string.Empty, new IntentScore() { Score = 1.0 } } },
Entities = new JObject(),
};
}
else
{
var uri = new UriBuilder(_application.Endpoint);
// TODO: When the endpoint GAs, we will need to change this. I could make it an option, but other code is likely to need to change.
uri.Path += $"luis/v3.0-preview/apps/{_application.ApplicationId}";
var query = AddParam(null, "verbose", options.IncludeInstanceData);
query = AddParam(query, "log", options.Log);
query = AddParam(query, "show-all-intents", options.IncludeAllIntents);
uri.Query = query;
var content = new JObject
{
{ "query", utterance },
};
var queryOptions = new JObject
{
{ "overridePredictions", options.PreferExternalEntities },
};
content.Add("options", queryOptions);
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
if (options.DynamicLists != null)
{
foreach (var list in options.DynamicLists)
{
list.Validate();
}
content.Add("dynamicLists", (JArray)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(options.DynamicLists, settings)));
}
if (options.ExternalEntities != null)
{
foreach (var entity in options.ExternalEntities)
{
entity.Validate();
}
content.Add("externalEntities", (JArray)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(options.ExternalEntities, settings)));
}
if (options.Version == null)
{
uri.Path += $"/slots/{options.Slot}/predict";
}
else
{
uri.Path += $"/versions/{options.Version}/predict";
}
var response = await DefaultHttpClient.PostAsync(uri.Uri, new StringContent(content.ToString(), System.Text.Encoding.UTF8, "application/json")).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
luisResponse = (JObject)JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
var prediction = (JObject)luisResponse["prediction"];
recognizerResult = new RecognizerResult
{
Text = utterance,
AlteredText = prediction["alteredQuery"]?.Value<string>(),
Intents = LuisUtil.GetIntents(prediction),
Entities = LuisUtil.ExtractEntitiesAndMetadata(prediction),
};
LuisUtil.AddProperties(prediction, recognizerResult);
if (options.IncludeAPIResults)
{
recognizerResult.Properties.Add("luisResult", luisResponse);
}
}
// Log telemetry
OnRecognizerResult(recognizerResult, turnContext, telemetryProperties, telemetryMetrics);
var traceInfo = JObject.FromObject(
new
{
recognizerResult,
luisModel = new
{
ModelID = _application.ApplicationId,
},
luisOptions = options,
luisResult = luisResponse,
});
await turnContext.TraceActivityAsync("LuisRecognizer", traceInfo, LuisTraceType, LuisTraceLabel, cancellationToken).ConfigureAwait(false);
return recognizerResult;
}
private DelegatingHandler CreateHttpHandlerPipeline(HttpClientHandler httpClientHandler, params DelegatingHandler[] handlers)
{
// Now, the RetryAfterDelegatingHandler should be the absolute outermost handler
// because it's extremely lightweight and non-interfering
DelegatingHandler currentHandler =
new RetryDelegatingHandler(new RetryAfterDelegatingHandler { InnerHandler = httpClientHandler });
if (handlers != null)
{
for (var i = handlers.Length - 1; i >= 0; --i)
{
var handler = handlers[i];
// Non-delegating handlers are ignored since we always
// have RetryDelegatingHandler as the outer-most handler
while (handler.InnerHandler is DelegatingHandler)
{
handler = handler.InnerHandler as DelegatingHandler;
}
handler.InnerHandler = currentHandler;
currentHandler = handlers[i];
}
}
return currentHandler;
}
private HttpClientHandler CreateRootHandler() => new HttpClientHandler();
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Net.Http;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Optional parameters for a LUIS recognizer.
/// </summary>
public class LuisRecognizerOptions
{
/// <summary>
/// Gets or sets the time span to wait before the request times out.
/// </summary>
/// <value>
/// The time span to wait before the request times out. Default is 2 seconds.
/// </value>
public TimeSpan Timeout { get; set; } = new TimeSpan(0, 2, 0);
/// <summary>
/// Gets or sets the IBotTelemetryClient used to log the LuisResult event.
/// </summary>
/// <value>
/// The client used to log telemetry events.
/// </value>
[JsonIgnore]
public IBotTelemetryClient TelemetryClient { get; set; } = new NullBotTelemetryClient();
/// <summary>
/// Gets or sets a value indicating whether to log personal information that came from the user to telemetry.
/// </summary>
/// <value>If true, personal information is logged to Telemetry; otherwise the properties will be filtered.</value>
public bool LogPersonalInformation { get; set; } = false;
/// <summary>
/// Gets or sets the handler for sending http calls.
/// </summary>
/// <value>
/// Handler for intercepting http calls for logging or testing.
/// </value>
public HttpClientHandler HttpClient { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Bot.Builder.AI.LuisV3
{
/// <summary>
/// Default LUIS slot names.
/// </summary>
public static class LuisSlot
{
/// <summary>
/// Production slot on LUIS.
/// </summary>
public static readonly string Production = "production";
/// <summary>
/// Staging slot on LUIS.
/// </summary>
public static readonly string Staging = "staging";
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// The IBotTelemetryClient event and property names that logged by default.
/// </summary>
public static class LuisTelemetryConstants
{
public static readonly string LuisResult = "LuisResult"; // Event name
public static readonly string ApplicationIdProperty = "applicationId";
public static readonly string IntentProperty = "intent";
public static readonly string IntentScoreProperty = "intentScore";
public static readonly string Intent2Property = "intent2";
public static readonly string IntentScore2Property = "intentScore2";
public static readonly string EntitiesProperty = "entities";
public static readonly string QuestionProperty = "question";
public static readonly string ActivityIdProperty = "activityId";
public static readonly string SentimentLabelProperty = "sentimentLabel";
public static readonly string SentimentScoreProperty = "sentimentScore";
public static readonly string FromIdProperty = "fromId";
}
}

Просмотреть файл

@ -0,0 +1,194 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace Microsoft.Bot.Builder.AI.Luis
{
// Utility functions used to extract and transform data from Luis SDK
internal static class LuisUtil
{
internal const string MetadataKey = "$instance";
internal const string GeoV2 = "builtin.geographyV2.";
internal static readonly HashSet<string> _dateSubtypes = new HashSet<string> { "date", "daterange", "datetime", "datetimerange", "duration", "set", "time", "timerange" };
internal static string NormalizedIntent(string intent) => intent.Replace('.', '_').Replace(' ', '_');
internal static IDictionary<string, IntentScore> GetIntents(JObject luisResult)
{
var result = new Dictionary<string, IntentScore>();
var intents = (JObject)luisResult["intents"];
if (intents != null)
{
foreach (var intent in intents)
{
result.Add(NormalizedIntent(intent.Key), new IntentScore { Score = intent.Value["score"] == null ? 0.0 : intent.Value["score"].Value<double>() });
}
}
return result;
}
// Remove role and ensure that dot and space are not a part of entity names since we want to do JSON paths.
internal static string NormalizeEntity(string entity)
{
// Type::Role -> Role
var type = entity.Split(':').Last();
return type.Replace('.', '_').Replace(' ', '_');
}
// TODO: This code should be removed once V3.1 returns a geography object.
// It exists to find the type field in $instance if present in order to create
// a geography object with type and value.
internal static void FindGeographyTypes(JToken source, Dictionary<string, string> geoTypes)
{
if (source != null)
{
if (source is JObject obj)
{
if (obj.TryGetValue("type", out var type) && type.Type == JTokenType.String && type.Value<string>().StartsWith(GeoV2))
{
var path = type.Path.Replace(MetadataKey + ".", string.Empty);
path = path.Substring(0, path.LastIndexOf('.'));
geoTypes.Add(path, type.Value<string>().Substring(GeoV2.Length));
}
else
{
foreach (var property in obj.Properties())
{
FindGeographyTypes(property.Value, geoTypes);
}
}
}
else if (source is JArray arr)
{
foreach (var elt in arr)
{
FindGeographyTypes(elt, geoTypes);
}
}
}
}
internal static JToken MapProperties(JToken source, bool inInstance, Dictionary<string, string> geoTypes)
{
var result = source;
if (source is JObject obj)
{
var nobj = new JObject();
// Fix datetime by reverting to simple timex
if (!inInstance && obj.TryGetValue("type", out var type) && type.Type == JTokenType.String && _dateSubtypes.Contains(type.Value<string>()))
{
var timexs = obj["values"];
var arr = new JArray();
if (timexs != null)
{
var unique = new HashSet<string>();
foreach (var elt in timexs)
{
unique.Add(elt["timex"]?.Value<string>());
}
foreach (var timex in unique)
{
arr.Add(timex);
}
nobj["timex"] = arr;
}
nobj["type"] = type;
}
else
{
// Map or remove properties
foreach (var property in obj.Properties())
{
var name = NormalizeEntity(property.Name);
var isObj = property.Value.Type == JTokenType.Object;
var isArr = property.Value.Type == JTokenType.Array;
var isStr = property.Value.Type == JTokenType.String;
var isInt = property.Value.Type == JTokenType.Integer;
var val = MapProperties(property.Value, inInstance || property.Name == MetadataKey, geoTypes);
if (name == "datetime" && isArr)
{
nobj.Add("datetimeV1", val);
}
else if (name == "datetimeV2" && isArr)
{
nobj.Add("datetime", val);
}
else if (inInstance)
{
// Correct $instance issues
if (name == "length" && isInt)
{
nobj.Add("endIndex", property.Value.Value<int>() + property.Parent["startIndex"].Value<int>());
}
else if (!((isInt && name == "modelTypeId") ||
(isStr && name == "role")))
{
nobj.Add(name, val);
}
}
else
{
// Correct non-$instance values
if (name == "unit" && isStr)
{
nobj.Add("units", val);
}
else
{
nobj.Add(name, val);
}
}
}
}
result = nobj;
}
else if (source is JArray arr)
{
var narr = new JArray();
foreach (var elt in arr)
{
if (!inInstance && geoTypes.TryGetValue(elt.Path, out var geoType))
{
narr.Add(new JObject(new JProperty("location", elt.Value<string>()), new JProperty("type", geoType)));
}
else
{
narr.Add(MapProperties(elt, inInstance, geoTypes));
}
}
result = narr;
}
return result;
}
internal static JObject ExtractEntitiesAndMetadata(JObject prediction)
{
var entities = (JObject)JObject.FromObject(prediction["entities"]);
var geoTypes = new Dictionary<string, string>();
FindGeographyTypes(entities, geoTypes);
return (JObject)MapProperties(entities, false, geoTypes);
}
internal static void AddProperties(JObject luis, RecognizerResult result)
{
var sentiment = luis["sentiment"];
if (luis["sentiment"] != null)
{
result.Properties.Add("sentiment", new JObject(
new JProperty("label", sentiment["label"]),
new JProperty("score", sentiment["score"])));
}
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>4.0.0-local</Version>
<Description>LUIS V3 endpoint Middleware and Recognizers for the Microsoft Bot Builder SDK</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bot.Builder" Version="4.5.1" />
<PackageReference Include="Microsoft.Bot.Builder.ApplicationInsights" Version="4.5.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Strongly typed LUIS builtin_age.
/// </summary>
public class Age : NumberWithUnits
{
/// <summary>
/// Initializes a new instance of the <see cref="Age"/> class.
/// </summary>
/// <param name="age">Age.</param>
/// <param name="units">Units for age.</param>
public Age(double age, string units)
: base(age, units)
{
}
/// <inheritdoc/>
public override string ToString() => $"Age({Number} {Units})";
}
}

Просмотреть файл

@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Represents the built-in LUIS date-time type.
/// </summary>
/// <remarks>
/// LUIS recognizes time expressions like "next monday" and converts those to a type and set of timex expressions.
/// More information on timex can be found here: http://www.timeml.org/publications/timeMLdocs/timeml_1.2.1.html#timex3.
/// More information on the library which does the recognition can be found here: https://github.com/Microsoft/Recognizers-Text.
/// </remarks>
public class DateTimeSpec
{
/// <summary>
/// Initializes a new instance of the <see cref="DateTimeSpec"/> class.
/// </summary>
/// <param name="type">The type of TIMEX expression.</param>
/// <param name="expressions">The TIMEX expression.</param>
/// <exception cref="ArgumentNullException"><paramref name="type"/> is null or contains only white space,
/// or <paramref name="expressions"/> is null.</exception>
public DateTimeSpec(string type, IEnumerable<string> expressions)
{
if (string.IsNullOrWhiteSpace(type))
{
throw new ArgumentNullException(nameof(type));
}
if (expressions == null)
{
throw new ArgumentNullException(nameof(expressions));
}
Type = type;
Expressions = expressions.ToList();
}
/// <summary>
/// Gets type of expression.
/// </summary>
/// <remarks>Example types include:
/// <list type="*">
/// <item>time -- simple time expression like "3pm".</item>
/// <item>date -- simple date like "july 3rd".</item>
/// <item>datetime -- combination of date and time like "march 23 2pm".</item>
/// <item>timerange -- a range of time like "2pm to 4pm".</item>
/// <item>daterange -- a range of dates like "march 23rd to 24th".</item>
/// <item>datetimerange -- a range of dates and times like "july 3rd 2pm to 5th 4pm".</item>
/// <item>set -- a recurrence like "every monday".</item>
/// </list>
/// </remarks>
/// <value>
/// The type of expression.
/// </value>
[JsonProperty("type")]
public string Type { get; }
/// <summary>
/// Gets Timex expressions.
/// </summary>
/// <value>
/// Timex expressions.
/// </value>
[JsonProperty("timex")]
public IReadOnlyList<string> Expressions { get; }
/// <inheritdoc/>
public override string ToString() => $"DateTimeSpec({Type}, [{string.Join(", ", Expressions)}]";
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Strongly typed LUIS builtin_dimension.
/// </summary>
public class Dimension : NumberWithUnits
{
/// <summary>
/// Initializes a new instance of the <see cref="Dimension"/> class.
/// </summary>
/// <param name="number">Number.</param>
/// <param name="units">Units for number.</param>
public Dimension(double number, string units)
: base(number, units)
{
}
/// <inheritdoc/>
public override string ToString() => $"Dimension({Number} {Units})";
}
}

Просмотреть файл

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Strongly typed LUIS builtin GeographyV2.
/// </summary>
public class GeographyV2
{
/// <summary>
/// Initializes a new instance of the <see cref="GeographyV2"/> class.
/// </summary>
/// <param name="type">Type of geographic location from <see cref="Types"/>.</param>
/// <param name="location">Geographic location.</param>
public GeographyV2(string type, string location)
{
Type = type;
Location = location;
}
/// <summary>
/// Gets or sets type of geographic location.
/// </summary>
/// <value>
/// Type of geographic location from <see cref="Types"/>.
/// </value>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// Gets or sets geographic location.
/// </summary>
/// <value>
/// Geographic location.
/// </value>
[JsonProperty("location")]
public string Location { get; set; }
/// <inheritdoc/>
public override string ToString() => $"GeographyV2({Type}, {Location})";
/// <summary>
/// Different types of geographic locations.
/// </summary>
public static class Types
{
public const string POI = "poi";
public const string City = "city";
public const string CountryRegion = "countryRegion";
public const string Continent = "continent";
public const string State = "state";
}
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Strongly typed information corresponding to LUIS $instance value.
/// </summary>
public class InstanceData
{
/// <summary>
/// Gets or sets 0-based index in the analyzed text for where entity starts.
/// </summary>
/// <value>
/// 0-based index in the analyzed text for where entity starts.
/// </value>
[JsonProperty("startIndex")]
public int StartIndex { get; set; }
/// <summary>
/// Gets or sets 0-based index of the first character beyond the recognized entity.
/// </summary>
/// <value>
/// 0-based index of the first character beyond the recognized entity.
/// </value>
[JsonProperty("endIndex")]
public int EndIndex { get; set; }
/// <summary>
/// Gets or sets word broken and normalized text for the entity.
/// </summary>
/// <value>
/// Word broken and normalized text for the entity.
/// </value>
[JsonProperty("text")]
public string Text { get; set; }
/// <summary>
/// Gets or sets optional confidence in the recognition.
/// </summary>
/// <value>
/// Optional confidence in the recognition.
/// </value>
[JsonProperty("score")]
public double? Score { get; set; }
/// <summary>
/// Gets or sets optional type for the entity.
/// </summary>
/// <value>
/// Optional entity type.
/// </value>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// Gets or sets optional subtype for the entity.
/// </summary>
/// <value>
/// Optional entity subtype.
/// </value>
[JsonProperty("subtype")]
public string Subtype { get; set; }
/// <summary>
/// Gets or sets any extra properties.
/// </summary>
/// <value>
/// Any extra properties.
/// </value>
[JsonExtensionData(ReadData = true, WriteData = true)]
public IDictionary<string, object> Properties { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Strongly typed LUIS builtin_money.
/// </summary>
public class Money : NumberWithUnits
{
/// <summary>
/// Initializes a new instance of the <see cref="Money"/> class.
/// </summary>
/// <param name="money">Money amount.</param>
/// <param name="units">Currency units.</param>
public Money(double money, string units)
: base(money, units)
{
}
/// <inheritdoc/>
public override string ToString() => $"Currency({Number} {Units})";
}
}

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Strongly typed class for LUIS number and units entity recognition.
/// </summary>
/// <remarks>
/// Specific subtypes of this class are generated to match the builtin age, currency, dimension and temperature entities.
/// </remarks>
public class NumberWithUnits
{
/// <summary>
/// Initializes a new instance of the <see cref="NumberWithUnits"/> class.
/// </summary>
/// <param name="number">Number.</param>
/// <param name="units">Units for number.</param>
public NumberWithUnits(double? number, string units)
{
Number = number;
Units = units;
}
/// <summary>
/// Gets or sets recognized number, or null if unit only.
/// </summary>
/// <value>
/// Recognized number, or null if unit only.
/// </value>
[JsonProperty("number")]
public double? Number { get; set; }
/// <summary>
/// Gets or sets normalized recognized unit.
/// </summary>
/// <value>
/// Normalized recognized unit.
/// </value>
[JsonProperty("units")]
public string Units { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Strongly typed LUIS builtin OrdinalV2.
/// </summary>
public class OrdinalV2
{
/// <summary>
/// Initializes a new instance of the <see cref="OrdinalV2"/> class.
/// </summary>
/// <param name="offset">Offset from <see cref="RelativeTo"/>.</param>
/// <param name="relativeTo">Position that anchors offset.</param>
public OrdinalV2(string relativeTo, long offset)
{
RelativeTo = relativeTo;
Offset = offset;
}
/// <summary>
/// Gets or sets the anchor for the offset.
/// </summary>
/// <value>
/// The base position in a sequence one of <see cref="Anchor"/>.
/// </value>
[JsonProperty("relativeTo")]
public string RelativeTo { get; set; }
/// <summary>
/// Gets or sets the offset in the sequence with respect to <see cref="RelativeTo"/>.
/// </summary>
/// <value>
/// Offset in sequence relative to <see cref="RelativeTo"/>.
/// </value>
[JsonProperty("offset")]
public long Offset { get; set; }
/// <inheritdoc/>
public override string ToString() => $"OrdinalV2({RelativeTo}, {Offset})";
/// <summary>
/// Possible anchors for offsets.
/// </summary>
public static class Anchor
{
public const string Current = "current";
public const string End = "end";
public const string Start = "start";
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.Luis
{
/// <summary>
/// Strongly typed LUIS builtin_temperature.
/// </summary>
public class Temperature : NumberWithUnits
{
/// <summary>
/// Initializes a new instance of the <see cref="Temperature"/> class.
/// </summary>
/// <param name="temperature">Temperature.</param>
/// <param name="units">Units.</param>
public Temperature(double temperature, string units)
: base(temperature, units)
{
}
/// <inheritdoc/>
public override string ToString() => $"Temperature({Number} {Units})";
}
}

Просмотреть файл

@ -11,29 +11,23 @@ namespace Microsoft.Bot.Builder.AI.QnA
public class FeedbackRecord
{
/// <summary>
/// Gets or sets user id.
/// Gets or sets the user id.
/// </summary>
/// <value>
/// User id.
/// </value>
/// <value>the user id.</value>
[JsonProperty("userId")]
public string UserId { get; set; }
/// <summary>
/// Gets or sets user question.
/// Gets or sets the user question.
/// </summary>
/// <value>
/// User question.
/// </value>
/// <value>the user question.</value>
[JsonProperty("userQuestion")]
public string UserQuestion { get; set; }
/// <summary>
/// Gets or sets qnA Id.
/// Gets or sets the QnA Id.
/// </summary>
/// <value>
/// QnA Id.
/// </value>
/// <value>the qnaMaker id.</value>
[JsonProperty("qnaId")]
public int QnaId { get; set; }
}

Просмотреть файл

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.QnA
{
/// <summary>
/// Prompt Object.
/// </summary>
public class Prompt
{
private const int DefaultDisplayOrder = 0;
/// <summary>
/// Gets or sets displayOrder - index of the prompt - used in ordering of the prompts.
/// </summary>
/// <value>Display order.</value>
[JsonProperty("displayOrder")]
public int DisplayOrder { get; set; } = DefaultDisplayOrder;
/// <summary>
/// Gets or sets qna id corresponding to the prompt - if QnaId is present, QnADTO object is ignored.
/// </summary>
/// <value>QnA Id.</value>
[JsonProperty("qnaId")]
public int QnaId { get; set; }
/// <summary>
/// Gets or sets displayText - Text displayed to represent a follow up question prompt.
/// </summary>
/// <value>Display test.</value>
[JsonProperty("displayText")]
public string DisplayText { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the QnADTO returned from the API.
/// </summary>
/// <value>
/// The QnA DTO.
/// </value>
[JsonProperty("qna")]
public object Qna { get; set; }
}
}

Просмотреть файл

@ -75,5 +75,23 @@ namespace Microsoft.Bot.Builder.AI.QnA
/// </value>
[JsonProperty("metadataBoost")]
public Metadata[] MetadataBoost { get; set; }
/// <summary>
/// Gets or sets context for multi-turn responses.
/// </summary>
/// <value>
/// The context from which the QnA was extracted.
/// </value>
[JsonProperty("context")]
public QnARequestContext Context { get; set; }
/// <summary>
/// Gets or sets QnA Id of the current question asked.
/// </summary>
/// <value>
/// Id of the current question asked.
/// </value>
[JsonProperty("qnaId")]
public int QnAId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.QnA
{
/// <summary>
/// The context associated with QnA. Used to mark if the current prompt is relevant with a previous question or not.
/// </summary>
public class QnARequestContext
{
/// <summary>
/// Gets or sets the previous QnA Id that was returned.
/// </summary>
/// <value>
/// The previous QnA Id.
/// </value>
[JsonProperty(PropertyName = "previousQnAId")]
public int PreviousQnAId { get; set; }
/// <summary>
/// Gets or sets the previous user query/question.
/// </summary>
/// <value>
/// The previous user query.
/// </value>
[JsonProperty(PropertyName = "previousUserQuery")]
public string PreviousUserQuery { get; set; } = string.Empty;
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.AI.QnA
{
/// <summary>
/// The context associated with QnA. Used to mark if the qna response has related prompts to display.
/// </summary>
public class QnAResponseContext
{
/// <summary>
/// Gets or sets the prompts collection of related prompts.
/// </summary>
/// <value>
/// The QnA prompts array.
/// </value>
[JsonProperty(PropertyName = "prompts")]
public Prompt[] Prompts { get; set; }
}
}

Просмотреть файл

@ -67,5 +67,14 @@ namespace Microsoft.Bot.Builder.AI.QnA
/// </value>
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
/// <summary>
/// Gets or sets context for multi-turn responses.
/// </summary>
/// <value>
/// The context from which the QnA was extracted.
/// </value>
[JsonProperty(PropertyName = "context")]
public QnAResponseContext Context { get; set; }
}
}

Просмотреть файл

@ -47,7 +47,22 @@ namespace Microsoft.Bot.Builder.AI.QnA
[JsonProperty("top")]
public int Top { get; set; }
[JsonProperty("strictFilters")]
/// <summary>
/// Gets or sets context of the previous turn.
/// </summary>
/// <value>
/// The context of previous turn.
/// </value>
public QnARequestContext Context { get; set; }
/// <summary>
/// Gets or sets QnA Id of the current question asked (if avaliable).
/// </summary>
/// <value>
/// Id of the current question asked.
/// </value>
public int QnAId { get; set; }
public Metadata[] StrictFilters { get; set; }
[JsonProperty("metadataBoost")]

Просмотреть файл

@ -39,9 +39,7 @@ namespace Microsoft.Bot.Builder.AI.QnA
/// <summary>
/// Gets or sets qnA Maker options.
/// </summary>
/// <value>
/// QnA Maker options.
/// </value>
/// <value>The options for QnAMaker.</value>
public QnAMakerOptions Options { get; set; }
/// <summary>
@ -162,6 +160,9 @@ namespace Microsoft.Bot.Builder.AI.QnA
{
hydratedOptions.MetadataBoost = queryOptions.MetadataBoost;
}
hydratedOptions.Context = queryOptions.Context;
hydratedOptions.QnAId = queryOptions.QnAId;
}
return hydratedOptions;
@ -178,6 +179,8 @@ namespace Microsoft.Bot.Builder.AI.QnA
strictFilters = options.StrictFilters,
metadataBoost = options.MetadataBoost,
scoreThreshold = options.ScoreThreshold,
context = options.Context,
qnaId = options.QnAId,
}, Formatting.None);
var httpRequestHelper = new HttpRequestUtils(httpClient);
@ -199,6 +202,8 @@ namespace Microsoft.Bot.Builder.AI.QnA
Top = options.Top,
StrictFilters = options.StrictFilters,
MetadataBoost = options.MetadataBoost,
Context = options.Context,
QnAId = options.QnAId,
};
var traceActivity = Activity.CreateTraceActivity(QnAMaker.QnAMakerName, QnAMaker.QnAMakerTraceType, traceInfo, QnAMaker.QnAMakerTraceLabel);
await turnContext.SendActivityAsync(traceActivity).ConfigureAwait(false);

Просмотреть файл

@ -1,9 +1,9 @@
using System.Collections.Generic;
using Microsoft.Bot.Builder.AI.Luis;
using Microsoft.Bot.Builder.Dialogs.Adaptive.Actions;
using Microsoft.Bot.Builder.Dialogs.Adaptive.TriggerHandlers;
using Microsoft.Bot.Builder.Dialogs.Adaptive.Input;
using Microsoft.Bot.Builder.Dialogs.Adaptive.Recognizers;
using Microsoft.Bot.Builder.Dialogs.Adaptive.TriggerHandlers;
using Microsoft.Bot.Builder.Dialogs.Debugging;
using Microsoft.Bot.Builder.Dialogs.Declarative;
using Microsoft.Bot.Builder.Dialogs.Declarative.Converters;

Просмотреть файл

@ -8,8 +8,8 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs.Adaptive.TriggerHandlers;
using Microsoft.Bot.Builder.Dialogs.Adaptive.Selectors;
using Microsoft.Bot.Builder.Dialogs.Adaptive.TriggerHandlers;
using Microsoft.Bot.Builder.Dialogs.Debugging;
using Microsoft.Bot.Builder.LanguageGeneration;
using Microsoft.Bot.Schema;
@ -83,6 +83,11 @@ namespace Microsoft.Bot.Builder.Dialogs.Adaptive
/// </value>
public string DefaultResultProperty { get; set; } = "dialog.result";
/// <summary>
/// Gets the dialogs which make up the AdaptiveDialog
/// </summary>
public DialogSet Dialogs => this._dialogs;
public override IBotTelemetryClient TelemetryClient
{
get

Просмотреть файл

@ -60,11 +60,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Recognizers.Text" Version="1.1.3" />
<PackageReference Include="Microsoft.Recognizers.Text.Choice" Version="1.1.3" />
<PackageReference Include="Microsoft.Recognizers.Text.DateTime" Version="1.1.3" />
<PackageReference Include="Microsoft.Recognizers.Text.Number" Version="1.1.3" />
<PackageReference Include="Microsoft.Recognizers.Text.Sequence" Version="1.1.3" />
<PackageReference Include="Microsoft.Recognizers.Text" Version="1.2.6.1" />
<PackageReference Include="Microsoft.Recognizers.Text.Choice" Version="1.2.6.1" />
<PackageReference Include="Microsoft.Recognizers.Text.DateTime" Version="1.2.6.1" />
<PackageReference Include="Microsoft.Recognizers.Text.Number" Version="1.2.6.1" />
<PackageReference Include="Microsoft.Recognizers.Text.Sequence" Version="1.2.6.1" />
</ItemGroup>
<ProjectExtensions>

Просмотреть файл

@ -106,7 +106,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Choices
private static List<ModelResult<FoundChoice>> RecognizeOrdinal(string utterance, string culture)
{
var model = new NumberRecognizer(culture).GetOrdinalModel(culture);
var model = new NumberRecognizer(culture, NumberOptions.SuppressExtendedTypes).GetOrdinalModel(culture);
var result = model.Parse(utterance);
return result.Select(r =>
new ModelResult<FoundChoice>
@ -123,7 +123,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Choices
private static List<ModelResult<FoundChoice>> RecognizeNumber(string utterance, string culture)
{
var model = new NumberRecognizer(culture).GetNumberModel(culture);
var model = new NumberRecognizer(culture, NumberOptions.SuppressExtendedTypes).GetNumberModel(culture);
var result = model.Parse(utterance);
return result.Select(r =>
new ModelResult<FoundChoice>

Просмотреть файл

@ -9,12 +9,21 @@ using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Dialogs
{
/// <summary>
/// A <see cref="Dialog"/> that is composed of other dialogs.
/// </summary>
/// <remarks>A component dialog has an inner <see cref="DialogSet"/> and <see cref="DialogContext"/>,
/// which provides an inner dialog stack that is hidden from the parent dialog.</remarks>
public class ComponentDialog : DialogContainer
{
public const string PersistedDialogState = "dialogs";
private bool initialized = false;
/// <summary>
/// Initializes a new instance of the <see cref="ComponentDialog"/> class.
/// </summary>
/// <param name="dialogId">The ID to assign to the new dialog within the parent dialog set.</param>
public ComponentDialog(string dialogId = null)
: base(dialogId)
{
@ -23,10 +32,12 @@ namespace Microsoft.Bot.Builder.Dialogs
public string InitialDialogId { get; set; }
/// <summary>
/// Gets or sets or set the <see cref="IBotTelemetryClient"/> to use.
/// When setting this property, all the contained dialogs TelemetryClient properties are also set.
/// Gets or sets the <see cref="IBotTelemetryClient"/> to use for logging.
/// When setting this property, all of the contained dialogs' <see cref="Dialog.TelemetryClient"/>
/// properties are also set.
/// </summary>
/// <value>The <see cref="IBotTelemetryClient"/> to use when logging.</value>
/// <seealso cref="DialogSet.TelemetryClient"/>
public new IBotTelemetryClient TelemetryClient
{
get
@ -41,6 +52,18 @@ namespace Microsoft.Bot.Builder.Dialogs
}
}
/// <summary>
/// Called when the dialog is started and pushed onto the parent's dialog stack.
/// </summary>
/// <param name="outerDc">The parent <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="options">Optional, initial information to pass to the dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.</remarks>
/// <seealso cref="OnBeginDialogAsync(DialogContext, object, CancellationToken)"/>
/// <seealso cref="DialogContext.BeginDialogAsync(string, object, CancellationToken)"/>
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (options is CancellationToken)
@ -61,12 +84,6 @@ namespace Microsoft.Bot.Builder.Dialogs
// Check for end of inner dialog
if (turnResult.Status != DialogTurnStatus.Waiting)
{
if (turnResult.Status == DialogTurnStatus.Cancelled)
{
await EndComponentAsync(outerDc, turnResult.Result, cancellationToken).ConfigureAwait(false);
return new DialogTurnResult(DialogTurnStatus.Cancelled, turnResult.Result);
}
// Return result to calling dialog
return await EndComponentAsync(outerDc, turnResult.Result, cancellationToken).ConfigureAwait(false);
}
@ -75,6 +92,26 @@ namespace Microsoft.Bot.Builder.Dialogs
return Dialog.EndOfTurn;
}
/// <summary>
/// Called when the dialog is _continued_, where it is the active dialog and the
/// user replies with a new activity.
/// </summary>
/// <param name="outerDc">The parent <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog. The result may also contain a
/// return value.
///
/// If this method is *not* overridden, the component dialog calls the
/// <see cref="DialogContext.ContinueDialogAsync(CancellationToken)"/> method on its inner
/// dialog context. If the inner dialog stack is empty, the component dialog ends, and if
/// a <see cref="DialogTurnResult.Result"/> is available, the component dialog uses that as
/// its return value.
/// </remarks>
/// <seealso cref="OnContinueDialogAsync(DialogContext, CancellationToken)"/>
/// <seealso cref="DialogContext.ContinueDialogAsync(CancellationToken)"/>
public override async Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default(CancellationToken))
{
// Continue execution of inner dialog
@ -100,6 +137,31 @@ namespace Microsoft.Bot.Builder.Dialogs
}
}
/// <summary>
/// Called when a child dialog on the parent's dialog stack completed this turn, returning
/// control to this dialog component.
/// </summary>
/// <param name="outerDc">The <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="reason">Reason why the dialog resumed.</param>
/// <param name="result">Optional, value returned from the dialog that was called. The type
/// of the value returned is dependent on the child dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result indicates whether this dialog is still
/// active after this dialog turn has been processed.
///
/// Generally, the child dialog was started with a call to
/// <see cref="BeginDialogAsync(DialogContext, object, CancellationToken)"/> in the parent's
/// context. However, if the
/// <see cref="DialogContext.ReplaceDialogAsync(string, object, CancellationToken)"/> method
/// is called, the logical child dialog may be different than the original.
///
/// If this method is *not* overridden, the dialog automatically calls its
/// <see cref="RepromptDialogAsync(ITurnContext, DialogInstance, CancellationToken)"/> when
/// the user replies.
/// </remarks>
/// <seealso cref="RepromptDialogAsync(ITurnContext, DialogInstance, CancellationToken)"/>
public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (result is CancellationToken)
@ -118,6 +180,16 @@ namespace Microsoft.Bot.Builder.Dialogs
return Dialog.EndOfTurn;
}
/// <summary>
/// Called when the dialog should re-prompt the user for input.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="instance">State information for this dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <seealso cref="OnRepromptDialogAsync(ITurnContext, DialogInstance, CancellationToken)"/>
/// <seealso cref="DialogContext.RepromptDialogAsync(CancellationToken)"/>
public override async Task RepromptDialogAsync(ITurnContext turnContext, DialogInstance instance, CancellationToken cancellationToken = default(CancellationToken))
{
// Delegate to inner dialog.
@ -128,6 +200,20 @@ namespace Microsoft.Bot.Builder.Dialogs
await OnRepromptDialogAsync(turnContext, instance, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Called when the dialog is ending.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="instance">State information associated with the instance of this component
/// dialog on its parent's dialog stack.</param>
/// <param name="reason">Reason why the dialog ended.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>When this method is called from the parent dialog's context, the component dialog
/// cancels all of the dialogs on its inner dialog stack before ending.</remarks>
/// <seealso cref="OnEndDialogAsync(ITurnContext, DialogInstance, DialogReason, CancellationToken)"/>
/// <seealso cref="DialogContext.EndDialogAsync(object, CancellationToken)"/>
public override async Task EndDialogAsync(ITurnContext turnContext, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken = default(CancellationToken))
{
// Forward cancel to inner dialogs
@ -141,14 +227,15 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Adds a dialog to the component dialog.
/// Adds a new <see cref="Dialog"/> to the component dialog and returns the updated component.
/// </summary>
/// <param name="dialog">The dialog to add.</param>
/// <returns>The updated <see cref="ComponentDialog"/>.</returns>
/// <remarks>Adding a new dialog will inherit the <see cref="IBotTelemetryClient"/> of the ComponentDialog.</remarks>
public override Dialog AddDialog(Dialog dialog)
/// <returns>The <see cref="ComponentDialog"/> after the operation is complete.</returns>
/// <remarks>The added dialog's <see cref="Dialog.TelemetryClient"/> is set to the
/// <see cref="TelemetryClient"/> of the component dialog.</remarks>
public virtual ComponentDialog AddDialog(Dialog dialog)
{
base.AddDialog(dialog);
this._dialogs.Add(dialog);
if (this.InitialDialogId == null)
{
@ -158,16 +245,6 @@ namespace Microsoft.Bot.Builder.Dialogs
return this;
}
/// <summary>
/// Finds a dialog by ID (ONLY in this ComponentDialog, use DialogContext.FindDialog to get scoped dialogs).
/// </summary>
/// <param name="dialogId">The ID of the dialog to find.</param>
/// <returns>The dialog; or <c>null</c> if there is not a match for the ID.</returns>
public new Dialog FindDialog(string dialogId)
{
return _dialogs.Find(dialogId);
}
public override DialogContext CreateChildContext(DialogContext dc)
{
var childDc = this.CreateInnerDc(dc.Context, dc.ActiveDialog);
@ -194,26 +271,108 @@ namespace Microsoft.Bot.Builder.Dialogs
return Task.CompletedTask;
}
/// <summary>
/// Called when the dialog is started and pushed onto the parent's dialog stack.
/// </summary>
/// <param name="innerDc">The inner <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="options">Optional, initial information to pass to the dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.
///
/// By default, this calls the
/// <see cref="Dialog.BeginDialogAsync(DialogContext, object, CancellationToken)"/> method
/// of the component dialog's initial dialog, as defined by <see cref="InitialDialogId"/>.
///
/// Override this method in a derived class to implement interrupt logic.</remarks>
/// <seealso cref="BeginDialogAsync(DialogContext, object, CancellationToken)"/>
protected virtual Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken))
{
return innerDc.BeginDialogAsync(InitialDialogId, options, cancellationToken);
}
/// <summary>
/// Called when the dialog is _continued_, where it is the active dialog and the
/// user replies with a new activity.
/// </summary>
/// <param name="innerDc">The inner <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog. The result may also contain a
/// return value.
///
/// By default, this calls the currently active inner dialog's
/// <see cref="Dialog.ContinueDialogAsync(DialogContext, CancellationToken)"/> method.
///
/// Override this method in a derived class to implement interrupt logic.</remarks>
/// <seealso cref=" ContinueDialogAsync(DialogContext, CancellationToken)"/>
protected virtual Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
{
return innerDc.ContinueDialogAsync(cancellationToken);
}
/// <summary>
/// Called when the dialog is ending.
/// </summary>
/// <param name="context">The context object for this turn.</param>
/// <param name="instance">State information associated with the inner dialog stack of this
/// component dialog.</param>
/// <param name="reason">Reason why the dialog ended.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>Override this method in a derived class to implement any additional logic that
/// should happen at the component level, after all inner dialogs have been canceled.</remarks>
/// <seealso cref="EndDialogAsync(ITurnContext, DialogInstance, DialogReason, CancellationToken)"/>
protected virtual Task OnEndDialogAsync(ITurnContext context, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.CompletedTask;
}
/// <summary>
/// Called when the dialog should re-prompt the user for input.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="instance">State information associated with the inner dialog stack of this
/// component dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>Override this method in a derived class to implement any additional logic that
/// should happen at the component level, after the re-prompt operation completes for the inner
/// dialog.</remarks>
/// <seealso cref="RepromptDialogAsync(ITurnContext, DialogInstance, CancellationToken)"/>
protected virtual Task OnRepromptDialogAsync(ITurnContext turnContext, DialogInstance instance, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.CompletedTask;
}
/// <summary>
/// Ends the component dialog in its parent's context.
/// </summary>
/// <param name="outerDc">The parent <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="result">Optional, value to return from the dialog component to the parent context.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the task is successful, the result indicates that the dialog ended after the
/// turn was processed by the dialog.
///
/// In general, the parent context is the dialog or bot turn handler that started the dialog.
/// If the parent is a dialog, the stack calls the parent's
/// <see cref="Dialog.ResumeDialogAsync(DialogContext, DialogReason, object, CancellationToken)"/>
/// method to return a result to the parent dialog. If the parent dialog does not implement
/// `ResumeDialogAsync`, then the parent will end, too, and the result is passed to the next
/// parent context, if one exists.
///
/// The returned <see cref="DialogTurnResult"/> contains the return value in its
/// <see cref="DialogTurnResult.Result"/> property.</remarks>
/// <seealso cref="BeginDialogAsync(DialogContext, object, CancellationToken)"/>
/// <seealso cref="ContinueDialogAsync(DialogContext, CancellationToken)"/>
protected virtual Task<DialogTurnResult> EndComponentAsync(DialogContext outerDc, object result, CancellationToken cancellationToken)
{
return outerDc.EndDialogAsync(result, cancellationToken);

Просмотреть файл

@ -16,6 +16,10 @@ namespace Microsoft.Bot.Builder.Dialogs
[DebuggerDisplay("{Id}")]
public abstract class Dialog
{
/// <summary>
/// A <see cref="DialogTurnResult"/> that indicates that the current dialog is still
/// active and waiting for input from the user next turn.
/// </summary>
public static readonly DialogTurnResult EndOfTurn = new DialogTurnResult(DialogTurnStatus.Waiting);
private IBotTelemetryClient _telemetryClient;
@ -62,9 +66,10 @@ namespace Microsoft.Bot.Builder.Dialogs
public List<string> Tags { get; private set; } = new List<string>();
/// <summary>
/// Gets or sets the telemetry client for logging events.
/// Gets or sets the <see cref="IBotTelemetryClient"/> to use for logging.
/// </summary>
/// <value>The Telemetry Client logger.</value>
/// <value>The <see cref="IBotTelemetryClient"/> to use for logging.</value>
/// <seealso cref="DialogSet.TelemetryClient"/>
public virtual IBotTelemetryClient TelemetryClient
{
get
@ -79,29 +84,33 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Method called when a new dialog has been pushed onto the stack and is being activated.
/// Called when the dialog is started and pushed onto the dialog stack.
/// </summary>
/// <param name="dc">The dialog context for the current turn of conversation.</param>
/// <param name="options">(Optional) additional argument(s) to pass to the dialog being started.</param>
/// <param name="dc">The <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="options">Optional, initial information to pass to the dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.</remarks>
/// <seealso cref="DialogContext.BeginDialogAsync(string, object, CancellationToken)"/>
public abstract Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Method called when an instance of the dialog is the "current" dialog and the
/// user replies with a new activity. The dialog will generally continue to receive the user's
/// replies until it calls either `EndDialogAsync()` or `BeginDialogAsync()`.
/// If this method is NOT implemented then the dialog will automatically be ended when the user replies.
/// Called when the dialog is _continued_, where it is the active dialog and the
/// user replies with a new activity.
/// </summary>
/// <param name="dc">The dialog context for the current turn of conversation.</param>
/// <param name="dc">The <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.</remarks>
/// active after the turn has been processed by the dialog. The result may also contain a
/// return value.
///
/// If this method is *not* overridden, the dialog automatically ends when the user replies.
/// </remarks>
/// <seealso cref="DialogContext.ContinueDialogAsync(CancellationToken)"/>
public virtual async Task<DialogTurnResult> ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
// By default just end the current dialog.
@ -109,20 +118,26 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Method called when an instance of the dialog is being returned to from another
/// dialog that was started by the current instance using `BeginDialogAsync()`.
/// If this method is NOT implemented then the dialog will be automatically ended with a call
/// to `EndDialogAsync()`. Any result passed from the called dialog will be passed
/// to the current dialog's parent.
/// Called when a child dialog completed this turn, returning control to this dialog.
/// </summary>
/// <param name="dc">The dialog context for the current turn of the conversation.</param>
/// <param name="reason">An enum indicating why the dialog resumed.</param>
/// <param name="result">(Optional) value returned from the dialog that was called. The type of the value returned is dependent on the dialog that was called.</param>
/// <param name="reason">Reason why the dialog resumed.</param>
/// <param name="result">Optional, value returned from the dialog that was called. The type
/// of the value returned is dependent on the child dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.</remarks>
/// <remarks>If the task is successful, the result indicates whether this dialog is still
/// active after this dialog turn has been processed.
///
/// Generally, the child dialog was started with a call to
/// <see cref="BeginDialogAsync(DialogContext, object, CancellationToken)"/>. However, if the
/// <see cref="DialogContext.ReplaceDialogAsync(string, object, CancellationToken)"/> method
/// is called, the logical child dialog may be different than the original.
///
/// If this method is *not* overridden, the dialog automatically ends when the user replies.
/// </remarks>
/// <seealso cref="DialogContext.EndDialogAsync(object, CancellationToken)"/>
public virtual async Task<DialogTurnResult> ResumeDialogAsync(DialogContext dc, DialogReason reason, object result = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (result is CancellationToken)
@ -135,19 +150,29 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Method called when the dialog has been requested to re-prompt the user for input.
/// Called when the dialog should re-prompt the user for input.
/// </summary>
/// <param name="turnContext">Context for the current turn of conversation with the user.</param>
/// <param name="instance">The instance of the dialog on the stack.</param>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="instance">State information for this dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <seealso cref="DialogContext.RepromptDialogAsync(CancellationToken)"/>
public virtual Task RepromptDialogAsync(ITurnContext turnContext, DialogInstance instance, CancellationToken cancellationToken = default(CancellationToken))
{
// No-op by default
return Task.CompletedTask;
}
/// <summary>
/// Called when the dialog is ending.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="instance">State information associated with the instance of this dialog on the dialog stack.</param>
/// <param name="reason">Reason why the dialog ended.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public virtual Task EndDialogAsync(ITurnContext turnContext, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken = default(CancellationToken))
{
// No-op by default

Просмотреть файл

@ -16,13 +16,7 @@ namespace Microsoft.Bot.Builder.Dialogs
public abstract DialogContext CreateChildContext(DialogContext dc);
public virtual Dialog AddDialog(Dialog dialog)
{
this._dialogs.Add(dialog);
return this;
}
public Dialog FindDialog(string dialogId)
public virtual Dialog FindDialog(string dialogId)
{
return this._dialogs.Find(dialogId);
}

Просмотреть файл

@ -12,6 +12,11 @@ using static Microsoft.Bot.Builder.Dialogs.Debugging.DebugSupport;
namespace Microsoft.Bot.Builder.Dialogs
{
/// <summary>
/// Provides context for the current state of the dialog stack.
/// </summary>
/// <remarks>The <see cref="Context"/> property contains the <see cref="ITurnContext"/>
/// for the current turn.</remarks>
[System.Diagnostics.DebuggerDisplay("{GetType().Name}[{ActiveDialog?.Id}]")]
public class DialogContext
{
@ -54,11 +59,17 @@ namespace Microsoft.Bot.Builder.Dialogs
/// <summary>
/// Gets the context for the current turn of conversation.
/// </summary>
/// <value>
/// The context for the current turn of conversation.
/// </value>
public ITurnContext Context { get; private set; }
/// <summary>
/// Gets the current dialog stack.
/// </summary>
/// <value>
/// The current dialog stack.
/// </value>
public List<DialogInstance> Stack { get; private set; }
/// <summary>
@ -67,7 +78,7 @@ namespace Microsoft.Bot.Builder.Dialogs
public DialogStateManager State { get; private set; }
/// <summary>
/// Gets or sets parent context.
/// Gets or sets the parent <see cref="DialogContext"/>, if any. Used when searching for the ID of a dialog to start.
/// </summary>
public DialogContext Parent { get; set; }
@ -114,12 +125,18 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Pushes a new dialog onto the dialog stack.
/// Starts a new dialog and pushes it onto the dialog stack.
/// </summary>
/// <param name="dialogId">ID of the dialog to start.</param>
/// <param name="options">(Optional) additional argument(s) to pass to the dialog being started.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <param name="options">Optional, information to pass to the dialog being started.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.</remarks>
/// <seealso cref="EndDialogAsync(object, CancellationToken)"/>
/// <seealso cref="PromptAsync(string, PromptOptions, CancellationToken)"/>
/// <seealso cref="Dialog.BeginDialogAsync(DialogContext, object, CancellationToken)"/>
public async Task<DialogTurnResult> BeginDialogAsync(string dialogId, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(dialogId))
@ -155,10 +172,15 @@ namespace Microsoft.Bot.Builder.Dialogs
/// Helper function to simplify formatting the options for calling a prompt dialog. This helper will
/// take a `PromptOptions` argument and then call[begin(context, dialogId, options)](#begin).
/// </summary>
/// <param name="dialogId">ID of the prompt to start.</param>
/// <param name="options">Contains a Prompt, potentially a RetryPrompt and if using ChoicePrompt, Choices.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <param name="dialogId">ID of the prompt dialog to start.</param>
/// <param name="options">Information to pass to the prompt dialog being started.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.</remarks>
/// <seealso cref="BeginDialogAsync(string, object, CancellationToken)"/>
/// <seealso cref="Prompt{T}.BeginDialogAsync(DialogContext, object, CancellationToken)"/>
public async Task<DialogTurnResult> PromptAsync(string dialogId, PromptOptions options, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(dialogId))
@ -175,12 +197,20 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Continues execution of the active dialog, if there is one, by passing the context object to
/// its `Dialog.ContinueDialogAsync()` method. You can check `context.responded` after the call completes
/// to determine if a dialog was run and a reply was sent to the user.
/// Continues execution of the active dialog, if there is one, by passing the current
/// <see cref="DialogContext"/> to the active dialog's
/// <see cref="Dialog.ContinueDialogAsync(DialogContext, CancellationToken)"/> method.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.
///
/// Check the <see cref="TurnContext.Responded"/> property after the call completes
/// to determine if the active dialog sent a reply message to the user.
/// </remarks>
/// <seealso cref="Dialog.ContinueDialogAsync(DialogContext, CancellationToken)"/>
public async Task<DialogTurnResult> ContinueDialogAsync(CancellationToken cancellationToken = default(CancellationToken))
{
// if we are continuing and haven't emitted the activityReceived event, emit it
@ -222,9 +252,25 @@ namespace Microsoft.Bot.Builder.Dialogs
/// automatically ended as well and the result passed to its parent. If there are no more
/// parent dialogs on the stack then processing of the turn will end.
/// </summary>
/// <param name="result">(Optional) result to pass to the parent dialogs.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <param name="result">Optional, result to pass to the parent context.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the task is successful, the result indicates that the dialog ended after the
/// turn was processed by the dialog.
///
/// In general, the parent context is the dialog or bot turn handler that started the dialog.
/// If the parent is a dialog, the stack calls the parent's
/// <see cref="Dialog.ResumeDialogAsync(DialogContext, DialogReason, object, CancellationToken)"/> method to
/// return a result to the parent dialog. If the parent dialog does not implement `ResumeDialogAsyn`,
/// then the parent will end, too, and the result passed to the next parent context.
///
/// The returned <see cref="DialogTurnResult"/> contains the return value in its
/// <see cref="DialogTurnResult.Result"/> property.</remarks>
/// <seealso cref="BeginDialogAsync(string, object, CancellationToken)"/>
/// <seealso cref="PromptAsync(string, PromptOptions, CancellationToken)"/>
/// <seealso cref="ReplaceDialogAsync(string, object, CancellationToken)"/>
/// <seealso cref="Dialog.EndDialogAsync(ITurnContext, DialogInstance, DialogReason, CancellationToken)"/>
public async Task<DialogTurnResult> EndDialogAsync(object result = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (result is CancellationToken token)
@ -256,7 +302,7 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Cancels all dialogs on the dialog stack.
/// Deletes any existing dialog stack thus cancelling all dialogs on the stack.
/// </summary>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
@ -281,8 +327,19 @@ namespace Microsoft.Bot.Builder.Dialogs
/// </summary>
/// <param name="eventName">The event.</param>
/// <param name="eventValue">The event value.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The dialog context.</returns>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the task is successful, the result indicates that dialogs were canceled after the
/// turn was processed by the dialog or that the stack was already empty.
///
/// In general, the parent context is the dialog or bot turn handler that started the dialog.
/// If the parent is a dialog, the stack calls the parent's
/// <see cref="Dialog.ResumeDialogAsync(DialogContext, DialogReason, object, CancellationToken)"/>
/// method to return a result to the parent dialog. If the parent dialog does not implement
/// `ResumeDialogAsync`, then the parent will end, too, and the result is passed to the next
/// parent context.</remarks>
/// <seealso cref="EndDialogAsync(object, CancellationToken)"/>
public async Task<DialogTurnResult> CancelAllDialogsAsync(string eventName, object eventValue = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (eventValue is CancellationToken)
@ -334,13 +391,18 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Ends the active dialog and starts a new dialog in its place. This is particularly useful
/// for creating loops or redirecting to another dialog.
/// Starts a new dialog and replaces on the stack the currently active dialog with the new one.
/// This is particularly useful for creating loops or redirecting to another dialog.
/// </summary>
/// <param name="dialogId">ID of the new dialog to start.</param>
/// <param name="options">(Optional) additional argument(s) to pass to the new dialog.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <param name="options">Optional, information to pass to the dialog being started.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the task is successful, the result indicates whether the dialog is still
/// active after the turn has been processed by the dialog.</remarks>
/// <seealso cref="BeginDialogAsync(string, object, CancellationToken)"/>
/// <seealso cref="EndDialogAsync(object, CancellationToken)"/>
public async Task<DialogTurnResult> ReplaceDialogAsync(string dialogId, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (options is CancellationToken)
@ -358,10 +420,14 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Calls reprompt on the currently active dialog, if there is one. Used with Prompts that have a reprompt behavior.
/// Calls the currently active dialog's
/// <see cref="Dialog.RepromptDialogAsync(ITurnContext, DialogInstance, CancellationToken)"/>
/// method. Used with dialogs that implement a re-prompt behavior.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <seealso cref="Dialog.RepromptDialogAsync(ITurnContext, DialogInstance, CancellationToken)"/>
public async Task RepromptDialogAsync(CancellationToken cancellationToken = default(CancellationToken))
{
// Emit 'RepromptDialog' event

Просмотреть файл

@ -9,16 +9,16 @@ using Newtonsoft.Json.Serialization;
namespace Microsoft.Bot.Builder.Dialogs
{
/// <summary>
/// Tracking information for a dialog on the stack.
/// Contains state information associated with a <see cref="Dialog"/> on a dialog stack.
/// </summary>
[DebuggerDisplay("{Id}")]
public class DialogInstance
{
/// <summary>
/// Gets or sets the ID of the dialog this instance is for.
/// Gets or sets the ID of the dialog.
/// </summary>
/// <value>
/// ID of the dialog this instance is for.
/// The ID of the dialog.
/// </value>
[JsonProperty("id")]
public string Id { get; set; }

Просмотреть файл

@ -3,36 +3,46 @@
namespace Microsoft.Bot.Builder.Dialogs
{
/// <summary>
/// Indicates in which a dialog-related method is being called.
/// </summary>
public enum DialogReason
{
/// <summary>
/// A dialog is being started through a call to `DialogContext.BeginAsync()`.
/// A dialog was started.
/// </summary>
/// <seealso cref="DialogContext.BeginDialogAsync(string, object, System.Threading.CancellationToken)"/>
/// <seealso cref="DialogContext.PromptAsync(string, PromptOptions, System.Threading.CancellationToken)"/>
BeginCalled,
/// <summary>
/// A dialog is being continued through a call to `DialogContext.ContinueDialogAsync()`.
/// A dialog was continued.
/// </summary>
/// <seealso cref="DialogContext.ContinueDialogAsync(System.Threading.CancellationToken)"/>
ContinueCalled,
/// <summary>
/// A dialog ended normally through a call to `DialogContext.EndDialogAsync()`.
/// A dialog was ended normally.
/// </summary>
/// <seealso cref="DialogContext.EndDialogAsync(object, System.Threading.CancellationToken)"/>
EndCalled,
/// <summary>
/// A dialog is ending because it's being replaced through a call to `DialogContext.ReplaceDialogAsync()`.
/// A dialog was ending because it was replaced.
/// </summary>
/// <seealso cref="DialogContext.ReplaceDialogAsync(string, object, System.Threading.CancellationToken)"/>
ReplaceCalled,
/// <summary>
/// A dialog was cancelled as part of a call to `DialogContext.CancelAllDialogsAsync()`.
/// A dialog was canceled.
/// </summary>
/// <seealso cref="DialogContext.CancelAllDialogsAsync(System.Threading.CancellationToken)"/>
CancelCalled,
/// <summary>
/// A step was advanced through a call to `WaterfallStepContext.NextAsync()`.
/// A preceding step of the dialog was skipped.
/// </summary>
/// <seealso cref="WaterfallStepContext.NextAsync(object, System.Threading.CancellationToken)"/>
NextCalled,
}
}

Просмотреть файл

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Dialogs
{
/// <summary>
/// A related set of dialogs that can all call each other.
/// A collection of <see cref="Dialog"/> objects that can all call each other.
/// </summary>
public class DialogSet
{
@ -18,6 +18,15 @@ namespace Microsoft.Bot.Builder.Dialogs
private IBotTelemetryClient _telemetryClient;
/// <summary>
/// Initializes a new instance of the <see cref="DialogSet"/> class.
/// </summary>
/// <param name="dialogState">The state property accessor with which to manage the stack for
/// this dialog set.</param>
/// <remarks>To start and control the dialogs in this dialog set, create a <see cref="DialogContext"/>
/// and use its methods to start, continue, or end dialogs. To create a dialog context,
/// call <see cref="CreateContextAsync(ITurnContext, CancellationToken)"/>.
/// </remarks>
public DialogSet(IStatePropertyAccessor<DialogState> dialogState)
{
_dialogState = dialogState ?? throw new ArgumentNullException(nameof(dialogState));
@ -31,10 +40,11 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Gets or sets the <see cref="IBotTelemetryClient"/> to use.
/// When setting this property, all of the contained dialogs' TelemetryClient properties are also set.
/// Gets or sets the <see cref="IBotTelemetryClient"/> to use for logging.
/// </summary>
/// <value>The <see cref="IBotTelemetryClient"/> to use when logging.</value>
/// <value>The <see cref="IBotTelemetryClient"/> to use for logging.</value>
/// <remarks>When this property is set, it sets the <see cref="Dialog.TelemetryClient"/> of each
/// dialog in the set to the new value.</remarks>
public IBotTelemetryClient TelemetryClient
{
get
@ -60,8 +70,9 @@ namespace Microsoft.Bot.Builder.Dialogs
/// of "duplicate2".
/// </summary>
/// <param name="dialog">The dialog to add.</param>
/// <returns>The DialogSet for fluent calls to Add().</returns>
/// <remarks>Adding a new dialog will inherit the <see cref="IBotTelemetryClient"/> of the DialogSet.</remarks>
/// <returns>The dialog set after the operation is complete.</returns>
/// <remarks>The added dialog's <see cref="Dialog.TelemetryClient"/> is set to the
/// <see cref="TelemetryClient"/> of the dialog set.</remarks>
public DialogSet Add(Dialog dialog)
{
if (dialog == null)
@ -104,12 +115,17 @@ namespace Microsoft.Bot.Builder.Dialogs
return this;
}
public Task<DialogContext> CreateContextAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
return CreateContextAsync(turnContext, null, null, cancellationToken);
}
public async Task<DialogContext> CreateContextAsync(ITurnContext turnContext, Dictionary<string, object> conversationState, Dictionary<string, object> userState, CancellationToken cancellationToken = default(CancellationToken))
/// <summary>
/// Creates a <see cref="DialogContext"/> which can be used to work with the dialogs in the
/// <see cref="DialogSet"/>.
/// </summary>
/// <param name="turnContext">Context for the current turn of conversation with the user.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>If the task is successful, the result contains the created <see cref="DialogContext"/>.
/// </remarks>
public async Task<DialogContext> CreateContextAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
BotAssert.ContextNotNull(turnContext);
@ -128,10 +144,10 @@ namespace Microsoft.Bot.Builder.Dialogs
}
/// <summary>
/// Finds a dialog that was previously added to the set using <see cref="Add(Dialog)"/>.
/// Searches the current <see cref="DialogSet"/> for a <see cref="Dialog"/> by its ID.
/// </summary>
/// <param name="dialogId">ID of the dialog/prompt to look up.</param>
/// <returns>The dialog if found, otherwise null.</returns>
/// <param name="dialogId">ID of the dialog to search for.</param>
/// <returns>The dialog if found; otherwise <c>null</c>.</returns>
public Dialog Find(string dialogId)
{
if (string.IsNullOrWhiteSpace(dialogId))

Просмотреть файл

@ -7,18 +7,37 @@ using Newtonsoft.Json.Serialization;
namespace Microsoft.Bot.Builder.Dialogs
{
/// <summary>
/// Contains state information for the dialog stack.
/// </summary>
public class DialogState
{
/// <summary>
/// Initializes a new instance of the <see cref="DialogState"/> class.
/// </summary>
/// <remarks>The new instance is created with an empty dialog stack.</remarks>
/// <seealso cref="DialogContext.Stack"/>
/// <seealso cref="DialogSet(IStatePropertyAccessor{DialogState})"/>
public DialogState()
: this(null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DialogState"/> class.
/// </summary>
/// <param name="stack">The state information to initialize the stack with.</param>
/// <remarks>The new instance has a dialog stack that is populated using the information
/// in <paramref name="stack"/>.</remarks>
public DialogState(List<DialogInstance> stack)
{
DialogStack = stack ?? new List<DialogInstance>();
}
/// <summary>
/// Gets the state information for a dialog stack.
/// </summary>
/// <value>State information for a dialog stack.</value>
[JsonProperty("dialogStack")]
public List<DialogInstance> DialogStack { get; set; } = new List<DialogInstance>();
}

Просмотреть файл

@ -4,9 +4,12 @@
namespace Microsoft.Bot.Builder.Dialogs
{
/// <summary>
/// Result returned to the caller of one of the various stack manipulation methods,
/// and used to return the result from a final call to `DialogContext.EndDialogAsync()` to the bot's logic.
/// Result returned to the caller of one of the various stack manipulation methods.
/// </summary>
/// <remarks>
/// Use <see cref="DialogContext.EndDialogAsync(object, System.Threading.CancellationToken)"/>
/// to end a <see cref="Dialog"/> and return a result to the calling context.
/// </remarks>
public class DialogTurnResult
{
public DialogTurnResult(DialogTurnStatus status, object result = null)

Просмотреть файл

@ -16,12 +16,14 @@ namespace Microsoft.Bot.Builder.Dialogs
Waiting,
/// <summary>
/// Indicates that the dialog completed successfully, the result is available, and the stack is empty.
/// Indicates that a dialog completed successfully, the result is available, and no child
/// dialogs to the current context are on the dialog stack.
/// </summary>
Complete,
/// <summary>
/// Indicates that the dialog was cancelled and the stack is empty.
/// Indicates that the dialog was canceled, and no child
/// dialogs to the current context are on the dialog stack.
/// </summary>
Cancelled,
}

Просмотреть файл

@ -28,6 +28,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Memory.PathResolvers
{
return base.TransformPath(path);
}
return path;
}
}

Просмотреть файл

@ -18,7 +18,40 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Description>This library implements .NET Simple Dialog classes </Description>
<Description>
This library implements .NET Simple Dialog classes
Library for building bots using Microsoft Bot Framework Connector
Recognizers-Text Version Upgrade: from v1.1.3 to v1.2.6
DateTime - Recognize
New recognized inputs:
- ash wednesday
- halloween
Changed results:
- black friday
- easter
- maundy thursday
- palm sunday
DateTime - Prompt
New recognized inputs:
- untill friday
- monday untill friday
- this past friday
- past friday
Ordinal
Removed inputs:
- the second to last
Number
Changed inputs:
- half
- half nelson
- half seas over
</Description>
<Summary>This library implements .NET Simple Dialog classes </Summary>
</PropertyGroup>
@ -44,8 +77,8 @@
<PackageReference Include="AsyncUsageAnalyzers" Version="1.0.0-alpha003" PrivateAssets="all" />
<PackageReference Include="Microsoft.Bot.Builder" Condition=" '$(PackageVersion)' == '' " Version="4.5.0-local" />
<PackageReference Include="Microsoft.Bot.Builder" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
<PackageReference Include="Microsoft.Recognizers.Text.Choice" Version="1.1.3" />
<PackageReference Include="Microsoft.Recognizers.Text.DateTime" Version="1.1.3" />
<PackageReference Include="Microsoft.Recognizers.Text.Choice" Version="1.2.6.1" />
<PackageReference Include="Microsoft.Recognizers.Text.DateTime" Version="1.2.6.1" />
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" />
</ItemGroup>

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Schema;
@ -101,14 +102,14 @@ namespace Microsoft.Bot.Builder.Dialogs
{
var message = turnContext.Activity.AsMessageActivity();
var culture = turnContext.Activity.Locale ?? DefaultLocale ?? English;
var results = NumberRecognizer.RecognizeNumber(message.Text, culture);
var results = NumberRecognizer.RecognizeNumber(message.Text, culture, NumberOptions.SuppressExtendedTypes);
if (results.Count > 0)
{
// Try to parse value based on type
var text = results[0].Resolution["value"].ToString();
if (typeof(T) == typeof(float))
{
if (float.TryParse(text, out var value))
if (float.TryParse(text, NumberStyles.Any, new CultureInfo(culture), out var value))
{
result.Succeeded = true;
result.Value = (T)(object)value;
@ -116,7 +117,7 @@ namespace Microsoft.Bot.Builder.Dialogs
}
else if (typeof(T) == typeof(int))
{
if (int.TryParse(text, out var value))
if (int.TryParse(text, NumberStyles.Any, new CultureInfo(culture), out var value))
{
result.Succeeded = true;
result.Value = (T)(object)value;
@ -124,7 +125,7 @@ namespace Microsoft.Bot.Builder.Dialogs
}
else if (typeof(T) == typeof(long))
{
if (long.TryParse(text, out var value))
if (long.TryParse(text, NumberStyles.Any, new CultureInfo(culture), out var value))
{
result.Succeeded = true;
result.Value = (T)(object)value;
@ -132,7 +133,7 @@ namespace Microsoft.Bot.Builder.Dialogs
}
else if (typeof(T) == typeof(double))
{
if (double.TryParse(text, out var value))
if (double.TryParse(text, NumberStyles.Any, new CultureInfo(culture), out var value))
{
result.Succeeded = true;
result.Value = (T)(object)value;
@ -140,16 +141,12 @@ namespace Microsoft.Bot.Builder.Dialogs
}
else if (typeof(T) == typeof(decimal))
{
if (decimal.TryParse(text, out var value))
if (decimal.TryParse(text, NumberStyles.Any, new CultureInfo(culture), out var value))
{
result.Succeeded = true;
result.Value = (T)(object)value;
}
}
else
{
throw new NotSupportedException($"NumberPrompt: type argument T of type 'typeof(T)' is not supported");
}
}
}

Просмотреть файл

@ -24,7 +24,7 @@ namespace Microsoft.Bot.Builder.Dialogs
///
/// - The automatic signin flow where once the user signs in and the SSO service will forward the bot
/// the users access token using either an `event` or `invoke` activity.
/// - The "magic code" flow where where once the user signs in they will be prompted by the SSO
/// - The "magic code" flow where once the user signs in they will be prompted by the SSO
/// service to send the bot a six digit code confirming their identity. This code will be sent as a
/// standard `message` activity.
///

Просмотреть файл

@ -8,6 +8,11 @@ using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Dialogs
{
/// <summary>
/// Provides context for a step in a <see cref="WaterfallDialog"/>.
/// </summary>
/// <remarks>The <see cref="DialogContext.Context"/> property contains the <see cref="ITurnContext"/>
/// for the current turn.</remarks>
public class WaterfallStepContext : DialogContext
{
private readonly WaterfallDialog _parentWaterfall;
@ -15,7 +20,6 @@ namespace Microsoft.Bot.Builder.Dialogs
/// <summary>
/// Initializes a new instance of the <see cref="WaterfallStepContext"/> class.
/// Provides context for a turn of a waterfall dialog. Contains ITurnContext as property 'Context'.
/// </summary>
/// <param name= "parentWaterfall">The parent of the waterfall dialog.</param>
/// <param name= "dc">The dialog's context.</param>
@ -72,11 +76,13 @@ namespace Microsoft.Bot.Builder.Dialogs
public DialogReason Reason { get; }
/// <summary>
/// Gets results returned by a dialog called in the previous waterfall step.
/// Gets the result from the previous waterfall step.
/// </summary>
/// <value>
/// Results returned by a dialog called in the previous waterfall step.
/// The result from the previous waterfall step.
/// </value>
/// <remarks>The result is often the return value of a child dialog that was started in
/// the previous step of the waterfall.</remarks>
public object Result { get; }
/// <summary>
@ -88,9 +94,9 @@ namespace Microsoft.Bot.Builder.Dialogs
public IDictionary<string, object> Values { get; }
/// <summary>
/// Used to skip to the next waterfall step.
/// Skips to the next step of the waterfall.
/// </summary>
/// <param name="result">(Optional) result to pass to the next step of the current waterfall dialog.</param>
/// <param name="result">Optional, result to pass to the next step of the current waterfall dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>

Просмотреть файл

@ -57,6 +57,11 @@
<Content Include="**/*.lu" />
<Content Include="**/*.schema" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bot.Builder.Dialogs" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
<PackageReference Include="Microsoft.Bot.Builder.LanguageGeneration" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
<PackageReference Include="Microsoft.Bot.Builder.Dialogs.Declarative" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Bot.Builder.Dialogs.Declarative\Microsoft.Bot.Builder.Dialogs.Declarative.csproj" />

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
namespace Microsoft.Bot.Builder.StreamingExtensions
@ -14,12 +15,13 @@ namespace Microsoft.Bot.Builder.StreamingExtensions
{
/// <summary>
/// Maps various endpoint handlers for the registered bot into the request execution pipeline using the V4 protocol.
/// Throws <see cref="ArgumentNullException"/> if application is null.
/// </summary>
/// <param name="applicationBuilder">The application builder that defines the bot's pipeline.<see cref="IApplicationBuilder"/>.</param>
/// <param name="middlewareSet">The set of middleware the bot executes on each turn. <see cref="MiddlewareSet"/>.</param>
/// <param name="applicationBuilder">The application builder that defines the bot's pipeline.</param>
/// <param name="middlewareSet">The set of middleware the bot executes on each turn.</param>
/// <param name="onTurnError">A callback method to call when an error occurs while executing the pipeline.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IApplicationBuilder UseBotFrameworkNamedPipe(this IApplicationBuilder applicationBuilder, IList<IMiddleware> middlewareSet = null)
/// <exception cref="ArgumentNullException"><paramref name="applicationBuilder"/> is <c>null</c>.</exception>
public static IApplicationBuilder UseBotFrameworkNamedPipe(this IApplicationBuilder applicationBuilder, IList<IMiddleware> middlewareSet = null, Func<ITurnContext, Exception, Task> onTurnError = null)
{
if (applicationBuilder == null)
{
@ -27,7 +29,7 @@ namespace Microsoft.Bot.Builder.StreamingExtensions
}
var connector = new NamedPipeConnector();
connector.InitializeNamedPipeServer(applicationBuilder.ApplicationServices, middlewareSet);
connector.InitializeNamedPipeServer(applicationBuilder.ApplicationServices, middlewareSet, onTurnError);
return applicationBuilder;
}

Просмотреть файл

@ -24,11 +24,15 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Microsoft.Bot.Builder" Version="4.5.0" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.5.0" />
<PackageReference Include="Microsoft.Bot.Builder" Condition=" '$(PackageVersion)' == '' " Version="4.0.0-local" />
<PackageReference Include="Microsoft.Bot.Builder" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Condition=" '$(PackageVersion)' == '' " Version="4.0.0-local" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Bot.Builder\Microsoft.Bot.Builder.csproj" />
<ProjectReference Include="..\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
<ProjectReference Include="..\Microsoft.Bot.StreamingExtensions\Microsoft.Bot.StreamingExtensions.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -12,7 +12,7 @@ namespace Microsoft.Bot.Builder.StreamingExtensions
public class NamedPipeConnector
{
/* The default named pipe all instances of DL ASE listen on is named bfv4.pipes
Unfortunately this name is no longer very discriptive, but for the time being
Unfortunately this name is no longer very descriptive, but for the time being
we're unable to change it without coordinated updates to DL ASE, which we
currently are unable to perform.
*/

Просмотреть файл

@ -189,9 +189,16 @@ namespace Microsoft.Bot.Builder.StreamingExtensions
try
{
var adapter = new BotFrameworkStreamingExtensionsAdapter(_transportServer, _middlewareSet, logger);
IBot bot = null;
// First check if an IBot type definition is available from the service provider.
var bot = _services?.GetService<IBot>();
if (_services != null)
{
/* Creating a new scope for each request allows us to support
* bots that inject scoped services as dependencies.
*/
bot = _services.CreateScope().ServiceProvider.GetService<IBot>();
}
// If no bot has been set, check if a singleton bot is associated with this request handler.
if (bot == null)

Просмотреть файл

@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version Condition=" '$(PackageVersion)' == '' ">4.0.0-local</Version>
<Version Condition=" '$(PackageVersion)' != '' ">$(PackageVersion)</Version>
<PackageVersion Condition=" '$(PackageVersion)' == '' ">4.0.0-local</PackageVersion>
<PackageVersion Condition=" '$(PackageVersion)' != '' ">$(PackageVersion)</PackageVersion>
<Configurations>Debug;Release;Debug - NuGet Packages</Configurations>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>..\..\build\35MSSharedLib1024.snk</AssemblyOriginatorKeyFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Microsoft.Bot.Builder.Teams</PackageId>
<Description>Library for building bots using Microsoft Bot Framework</Description>
<Summary>Library for building bots using Microsoft Bot Framework</Summary>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AdaptiveCards" Version="1.0.0" />
<PackageReference Include="Microsoft.Bot.Builder" Condition=" '$(PackageVersion)' == '' " Version="4.0.0-local" />
<PackageReference Include="Microsoft.Bot.Builder" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
<!-- This may move to the root level dir.props file at some point. -->
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Bot.Connector.Teams\Microsoft.Bot.Connector.Teams.csproj" Exclude="Runtime" DevelopmentDependency="true" />
<ProjectReference Include="..\Microsoft.Bot.Schema.Teams\Microsoft.Bot.Schema.Teams.csproj" />
<ProjectReference Include="..\Microsoft.Bot.Builder\Microsoft.Bot.Builder.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,304 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Schema.Teams;
using Newtonsoft.Json.Linq;
namespace Microsoft.Bot.Builder.Teams
{
public class TeamsActivityHandler : ActivityHandler
{
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)
{
throw new ArgumentNullException(nameof(turnContext));
}
if (turnContext.Activity == null)
{
throw new ArgumentException($"{nameof(turnContext)} must have non-null Activity.");
}
if (turnContext.Activity.Type == null)
{
throw new ArgumentException($"{nameof(turnContext)}.Activity must have non-null Type.");
}
switch (turnContext.Activity.Type)
{
case ActivityTypes.Invoke:
var invokeResponse = await OnInvokeActivityAsync(new DelegatingTurnContext<IInvokeActivity>(turnContext), cancellationToken).ConfigureAwait(false);
if (invokeResponse != null)
{
await turnContext.SendActivityAsync(new Activity { Value = invokeResponse, Type = ActivityTypesEx.InvokeResponse }).ConfigureAwait(false);
}
break;
default:
await base.OnTurnAsync(turnContext, cancellationToken).ConfigureAwait(false);
break;
}
}
protected virtual async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Name == null)
{
return await OnTeamsCardActionInvokeAsync(turnContext, cancellationToken).ConfigureAwait(false);
}
else
{
switch (turnContext.Activity.Name)
{
case "signin/verifyState":
return await OnTeamsSigninVerifyStateAsync(turnContext, cancellationToken).ConfigureAwait(false);
case "fileConsent/invoke":
return await OnTeamsFileConsentAsync(turnContext, SafeCast<FileConsentCardResponse>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false);
case "actionableMessage/executeAction":
await OnTeamsO365ConnectorCardActionAsync(turnContext, SafeCast<O365ConnectorCardActionQuery>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false);
return CreateInvokeResponse();
case "composeExtension/query":
return CreateInvokeResponse(await OnTeamsMessagingExtensionQueryAsync(turnContext, SafeCast<MessagingExtensionQuery>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));
case "composeExtension/queryLink":
return CreateInvokeResponse(await OnTeamsAppBasedLinkQueryAsync(turnContext, SafeCast<AppBasedLinkQuery>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));
case "composeExtension/selectItem":
return CreateInvokeResponse(await OnTeamsMessagingExtensionSelectItemAsync(turnContext, turnContext.Activity.Value as JObject, cancellationToken).ConfigureAwait(false));
case "composeExtension/submitAction":
return CreateInvokeResponse(await OnTeamsMessagingExtensionSubmitActionDispatchAsync(turnContext, SafeCast<MessagingExtensionAction>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));
case "composeExtension/fetchTask":
return CreateInvokeResponse(await OnTeamsMessagingExtensionFetchTaskAsync(turnContext, cancellationToken).ConfigureAwait(false));
case "composeExtension/onquerySettingsUrl":
return CreateInvokeResponse(await OnTeamsMessagingExtensionConfigurationSettingsUrlAsync(turnContext, cancellationToken).ConfigureAwait(false));
case "composeExtension/setting":
return CreateInvokeResponse(await OnTeamsMessagingExtensionConfigurationSettingsAsync(turnContext, cancellationToken).ConfigureAwait(false));
case "task/fetch":
return CreateInvokeResponse(await OnTeamsTaskModuleFetchAsync(turnContext, cancellationToken).ConfigureAwait(false));
case "task/submit":
return CreateInvokeResponse(await OnTeamsTaskModuleSubmitAsync(turnContext, cancellationToken).ConfigureAwait(false));
default:
return null;
}
}
}
protected virtual Task<InvokeResponse> OnTeamsCardActionInvokeAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
return Task.FromResult<InvokeResponse>(null);
}
protected virtual Task<InvokeResponse> OnTeamsSigninVerifyStateAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
return Task.FromResult<InvokeResponse>(null);
}
protected virtual async Task<InvokeResponse> OnTeamsFileConsentAsync(ITurnContext<IInvokeActivity> turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken)
{
switch (fileConsentCardResponse.Action)
{
case "accept":
await OnTeamsFileConsentAcceptAsync(turnContext, fileConsentCardResponse, cancellationToken).ConfigureAwait(false);
return CreateInvokeResponse();
case "decline":
await OnTeamsFileConsentDeclineAsync(turnContext, fileConsentCardResponse, cancellationToken).ConfigureAwait(false);
return CreateInvokeResponse();
default:
return null;
}
}
protected virtual Task OnTeamsFileConsentAcceptAsync(ITurnContext<IInvokeActivity> turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
protected virtual Task OnTeamsFileConsentDeclineAsync(ITurnContext<IInvokeActivity> turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
protected virtual Task<MessagingExtensionResponse> OnTeamsMessagingExtensionQueryAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionQuery query, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionResponse>(null);
}
protected virtual Task OnTeamsO365ConnectorCardActionAsync(ITurnContext<IInvokeActivity> turnContext, O365ConnectorCardActionQuery query, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
protected virtual Task<MessagingExtensionResponse> OnTeamsAppBasedLinkQueryAsync(ITurnContext<IInvokeActivity> turnContext, AppBasedLinkQuery query, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionResponse>(null);
}
protected virtual Task<MessagingExtensionResponse> OnTeamsMessagingExtensionSelectItemAsync(ITurnContext<IInvokeActivity> turnContext, JObject query, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionResponse>(null);
}
protected virtual Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionFetchTaskAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionActionResponse>(null);
}
protected virtual async Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionSubmitActionDispatchAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction query, CancellationToken cancellationToken)
{
var value = turnContext.Activity.Value as MessagingExtensionAction;
if (value?.BotMessagePreviewAction != null)
{
switch (value.BotMessagePreviewAction)
{
case "edit":
return await OnTeamsMessagingExtensionBotMessagePreviewEditAsync(turnContext, query, cancellationToken).ConfigureAwait(false);
case "submit":
return await OnTeamsMessagingExtensionBotMessagePreviewSendAsync(turnContext, query, cancellationToken).ConfigureAwait(false);
default:
return null;
}
}
else
{
return await OnTeamsMessagingExtensionSubmitActionAsync(turnContext, query, cancellationToken).ConfigureAwait(false);
}
}
protected virtual Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionSubmitActionAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction query, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionActionResponse>(null);
}
protected virtual Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionBotMessagePreviewEditAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction query, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionActionResponse>(null);
}
protected virtual Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionBotMessagePreviewSendAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction query, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionActionResponse>(null);
}
protected virtual Task<MessagingExtensionResponse> OnTeamsMessagingExtensionConfigurationSettingsUrlAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionResponse>(null);
}
protected virtual Task<MessagingExtensionResponse> OnTeamsMessagingExtensionConfigurationSettingsAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
return Task.FromResult<MessagingExtensionResponse>(null);
}
protected virtual Task<TaskModuleResponse> OnTeamsTaskModuleFetchAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
return Task.FromResult<TaskModuleResponse>(null);
}
protected virtual Task<TaskModuleResponse> OnTeamsTaskModuleSubmitAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
return Task.FromResult<TaskModuleResponse>(null);
}
protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var channelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
if (string.IsNullOrEmpty(channelData?.EventType))
{
return base.OnConversationUpdateActivityAsync(turnContext, cancellationToken);
}
switch (channelData.EventType)
{
case "teamMemberAdded":
return OnTeamsMembersAddedAsync(turnContext.Activity.MembersAdded, channelData.Team, turnContext, cancellationToken);
case "teamMemberRemoved":
return OnTeamsMembersRemovedAsync(turnContext.Activity.MembersRemoved, channelData.Team, turnContext, cancellationToken);
case "channelCreated":
return OnTeamsChannelCreatedAsync(channelData.Channel, channelData.Team, turnContext, cancellationToken);
case "channelDeleted":
return OnTeamsChannelDeletedAsync(channelData.Channel, channelData.Team, turnContext, cancellationToken);
case "channelRenamed":
return OnTeamsChannelRenamedAsync(channelData.Channel, channelData.Team, turnContext, cancellationToken);
case "teamRenamed":
return OnTeamsTeamRenamedAsync(channelData.Team, turnContext, cancellationToken);
default:
return base.OnConversationUpdateActivityAsync(turnContext, cancellationToken);
}
}
protected virtual Task OnTeamsMembersAddedAsync(IList<ChannelAccount> membersAdded, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
return OnMembersAddedAsync(membersAdded, turnContext, cancellationToken);
}
protected virtual Task OnTeamsMembersRemovedAsync(IList<ChannelAccount> membersRemoved, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
return OnMembersRemovedAsync(membersRemoved, turnContext, cancellationToken);
}
protected virtual Task OnTeamsChannelCreatedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
protected virtual Task OnTeamsChannelDeletedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
protected virtual Task OnTeamsChannelRenamedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
protected virtual Task OnTeamsTeamRenamedAsync(TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private static InvokeResponse CreateInvokeResponse(object body = null)
{
return new InvokeResponse { Status = (int)HttpStatusCode.OK, Body = body };
}
private static T SafeCast<T>(object value)
{
var obj = value as JObject;
if (obj == null)
{
throw new Exception($"expected type '{value.GetType().Name}'");
}
return obj.ToObject<T>();
}
}
}

Просмотреть файл

@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema.Teams;
namespace Microsoft.Bot.Builder.Teams
{
/// <summary>
/// Filters request based on provided tenant list.
/// </summary>
/// <seealso cref="IMiddleware" />
public class TeamsTenantFilteringMiddleware : IMiddleware
{
/// <summary>
/// The tenant map.
/// </summary>
private readonly HashSet<string> tenantMap;
/// <summary>
/// Initializes a new instance of the <see cref="TeamsTenantFilteringMiddleware"/> class.
/// </summary>
/// <param name="allowedTenantIds">The list of allowed tenants.</param>
public TeamsTenantFilteringMiddleware(IEnumerable<string> allowedTenantIds)
{
if (allowedTenantIds == null)
{
throw new ArgumentNullException(nameof(allowedTenantIds));
}
this.tenantMap = new HashSet<string>(allowedTenantIds);
}
/// <summary>
/// When implemented in middleware, processess an incoming activity.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="next">The delegate to call to continue the bot middleware pipeline.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>
/// A task that represents the work queued to execute.
/// </returns>
/// <remarks>
/// Middleware calls the <paramref name="next" /> delegate to pass control to
/// the next middleware in the pipeline. If middleware doesnt call the next delegate,
/// the adapter does not call any of the subsequent middlewares request handlers or the
/// bots receive handler, and the pipeline short circuits.
/// <para>The <paramref name="turnContext" /> provides information about the
/// incoming activity, and other data needed to process the activity.</para>
/// </remarks>
/// <seealso cref="ITurnContext" />
/// <seealso cref="Schema.IActivity" />
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
{
if (!turnContext.Activity.ChannelId.Equals(Channels.Msteams, StringComparison.OrdinalIgnoreCase))
{
await next(cancellationToken).ConfigureAwait(false);
return;
}
TeamsChannelData teamsChannelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
string tenantId = teamsChannelData?.Tenant?.Id;
if (string.IsNullOrEmpty(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id is missing.");
}
if (!this.tenantMap.Contains(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id '" + tenantId + "' is not allowed access.");
}
await next(cancellationToken).ConfigureAwait(false);
}
}
}

Просмотреть файл

@ -11,21 +11,40 @@ using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder
{
/// <summary>
/// An implementation of the IBot interface intended for further subclassing.
/// Derive from this class to plug in code to handle particular Activity types.
/// Pre and post processing of Activities can be plugged in by deriving and calling
/// the base class implementation.
/// An implementation of the <see cref="IBot"/> interface, intended for further subclassing.
/// </summary>
/// <remarks>
/// Derive from this class to plug in code to handle particular activity types.
/// Pre- and post-processing of <see cref="Activity"/> objects can be added by calling
/// the base class implementation from the derived class.
/// </remarks>
public class ActivityHandler : IBot
{
/// <summary>
/// The OnTurnAsync function is called by the Adapter (for example, the <see cref="BotFrameworkAdapter"/>)
/// at runtime in order to process an inbound Activity.
/// Called by the adapter (for example, a <see cref="BotFrameworkAdapter"/>)
/// at runtime in order to process an inbound <see cref="Activity"/>.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// This method calls other methods in this class based on the type of the activity to
/// process, which allows a derived class to provide type-specific logic in a controlled way.
///
/// In a derived class, override this method to add logic that applies to all activity types.
/// Add logic to apply before the type-specific logic before the call to the base class
/// <see cref="OnTurnAsync(ITurnContext, CancellationToken)"/> method.
/// Add logic to apply after the type-specific logic after the call to the base class
/// <see cref="OnTurnAsync(ITurnContext, CancellationToken)"/> method.
/// </remarks>
/// <seealso cref="OnMessageActivityAsync(ITurnContext{IMessageActivity}, CancellationToken)"/>
/// <seealso cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
/// <seealso cref="OnMessageReactionActivityAsync(ITurnContext{IMessageReactionActivity}, CancellationToken)"/>
/// <seealso cref="OnEventActivityAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
/// <seealso cref="OnUnrecognizedActivityTypeAsync(ITurnContext, CancellationToken)"/>
/// <seealso cref="Activity.Type"/>
/// <seealso cref="ActivityTypes"/>
public virtual Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)
@ -63,15 +82,18 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Invoked when a message activity is received from the user when the base behavior of
/// <see cref="OnTurnAsync(ITurnContext, CancellationToken)"/> is used.
/// If overridden, this could potentially contain conversational logic.
/// By default, this method does nothing.
/// Override this in a derived class to provide logic specific to
/// <see cref="ActivityTypes.Message"/> activities, such as the conversational logic.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// method receives a message activity, it calls this method.
/// </remarks>
/// <seealso cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
protected virtual Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
@ -86,10 +108,27 @@ namespace Microsoft.Bot.Builder
/// if any users have been added or <see cref="OnMembersRemovedAsync(IList{ChannelAccount}, ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
/// if any users have been removed. The method checks the member ID so that it only responds to updates regarding members other than the bot itself.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// method receives a conversation update activity, it calls this method.
/// If the conversation update activity indicates that members other than the bot joined the conversation, it calls
/// <see cref="OnMembersAddedAsync(IList{ChannelAccount}, ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>.
/// If the conversation update activity indicates that members other than the bot left the conversation, it calls
/// <see cref="OnMembersRemovedAsync(IList{ChannelAccount}, ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>.
///
/// In a derived class, override this method to add logic that applies to all conversation update activities.
/// Add logic to apply before the member added or removed logic before the call to the base class
/// <see cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/> method.
/// Add logic to apply after the member added or removed logic after the call to the base class
/// <see cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/> method.
/// </remarks>
/// <seealso cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// <seealso cref="OnMembersAddedAsync(IList{ChannelAccount}, ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
/// <seealso cref="OnMembersRemovedAsync(IList{ChannelAccount}, ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
protected virtual Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.MembersAdded != null)
@ -111,32 +150,42 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Invoked when members other than this bot (like a user) are added to the conversation when the base behavior of
/// <see cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/> is used.
/// If overridden, this could potentially send a greeting message to the user instead of waiting for the user to send a message first.
/// By default, this method does nothing.
/// Override this in a derived class to provide logic for when members other than the bot
/// join the conversation, such as your bot's welcome logic.
/// </summary>
/// <param name="membersAdded">A list of all the users that have been added in the conversation update.</param>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="membersAdded">A list of all the members added to the conversation, as
/// described by the conversation update activity.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
/// method receives a conversation update activity that indicates one or more users other than the bot
/// are joining the conversation, it calls this method.
/// </remarks>
/// <seealso cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
protected virtual Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <summary>
/// Invoked when members other than this bot (like a user) are removed from the conversation when the base behavior of
/// <see cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/> is used.
/// This method could optionally be overridden to perform actions related to users leaving a group conversation.
/// By default, this method does nothing.
/// Override this in a derived class to provide logic for when members other than the bot
/// leave the conversation, such as your bot's good-bye logic.
/// </summary>
/// <param name="membersRemoved">A list of all the users that have been removed in the conversation update.</param>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="membersRemoved">A list of all the members removed from the conversation, as
/// described by the conversation update activity.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
/// method receives a conversation update activity that indicates one or more users other than the bot
/// are leaving the conversation, it calls this method.
/// </remarks>
/// <seealso cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
protected virtual Task OnMembersRemovedAsync(IList<ChannelAccount> membersRemoved, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
@ -151,10 +200,28 @@ namespace Microsoft.Bot.Builder
/// The value of this property is the activity id of a previously sent activity given back to the
/// bot as the response from a send call.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// method receives a message reaction activity, it calls this method.
/// If the message reaction indicates that reactions were added to a message, it calls
/// <see cref="OnReactionsAddedAsync(IList{MessageReaction}, ITurnContext{IMessageReactionActivity}, CancellationToken)"/>.
/// If the message reaction indicates that reactions were removed from a message, it calls
/// <see cref="OnReactionsRemovedAsync(IList{MessageReaction}, ITurnContext{IMessageReactionActivity}, CancellationToken)"/>.
///
/// In a derived class, override this method to add logic that applies to all message reaction activities.
/// Add logic to apply before the reactions added or removed logic before the call to the base class
/// <see cref="OnMessageReactionActivityAsync(ITurnContext{IMessageReactionActivity}, CancellationToken)"/> method.
/// Add logic to apply after the reactions added or removed logic after the call to the base class
/// <see cref="OnMessageReactionActivityAsync(ITurnContext{IMessageReactionActivity}, CancellationToken)"/> method.
///
/// </remarks>
/// <seealso cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// <seealso cref="OnReactionsAddedAsync(IList{MessageReaction}, ITurnContext{IMessageReactionActivity}, CancellationToken)"/>
/// <seealso cref="OnReactionsRemovedAsync(IList{MessageReaction}, ITurnContext{IMessageReactionActivity}, CancellationToken)"/>
protected virtual async Task OnMessageReactionActivityAsync(ITurnContext<IMessageReactionActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.ReactionsAdded != null)
@ -169,26 +236,52 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Called when there have been Reactions added that reference a previous Activity.
/// Override this in a derived class to provide logic for when reactions to a previous activity
/// are added to the conversation.
/// </summary>
/// <param name="messageReactions">The list of reactions added.</param>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji) to a
/// previously sent message on the conversation. Message reactions are supported by only a few channels.
/// The activity that the message is in reaction to is identified by the activity's
/// <see cref="Activity.ReplyToId"/> property. The value of this property is the activity ID
/// of a previously sent activity. When the bot sends an activity, the channel assigns an ID to it,
/// which is available in the <see cref="ResourceResponse.Id"/> of the result.
/// </remarks>
/// <seealso cref="OnMessageReactionActivityAsync(ITurnContext{IMessageReactionActivity}, CancellationToken)"/>
/// <seealso cref="Activity.Id"/>
/// <seealso cref="ITurnContext.SendActivityAsync(IActivity, CancellationToken)"/>
/// <seealso cref="ResourceResponse.Id"/>
protected virtual Task OnReactionsAddedAsync(IList<MessageReaction> messageReactions, ITurnContext<IMessageReactionActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <summary>
/// Called when there have been Reactions removed that reference a previous Activity.
/// Override this in a derived class to provide logic for when reactions to a previous activity
/// are removed from the conversation.
/// </summary>
/// <param name="messageReactions">The list of reactions removed.</param>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji) to a
/// previously sent message on the conversation. Message reactions are supported by only a few channels.
/// The activity that the message is in reaction to is identified by the activity's
/// <see cref="Activity.ReplyToId"/> property. The value of this property is the activity ID
/// of a previously sent activity. When the bot sends an activity, the channel assigns an ID to it,
/// which is available in the <see cref="ResourceResponse.Id"/> of the result.
/// </remarks>
/// <seealso cref="OnMessageReactionActivityAsync(ITurnContext{IMessageReactionActivity}, CancellationToken)"/>
/// <seealso cref="Activity.Id"/>
/// <seealso cref="ITurnContext.SendActivityAsync(IActivity, CancellationToken)"/>
/// <seealso cref="ResourceResponse.Id"/>
protected virtual Task OnReactionsRemovedAsync(IList<MessageReaction> messageReactions, ITurnContext<IMessageReactionActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
@ -202,10 +295,31 @@ namespace Microsoft.Bot.Builder
/// activity's name is <c>tokens/response</c> or <see cref="OnEventAsync(ITurnContext{IEventActivity}, CancellationToken)"/> otherwise.
/// A <c>tokens/response</c> event can be triggered by an <see cref="OAuthCard"/>.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// method receives an event activity, it calls this method.
/// If the event <see cref="IEventActivity.Name"/> is `tokens/response`, it calls
/// <see cref="OnTokenResponseEventAsync(ITurnContext{IEventActivity}, CancellationToken)"/>;
/// otherwise, it calls <see cref="OnEventAsync(ITurnContext{IEventActivity}, CancellationToken)"/>.
///
/// In a derived class, override this method to add logic that applies to all event activities.
/// Add logic to apply before the specific event-handling logic before the call to the base class
/// <see cref="OnEventActivityAsync(ITurnContext{IEventActivity}, CancellationToken)"/> method.
/// Add logic to apply after the specific event-handling logic after the call to the base class
/// <see cref="OnEventActivityAsync(ITurnContext{IEventActivity}, CancellationToken)"/> method.
///
/// Event activities communicate programmatic information from a client or channel to a bot.
/// The meaning of an event activity is defined by the <see cref="IEventActivity.Name"/> property,
/// which is meaningful within the scope of a channel.
/// A `tokens/response` event can be triggered by an <see cref="OAuthCard"/> or an OAuth prompt.
/// </remarks>
/// <seealso cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// <seealso cref="OnTokenResponseEventAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
/// <seealso cref="OnEventAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
protected virtual Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Name == "tokens/response")
@ -222,10 +336,20 @@ namespace Microsoft.Bot.Builder
/// If using an <c>OAuthPrompt</c>, override this method to forward this <see cref="Activity"/> to the current dialog.
/// By default, this method does nothing.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnEventActivityAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
/// method receives an event with a <see cref="IEventActivity.Name"/> of `tokens/response`,
/// it calls this method.
///
/// If your bot uses the <c>OAuthPrompt</c>, forward the incoming <see cref="Activity"/> to
/// the current dialog.
/// </remarks>
/// <seealso cref="OnEventActivityAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
/// <seealso cref="OnEventAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
protected virtual Task OnTokenResponseEventAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
@ -237,10 +361,17 @@ namespace Microsoft.Bot.Builder
/// This method could optionally be overridden if the bot is meant to handle miscellaneous events.
/// By default, this method does nothing.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnEventActivityAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
/// method receives an event with a <see cref="IEventActivity.Name"/> other than `tokens/response`,
/// it calls this method.
/// </remarks>
/// <seealso cref="OnEventActivityAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
/// <seealso cref="OnTokenResponseEventAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
protected virtual Task OnEventAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
@ -257,6 +388,18 @@ namespace Microsoft.Bot.Builder
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// When the <see cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// method receives an activity that is not a message, conversation update, message reaction,
/// or event activity, it calls this method.
/// </remarks>
/// <seealso cref="OnTurnAsync(ITurnContext, CancellationToken)"/>
/// <seealso cref="OnMessageActivityAsync(ITurnContext{IMessageActivity}, CancellationToken)"/>
/// <seealso cref="OnConversationUpdateActivityAsync(ITurnContext{IConversationUpdateActivity}, CancellationToken)"/>
/// <seealso cref="OnMessageReactionActivityAsync(ITurnContext{IMessageReactionActivity}, CancellationToken)"/>
/// <seealso cref="OnEventActivityAsync(ITurnContext{IEventActivity}, CancellationToken)"/>
/// <seealso cref="Activity.Type"/>
/// <seealso cref="ActivityTypes"/>
protected virtual Task OnUnrecognizedActivityTypeAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;

Просмотреть файл

@ -38,7 +38,7 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Gets or sets an error handler that can catche exceptions in the middleware or application.
/// Gets or sets an error handler that can catch exceptions in the middleware or application.
/// </summary>
/// <value>An error handler that can catch exceptions in the middleware or application.</value>
public Func<ITurnContext, Exception, Task> OnTurnError { get; set; }
@ -111,7 +111,7 @@ namespace Microsoft.Bot.Builder
/// <summary>
/// Sends a proactive message to a conversation.
/// </summary>
/// <param name="botId">The application ID of the bot. This paramter is ignored in
/// <param name="botId">The application ID of the bot. This parameter is ignored in
/// single tenant the Adapters (Console, Test, etc) but is critical to the BotFrameworkAdapter
/// which is multi-tenant aware. </param>
/// <param name="reference">A reference to the conversation to continue.</param>

Просмотреть файл

@ -10,6 +10,7 @@ using System.Net.Http;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Integration;
@ -827,9 +828,7 @@ namespace Microsoft.Bot.Builder
{
if (reference.Conversation != null)
{
var typeOfDynamic = reference.Conversation.GetType();
var tenantProperty = typeOfDynamic.GetProperty("tenantId");
var tenantId = tenantProperty?.GetValue(reference.Conversation, null);
var tenantId = reference.Conversation.TenantId;
if (tenantId != null)
{
@ -976,8 +975,8 @@ namespace Microsoft.Bot.Builder
// NOTE: we can't do async operations inside of a AddOrUpdate, so we split access pattern
string appPassword = await _credentialProvider.GetAppPasswordAsync(appId).ConfigureAwait(false);
appCredentials = (_channelProvider != null && _channelProvider.IsGovernment()) ?
new MicrosoftGovernmentAppCredentials(appId, appPassword, _httpClient) :
new MicrosoftAppCredentials(appId, appPassword, _httpClient);
new MicrosoftGovernmentAppCredentials(appId, appPassword, _httpClient, _logger) :
new MicrosoftAppCredentials(appId, appPassword, _httpClient, _logger);
_appCredentialMap[appId] = appCredentials;
return appCredentials;
}

Просмотреть файл

@ -12,8 +12,20 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.Bot.Builder
{
/// <summary>
/// Reads and writes state for your bot to storage.
/// Defines a state management object and automates the reading and writing of associated state
/// properties to a storage layer.
/// </summary>
/// <remarks>
/// Each state management object defines a scope for a storage layer.
///
/// State properties are created within a state management scope, and the Bot Framework
/// defines these scopes:
/// <see cref="ConversationState"/>, <see cref="UserState"/>, and <see cref="PrivateConversationState"/>.
///
/// You can define additional scopes for your bot.
/// </remarks>
/// <seealso cref="IStorage"/>
/// <seealso cref="IStatePropertyAccessor{T}"/>
public abstract class BotState : IPropertyManager
{
private readonly string _contextServiceKey;
@ -22,8 +34,16 @@ namespace Microsoft.Bot.Builder
/// <summary>
/// Initializes a new instance of the <see cref="BotState"/> class.
/// </summary>
/// <param name="storage">The storage provider to use.</param>
/// <param name="contextServiceKey">the key for caching on the context services dictionary.</param>
/// <param name="storage">The storage layer this state management object will use to store
/// and retrieve state.</param>
/// <param name="contextServiceKey">The key for the state cache for this <see cref="BotState"/>.</param>
/// <remarks>This constructor creates a state management object and associated scope.
/// The object uses <paramref name="storage"/> to persist state property values.
/// The object uses the <paramref name="contextServiceKey"/> to cache state within the context for each turn.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="storage"/> or <paramref name="contextServiceKey"/>
/// is <c>null</c>.</exception>
/// <seealso cref="ITurnContext"/>
public BotState(IStorage storage, string contextServiceKey)
{
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
@ -31,11 +51,13 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Create a property definition and register it with this BotState.
/// Creates a named state property within the scope of a <see cref="BotState"/> and returns
/// an accessor for the property.
/// </summary>
/// <typeparam name="T">type of property.</typeparam>
/// <param name="name">name of the property.</param>
/// <returns>The created state property accessor.</returns>
/// <typeparam name="T">The value type of the property.</typeparam>
/// <param name="name">The name of the property.</param>
/// <returns>An accessor for the property.</returns>
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
public IStatePropertyAccessor<T> CreateProperty<T>(string name)
{
if (string.IsNullOrWhiteSpace(name))
@ -47,13 +69,15 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Reads in the current state object and caches it in the context object for this turm.
/// Populates the state cache for this <see cref="BotState"/> from the storage layer.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="force">Optional. True to bypass the cache.</param>
/// <param name="force">Optional, <c>true</c> to overwrite any existing state cache;
/// or <c>false</c> to load state from storage only if the cache doesn't already exist.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <exception cref="ArgumentNullException"><paramref name="turnContext"/> is <c>null</c>.</exception>
public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)
@ -72,13 +96,15 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// If it has changed, writes to storage the state object that is cached in the current context object for this turn.
/// Writes the state cache for this <see cref="BotState"/> to the storage layer.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="force">Optional. True to save state to storage whether or not there are changes.</param>
/// <param name="force">Optional, <c>true</c> to save the state cache to storage;
/// or <c>false</c> to save state to storage only if a property in the cache has changed.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <exception cref="ArgumentNullException"><paramref name="turnContext"/> is <c>null</c>.</exception>
public virtual async Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)
@ -87,7 +113,7 @@ namespace Microsoft.Bot.Builder
}
var cachedState = turnContext.TurnState.Get<CachedBotState>(_contextServiceKey);
if (force || (cachedState != null && cachedState.IsChanged()))
if (cachedState != null && (force || cachedState.IsChanged()))
{
var key = GetStorageKey(turnContext);
var changes = new Dictionary<string, object>
@ -101,12 +127,17 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Clears any state currently stored in this state scope.
/// Clears the state cache for this <see cref="BotState"/>.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="cancellationToken">cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <remarks>NOTE: that SaveChangesAsync must be called in order for the cleared state to be persisted to the underlying store.</remarks>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>This method clears the state cache in the turn context. Call
/// <see cref="SaveChangesAsync(ITurnContext, bool, CancellationToken)"/> to persist this
/// change in the storage layer.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="turnContext"/> is <c>null</c>.</exception>
public virtual Task ClearStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)
@ -121,11 +152,13 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Delete any state currently stored in this state scope.
/// Deletes any state in storage and the cache for this <see cref="BotState"/>.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="cancellationToken">cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <exception cref="ArgumentNullException"><paramref name="turnContext"/> is <c>null</c>.</exception>
public virtual async Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)
@ -144,10 +177,11 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Returns a copy of the raw cached data from the TurnContext, this can be used for tracing scenarios.
/// Gets a copy of the raw cached data for this <see cref="BotState"/> from the turn context.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <returns>A JSON representation of the cached state.</returns>
/// <exception cref="ArgumentNullException"><paramref name="turnContext"/> is <c>null</c>.</exception>
public JToken Get(ITurnContext turnContext)
{
if (turnContext == null)
@ -168,15 +202,17 @@ namespace Microsoft.Bot.Builder
protected abstract string GetStorageKey(ITurnContext turnContext);
/// <summary>
/// Gets a property from the state cache in the turn context.
/// Gets the value of a property from the state cache for this <see cref="BotState"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <typeparam name="T">The value type of the property.</typeparam>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="propertyName">The name of the property to get.</param>
/// <param name="propertyName">The name of the property.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the task is successful, the result contains the property value.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="turnContext"/> or
/// <paramref name="propertyName"/> is <c>null</c>.</exception>
protected Task<T> GetPropertyValueAsync<T>(ITurnContext turnContext, string propertyName, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)
@ -197,13 +233,15 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Deletes a property from the state cache in the turn context.
/// Deletes a property from the state cache for this <see cref="BotState"/>.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="propertyName">The name of the property to delete.</param>
/// <param name="propertyName">The name of the property.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <exception cref="ArgumentNullException"><paramref name="turnContext"/> or
/// <paramref name="propertyName"/> is <c>null</c>.</exception>
protected Task DeletePropertyValueAsync(ITurnContext turnContext, string propertyName, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)
@ -222,7 +260,7 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Set the value of a property in the state cache in the turn context.
/// Sets the value of a property in the state cache for this <see cref="BotState"/>.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="propertyName">The name of the property to set.</param>
@ -230,6 +268,8 @@ namespace Microsoft.Bot.Builder
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <exception cref="ArgumentNullException"><paramref name="turnContext"/> or
/// <paramref name="propertyName"/> is <c>null</c>.</exception>
protected Task SetPropertyValueAsync(ITurnContext turnContext, string propertyName, object value, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext == null)

Просмотреть файл

@ -6,14 +6,18 @@ using System;
namespace Microsoft.Bot.Builder
{
/// <summary>
/// Handles persistence of a conversation state object using the conversation ID as part of the key.
/// Defines a state management object for conversation state.
/// </summary>
/// <remarks>
/// Conversation state is available in any turn in a specific conversation, regardless of user,
/// such as in a group conversation.
/// </remarks>
public class ConversationState : BotState
{
/// <summary>
/// Initializes a new instance of the <see cref="ConversationState"/> class.
/// </summary>
/// <param name="storage">The storage provider to use.</param>
/// <param name="storage">The storage layer to use.</param>
public ConversationState(IStorage storage)
: base(storage, nameof(ConversationState))
{
@ -24,6 +28,13 @@ namespace Microsoft.Bot.Builder
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <returns>The storage key.</returns>
/// <remarks>
/// Conversation state includes the channel ID and conversation ID as part of its storage key.
/// </remarks>
/// <exception cref="ArgumentNullException">The <see cref="ITurnContext.Activity"/> for the
/// current turn is missing <see cref="Schema.Activity.ChannelId"/> or
/// <see cref="Schema.Activity.Conversation"/> information, or the conversation's
/// <see cref="Schema.ConversationAccount.Id"/> is missing.</exception>
protected override string GetStorageKey(ITurnContext turnContext)
{
var channelId = turnContext.Activity.ChannelId ?? throw new ArgumentNullException("invalid activity-missing channelId");

Просмотреть файл

@ -34,7 +34,7 @@ namespace Microsoft.Bot.Builder
public interface IMiddleware
{
/// <summary>
/// When implemented in middleware, processess an incoming activity.
/// When implemented in middleware, processes an incoming activity.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="next">The delegate to call to continue the bot middleware pipeline.</param>

Просмотреть файл

@ -43,7 +43,7 @@ namespace Microsoft.Bot.Builder
/// activity.
/// <para>The activity's <see cref="IActivity.Id"/> indicates the activity in the
/// conversation to replace.</para>
/// <para>If the activity is successfully sent, the <paramref name="next"/> delegater returns
/// <para>If the activity is successfully sent, the <paramref name="next"/> delegate returns
/// a <see cref="ResourceResponse"/> object containing the ID that the receiving
/// channel assigned to the activity. Use this response object as the return value of this handler.</para>
/// </remarks>
@ -74,12 +74,23 @@ namespace Microsoft.Bot.Builder
public delegate Task DeleteActivityHandler(ITurnContext turnContext, ConversationReference reference, Func<Task> next);
/// <summary>
/// A ITurnContext where the inbound Activity property is strongly typed.
/// Provides context for a turn of a bot, where the context's <see cref="Activity"/> property is strongly typed.
/// </summary>
/// <typeparam name="T">An IActivity derived type, that is one of IMessageActivity, IConversationUpdateActivity etc.</typeparam>
/// <typeparam name="T">The activity type for this turn of the bot.</typeparam>
/// <remarks>The <see cref="IActivity"/> interface defines properties shared by every type of activity.
/// The interfaces that derive from <see cref="IActivity"/> include properties specific to a specific
/// type of activity. For example, <see cref="IMessageActivity"/> includes properties associated with
/// message activities, and <see cref="IEventActivity"/> includes properties associated with event activities.</remarks>
/// <seealso cref="IActivity"/>
/// <seealso cref="ActivityHandler"/>
/// <seealso cref="ITurnContext"/>
public interface ITurnContext<T> : ITurnContext
where T : IActivity
{
/// <summary>
/// Gets the activity for this turn of the bot.
/// </summary>
/// <value>The activity for this turn of the bot.</value>
new T Activity { get; }
}
@ -91,6 +102,7 @@ namespace Microsoft.Bot.Builder
/// length of the turn.</remarks>
/// <seealso cref="IBot"/>
/// <seealso cref="IMiddleware"/>
/// <seealso cref="ITurnContext{T}"/>
public interface ITurnContext
{
/// <summary>
@ -106,15 +118,16 @@ namespace Microsoft.Bot.Builder
TurnContextStateCollection TurnState { get; }
/// <summary>
/// Gets the incoming request.
/// Gets the activity for this turn of the bot.
/// </summary>
/// <value>The incoming request.</value>
/// <value>The activity for this turn of the bot.</value>
Activity Activity { get; }
/// <summary>
/// Gets a value indicating whether at least one response was sent for the current turn.
/// </summary>
/// <value><c>true</c> if at least one response was sent for the current turn; otherwise, <c>false</c>.</value>
/// <seealso cref="SendActivityAsync(IActivity, CancellationToken)"/>
bool Responded { get; }
/// <summary>
@ -125,12 +138,13 @@ namespace Microsoft.Bot.Builder
/// channel.</param>
/// <param name="inputHint">Optional, indicates whether your bot is accepting,
/// expecting, or ignoring user input after the message is delivered to the client.
/// One of: "acceptingInput", "ignoringInput", or "expectingInput".
/// Default is "acceptingInput".</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <see cref="InputHints"/> defines the possible values.
/// Default is <see cref="InputHints.AcceptingInput"/>.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the activity is successfully sent, the task result contains
/// a <see cref="ResourceResponse"/> object containing the ID that the receiving
/// a <see cref="ResourceResponse"/> object that contains the ID that the receiving
/// channel assigned to the activity.
/// <para>See the channel's documentation for limits imposed upon the contents of
/// <paramref name="textReplyToSend"/>.</para>
@ -149,7 +163,8 @@ namespace Microsoft.Bot.Builder
/// Sends an activity to the sender of the incoming activity.
/// </summary>
/// <param name="activity">The activity to send.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the activity is successfully sent, the task result contains
/// a <see cref="ResourceResponse"/> object containing the ID that the receiving
@ -165,7 +180,8 @@ namespace Microsoft.Bot.Builder
/// Sends a set of activities to the sender of the incoming activity.
/// </summary>
/// <param name="activities">The activities to send.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the activities are successfully sent, the task result contains
/// an array of <see cref="ResourceResponse"/> objects containing the IDs that
@ -181,14 +197,15 @@ namespace Microsoft.Bot.Builder
/// Replaces an existing activity.
/// </summary>
/// <param name="activity">New replacement activity.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the activity is successfully sent, the task result contains
/// a <see cref="ResourceResponse"/> object containing the ID that the receiving
/// channel assigned to the activity.
/// <para>Before calling this, set the ID of the replacement activity to the ID
/// of the activity to replace.</para>
/// <para>Not all channels support this operation. Channels that don't, may throw an exception.</para></remarks>
/// <para>Not all channels support this operation. For channels that don't, this call may throw an exception.</para></remarks>
/// <seealso cref="OnUpdateActivity(UpdateActivityHandler)"/>
/// <seealso cref="SendActivitiesAsync(IActivity[], CancellationToken)"/>
/// <seealso cref="DeleteActivityAsync(ConversationReference, CancellationToken)"/>
@ -198,9 +215,10 @@ namespace Microsoft.Bot.Builder
/// Deletes an existing activity.
/// </summary>
/// <param name="activityId">The ID of the activity to delete.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>Not all channels support this operation. Channels that don't, may throw an exception.</remarks>
/// <para>Not all channels support this operation. For channels that don\'t, this call may throw an exception.</para>
/// <seealso cref="OnDeleteActivity(DeleteActivityHandler)"/>
/// <seealso cref="DeleteActivityAsync(ConversationReference, CancellationToken)"/>
/// <seealso cref="SendActivitiesAsync(IActivity[], CancellationToken)"/>
@ -211,11 +229,12 @@ namespace Microsoft.Bot.Builder
/// Deletes an existing activity.
/// </summary>
/// <param name="conversationReference">The conversation containing the activity to delete.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>The conversation reference's <see cref="ConversationReference.ActivityId"/>
/// indicates the activity in the conversation to delete.
/// <para>Not all channels support this operation. Channels that don't, may throw an exception.</para></remarks>
/// <para>Not all channels support this operation. For channels that don't, this call may throw an exception.</para></remarks>
/// <seealso cref="OnDeleteActivity(DeleteActivityHandler)"/>
/// <seealso cref="DeleteActivityAsync(string, CancellationToken)"/>
/// <seealso cref="SendActivitiesAsync(IActivity[], CancellationToken)"/>
@ -228,7 +247,7 @@ namespace Microsoft.Bot.Builder
/// <param name="handler">The handler to add to the context object.</param>
/// <returns>The updated context object.</returns>
/// <remarks>When the context's <see cref="SendActivityAsync(IActivity, CancellationToken)"/>
/// or <see cref="SendActivitiesAsync(IActivity[], CancellationToken)"/> methods are called,
/// or <see cref="SendActivitiesAsync(IActivity[], CancellationToken)"/> method is called,
/// the adapter calls the registered handlers in the order in which they were
/// added to the context object.
/// </remarks>

Просмотреть файл

@ -9,7 +9,7 @@ using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.TraceExtensions
{
/// <summary>
/// Contains methods for woring with <see cref="ITurnContext"/> objects.
/// Contains methods for working with <see cref="ITurnContext"/> objects.
/// </summary>
public static class ITurnContextExtensions
{
@ -26,7 +26,7 @@ namespace Microsoft.Bot.Builder.TraceExtensions
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>If the adapter is being hosted in the Emulator, the task result contains
/// a <see cref="ResourceResponse"/> object with the original trace activity's ID; otherwise,
/// it containsa <see cref="ResourceResponse"/> object containing the ID that the receiving
/// it contains a <see cref="ResourceResponse"/> object containing the ID that the receiving
/// channel assigned to the activity.</remarks>
public static Task<ResourceResponse> TraceActivityAsync(this ITurnContext turnContext, string name, object value = null, string valueType = null, [CallerMemberName] string label = null, CancellationToken cancellationToken = default(CancellationToken))
{

Просмотреть файл

@ -39,7 +39,7 @@ namespace Microsoft.Bot.Builder.Integration
public IChannelProvider ChannelProvider { get; set; }
/// <summary>
/// Gets or sets an error handler to use to catche exceptions in the middleware or application.
/// Gets or sets an error handler to use to catch exceptions in the middleware or application.
/// </summary>
/// <value>The error handler.</value>
public Func<ITurnContext, Exception, Task> OnTurnError { get; set; }

Просмотреть файл

@ -32,7 +32,7 @@ namespace Microsoft.Bot.Builder.Integration
/// <summary>
/// Sends a proactive message to a conversation.
/// </summary>
/// <param name="botId">The application ID of the bot. This paramter is ignored in
/// <param name="botId">The application ID of the bot. This parameter is ignored in
/// single tenant the Adapters (Console, Test, etc) but is critical to the BotFrameworkAdapter
/// which is multi-tenant aware. </param>
/// <param name="reference">A reference to the conversation to continue.</param>

Просмотреть файл

@ -14,7 +14,7 @@ namespace Microsoft.Bot.Builder
/// The memory transcript store stores transcripts in volatile memory in a Dictionary.
/// </summary>
/// <remarks>
/// Because this uses an unbounded volitile dictionary this should only be used for unit tests or non-production environments.
/// Because this uses an unbounded volatile dictionary this should only be used for unit tests or non-production environments.
/// </remarks>
public class MemoryTranscriptStore : ITranscriptStore
{

Просмотреть файл

@ -6,14 +6,17 @@ using System;
namespace Microsoft.Bot.Builder
{
/// <summary>
/// Handles persistence of a conversation state object using the conversation.Id and from.Id part of an activity.
/// Defines a state management object for private conversation state.
/// </summary>
/// <remarks>
/// Private conversation state is scoped to both the specific conversation and to that specific user.
/// </remarks>
public class PrivateConversationState : BotState
{
/// <summary>
/// Initializes a new instance of the <see cref="PrivateConversationState"/> class.
/// </summary>
/// <param name="storage">The storage provider to use.</param>
/// <param name="storage">The storage layer to use.</param>
public PrivateConversationState(IStorage storage)
: base(storage, nameof(PrivateConversationState))
{
@ -24,6 +27,16 @@ namespace Microsoft.Bot.Builder
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <returns>The storage key.</returns>
/// <remarks>
/// Private conversation state includes the channel ID, conversation ID, and user ID as part
/// of its storage key.
/// </remarks>
/// <exception cref="ArgumentNullException">The <see cref="ITurnContext.Activity"/> for the
/// current turn is missing <see cref="Schema.Activity.ChannelId"/>,
/// <see cref="Schema.Activity.Conversation"/>, or
/// <see cref="Schema.Activity.From"/> information; or
/// the conversation's or sender's <see cref="Schema.ConversationAccount.Id"/> is missing.
/// </exception>
protected override string GetStorageKey(ITurnContext turnContext)
{
var channelId = turnContext.Activity.ChannelId ?? throw new ArgumentNullException("invalid activity-missing channelId");

Просмотреть файл

@ -10,7 +10,7 @@ namespace Microsoft.Bot.Builder
{
/// <summary>
/// When added, this middleware will send typing activities back to the user when a Message activity
/// is receieved to let them know that the bot has receieved the message and is working on the response.
/// is received to let them know that the bot has received the message and is working on the response.
/// You can specify a delay in milliseconds before the first typing activity is sent and then a frequency,
/// also in milliseconds which determines how often another typing activity is sent. Typing activities
/// will continue to be sent until your bot sends another message back to the user.
@ -42,7 +42,7 @@ namespace Microsoft.Bot.Builder
}
/// <summary>
/// Processess an incoming activity.
/// Processes an incoming activity.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="next">The delegate to call to continue the bot middleware pipeline.</param>
@ -64,7 +64,7 @@ namespace Microsoft.Bot.Builder
cts = new CancellationTokenSource();
cancellationToken.Register(() => cts.Cancel());
// do not await task - we want this to run in thw background and we wil cancel it when its done
// 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);
}

Просмотреть файл

@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder
{
/// <summary>
/// Middleware to patch mention Entities from Skype since they don't conform to expected values.
/// Bots that interact with Skype should use this middleware if mentions are used.
/// </summary>
/// <description>
/// A Skype mention "text" field is of the format:
/// &lt;at id=\"28:2bc5b54d-5d48-4ff1-bd25-03dcbb5ce918\">botname&lt;/at&gt;
/// But Activity.Text doesn't contain those tags and RemoveMentionText can't remove
/// the entity from Activity.Text.
/// This will remove the &lt;at&gt; nodes, leaving just the name.
/// </description>
public class SkypeMentionNormalizeMiddleware : IMiddleware
{
/// <summary>
/// Initializes a new instance of the <see cref="SkypeMentionNormalizeMiddleware"/> class.
/// </summary>
public SkypeMentionNormalizeMiddleware()
{
}
public static void NormalizeSkypMentionText(Activity activity)
{
if (activity.ChannelId == Channels.Skype && activity.Type == ActivityTypes.Message)
{
foreach (var entity in activity.Entities)
{
if (entity.Type == "mention")
{
string text = (string)entity.Properties["text"];
var mentionNameMatch = Regex.Match(text, @"(?<=<at.*>)(.*?)(?=<\/at>)", RegexOptions.IgnoreCase);
if (mentionNameMatch.Success)
{
entity.Properties["text"] = mentionNameMatch.Value;
}
}
}
}
}
/// <summary>
/// Middleware implementation which corrects Enity.Mention.Text to a value RemoveMentionText can work with.
/// </summary>
/// <param name="turnContext">turn context.</param>
/// <param name="next">next middleware.</param>
/// <param name="cancellationToken">cancellationToken.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
{
NormalizeSkypMentionText(turnContext.Activity);
await next(cancellationToken).ConfigureAwait(false);
}
}
}

Просмотреть файл

@ -35,7 +35,9 @@ namespace Microsoft.Bot.Builder
/// <summary>
/// Gets the currently configured <see cref="IBotTelemetryClient"/> that logs the QnaMessage event.
/// </summary>
/// <value>The <see cref="IBotTelemetryClient"/> being used to log events.</value>
/// <value>
/// The <see cref="IBotTelemetryClient"/> being used to log events.
/// </value>
public IBotTelemetryClient TelemetryClient { get; }
/// <summary>

Просмотреть файл

@ -300,9 +300,12 @@ namespace Microsoft.Bot.Builder
/// channel assigned to the activity.
/// <para>Before calling this, set the ID of the replacement activity to the ID
/// of the activity to replace.</para></remarks>
public async Task<ResourceResponse> UpdateActivityAsync(IActivity activity, CancellationToken cancellationToken = default(CancellationToken))
public async Task<ResourceResponse> UpdateActivityAsync(IActivity activity, CancellationToken cancellationToken = default)
{
Activity a = (Activity)activity;
BotAssert.ActivityNotNull(activity);
var conversationReference = Activity.GetConversationReference();
var a = activity.ApplyConversationReference(conversationReference);
async Task<ResourceResponse> ActuallyUpdateStuff()
{

Просмотреть файл

@ -6,14 +6,18 @@ using System;
namespace Microsoft.Bot.Builder
{
/// <summary>
/// Handles persistence of a user state object using the user ID as part of the key.
/// Defines a state management object for user state.
/// </summary>
/// <remarks>
/// User state is available in any turn that the bot is conversing with that user on that
/// channel, regardless of the conversation.
/// </remarks>
public class UserState : BotState
{
/// <summary>
/// Initializes a new instance of the <see cref="UserState"/> class.
/// </summary>
/// <param name="storage">The storage provider to use.</param>
/// <param name="storage">The storage layer to use.</param>
public UserState(IStorage storage)
: base(storage, nameof(UserState))
{
@ -24,6 +28,13 @@ namespace Microsoft.Bot.Builder
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <returns>The storage key.</returns>
/// <remarks>
/// User state includes the channel ID and user ID as part of its storage key.
/// </remarks>
/// <exception cref="ArgumentNullException">The <see cref="ITurnContext.Activity"/> for the
/// current turn is missing <see cref="Schema.Activity.ChannelId"/> or
/// <see cref="Schema.Activity.From"/> information, or the sender's
/// <see cref="Schema.ConversationAccount.Id"/> is missing.</exception>
protected override string GetStorageKey(ITurnContext turnContext)
{
var channelId = turnContext.Activity.ChannelId ?? throw new ArgumentNullException("invalid activity-missing channelId");

Просмотреть файл

@ -0,0 +1,73 @@
// <auto-generated>
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
// </auto-generated>
namespace Microsoft.Bot.Connector.Teams
{
using Microsoft.Rest;
using Microsoft.Bot.Schema.Teams;
using Newtonsoft.Json;
/// <summary>
/// The Bot Connector REST API extension for Microsoft Teams allows your
/// bot to perform extended operations on to Microsoft Teams channel
/// configured in the
/// [Bot Framework Developer Portal](https://dev.botframework.com). The
/// Connector service uses industry-standard REST and JSON over HTTPS.
///
/// Client libraries for this REST API are available. See below for a list.
///
///
///
/// Authentication for both the Bot Connector and Bot State REST APIs is
/// accomplished with JWT Bearer tokens, and is
/// described in detail in the [Connector
/// Authentication](https://docs.botframework.com/en-us/restapi/authentication)
/// document.
///
/// # Client Libraries for the Bot Connector REST API
///
/// * [Bot Builder for
/// C#](https://docs.botframework.com/en-us/csharp/builder/sdkreference/)
/// * [Bot Builder for
/// Node.js](https://docs.botframework.com/en-us/node/builder/overview/)
///
/// © 2016 Microsoft
/// </summary>
public partial interface ITeamsConnectorClient : System.IDisposable
{
/// <summary>
/// The base URI of the service.
/// </summary>
System.Uri BaseUri { get; set; }
/// <summary>
/// Gets or sets json serialization settings.
/// </summary>
JsonSerializerSettings SerializationSettings { get; }
/// <summary>
/// Gets or sets json deserialization settings.
/// </summary>
JsonSerializerSettings DeserializationSettings { get; }
/// <summary>
/// Subscription credentials which uniquely identify client
/// subscription.
/// </summary>
ServiceClientCredentials Credentials { get; }
/// <summary>
/// Gets the ITeamsOperations.
/// </summary>
ITeamsOperations Teams { get; }
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// <auto-generated>
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
// </auto-generated>
namespace Microsoft.Bot.Connector.Teams
{
using Microsoft.Rest;
using Microsoft.Bot.Schema.Teams;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// TeamsOperations operations.
/// </summary>
public partial interface ITeamsOperations
{
/// <summary>
/// Fetches channel list for a given team
/// </summary>
/// <remarks>
/// Fetch the channel list.
/// </remarks>
/// <param name='teamId'>
/// Team Id
/// </param>
/// <param name='customHeaders'>
/// The headers that will be added to request.
/// </param>
/// <param name='cancellationToken'>
/// The cancellation token.
/// </param>
/// <exception cref="Microsoft.Rest.HttpOperationException">
/// Thrown when the operation returned an invalid status code
/// </exception>
/// <exception cref="Microsoft.Rest.SerializationException">
/// Thrown when unable to deserialize the response
/// </exception>
/// <exception cref="Microsoft.Rest.ValidationException">
/// Thrown when a required parameter is null
/// </exception>
Task<HttpOperationResponse<ConversationList>> FetchChannelListWithHttpMessagesAsync(string teamId, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Fetches details related to a team
/// </summary>
/// <remarks>
/// Fetch details for a team
/// </remarks>
/// <param name='teamId'>
/// Team Id
/// </param>
/// <param name='customHeaders'>
/// The headers that will be added to request.
/// </param>
/// <param name='cancellationToken'>
/// The cancellation token.
/// </param>
/// <exception cref="Microsoft.Rest.HttpOperationException">
/// Thrown when the operation returned an invalid status code
/// </exception>
/// <exception cref="Microsoft.Rest.SerializationException">
/// Thrown when unable to deserialize the response
/// </exception>
/// <exception cref="Microsoft.Rest.ValidationException">
/// Thrown when a required parameter is null
/// </exception>
Task<HttpOperationResponse<TeamDetails>> FetchTeamDetailsWithHttpMessagesAsync(string teamId, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken));
}
}

Просмотреть файл

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version Condition=" '$(PackageVersion)' == '' ">4.0.0-local</Version>
<Version Condition=" '$(PackageVersion)' != '' ">$(PackageVersion)</Version>
<PackageVersion Condition=" '$(PackageVersion)' == '' ">4.0.0-local</PackageVersion>
<PackageVersion Condition=" '$(PackageVersion)' != '' ">$(PackageVersion)</PackageVersion>
<Configurations>Debug;Release;Debug - NuGet Packages</Configurations>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>..\..\build\35MSSharedLib1024.snk</AssemblyOriginatorKeyFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Microsoft.Bot.Connector.Teams</PackageId>
<Description>Library for building bots using Microsoft Bot Framework</Description>
<Summary>Library for building bots using Microsoft Bot Framework</Summary>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.10" />
<PackageReference Include="Microsoft.Bot.Schema.Teams" Condition=" '$(PackageVersion)' == '' " Version="4.0.0-local" />
<PackageReference Include="Microsoft.Bot.Schema.Teams" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
<!-- This may move to the root level dir.props file at some point. -->
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Bot.Schema.Teams\Microsoft.Bot.Schema.Teams.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,306 @@
// <auto-generated>
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
// </auto-generated>
namespace Microsoft.Bot.Connector.Teams
{
using Microsoft.Rest;
using Microsoft.Rest.Serialization;
using Microsoft.Bot.Schema.Teams;
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
/// <summary>
/// The Bot Connector REST API extension for Microsoft Teams allows your
/// bot to perform extended operations on to Microsoft Teams channel
/// configured in the
/// [Bot Framework Developer Portal](https://dev.botframework.com). The
/// Connector service uses industry-standard REST and JSON over HTTPS.
///
/// Client libraries for this REST API are available. See below for a list.
///
///
///
/// Authentication for both the Bot Connector and Bot State REST APIs is
/// accomplished with JWT Bearer tokens, and is
/// described in detail in the [Connector
/// Authentication](https://docs.botframework.com/en-us/restapi/authentication)
/// document.
///
/// # Client Libraries for the Bot Connector REST API
///
/// * [Bot Builder for
/// C#](https://docs.botframework.com/en-us/csharp/builder/sdkreference/)
/// * [Bot Builder for
/// Node.js](https://docs.botframework.com/en-us/node/builder/overview/)
///
/// © 2016 Microsoft
/// </summary>
public partial class TeamsConnectorClient : ServiceClient<TeamsConnectorClient>, ITeamsConnectorClient
{
/// <summary>
/// The base URI of the service.
/// </summary>
public System.Uri BaseUri { get; set; }
/// <summary>
/// Gets or sets json serialization settings.
/// </summary>
public JsonSerializerSettings SerializationSettings { get; private set; }
/// <summary>
/// Gets or sets json deserialization settings.
/// </summary>
public JsonSerializerSettings DeserializationSettings { get; private set; }
/// <summary>
/// Subscription credentials which uniquely identify client subscription.
/// </summary>
public ServiceClientCredentials Credentials { get; private set; }
/// <summary>
/// Gets the ITeamsOperations.
/// </summary>
public virtual ITeamsOperations Teams { get; private set; }
/// <summary>
/// Initializes a new instance of the TeamsConnectorClient class.
/// </summary>
/// <param name='handlers'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
protected TeamsConnectorClient(params DelegatingHandler[] handlers) : base(handlers)
{
Initialize();
}
/// <summary>
/// Initializes a new instance of the TeamsConnectorClient class.
/// </summary>
/// <param name='rootHandler'>
/// Optional. The http client handler used to handle http transport.
/// </param>
/// <param name='handlers'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
protected TeamsConnectorClient(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : base(rootHandler, handlers)
{
Initialize();
}
/// <summary>
/// Initializes a new instance of the TeamsConnectorClient class.
/// </summary>
/// <param name='baseUri'>
/// Optional. The base URI of the service.
/// </param>
/// <param name='handlers'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown when a required parameter is null
/// </exception>
protected TeamsConnectorClient(System.Uri baseUri, params DelegatingHandler[] handlers) : this(handlers)
{
if (baseUri == null)
{
throw new System.ArgumentNullException("baseUri");
}
BaseUri = baseUri;
}
/// <summary>
/// Initializes a new instance of the TeamsConnectorClient class.
/// </summary>
/// <param name='baseUri'>
/// Optional. The base URI of the service.
/// </param>
/// <param name='rootHandler'>
/// Optional. The http client handler used to handle http transport.
/// </param>
/// <param name='handlers'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown when a required parameter is null
/// </exception>
protected TeamsConnectorClient(System.Uri baseUri, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers)
{
if (baseUri == null)
{
throw new System.ArgumentNullException("baseUri");
}
BaseUri = baseUri;
}
/// <summary>
/// Initializes a new instance of the TeamsConnectorClient class.
/// </summary>
/// <param name='credentials'>
/// Required. Subscription credentials which uniquely identify client subscription.
/// </param>
/// <param name='handlers'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown when a required parameter is null
/// </exception>
public TeamsConnectorClient(ServiceClientCredentials credentials, params DelegatingHandler[] handlers) : this(handlers)
{
if (credentials == null)
{
throw new System.ArgumentNullException("credentials");
}
Credentials = credentials;
if (Credentials != null)
{
Credentials.InitializeServiceClient(this);
}
}
/// <summary>
/// Initializes a new instance of the TeamsConnectorClient class.
/// </summary>
/// <param name='credentials'>
/// Required. Subscription credentials which uniquely identify client subscription.
/// </param>
/// <param name='rootHandler'>
/// Optional. The http client handler used to handle http transport.
/// </param>
/// <param name='handlers'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown when a required parameter is null
/// </exception>
public TeamsConnectorClient(ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers)
{
if (credentials == null)
{
throw new System.ArgumentNullException("credentials");
}
Credentials = credentials;
if (Credentials != null)
{
Credentials.InitializeServiceClient(this);
}
}
/// <summary>
/// Initializes a new instance of the TeamsConnectorClient class.
/// </summary>
/// <param name='baseUri'>
/// Optional. The base URI of the service.
/// </param>
/// <param name='credentials'>
/// Required. Subscription credentials which uniquely identify client subscription.
/// </param>
/// <param name='handlers'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown when a required parameter is null
/// </exception>
public TeamsConnectorClient(System.Uri baseUri, ServiceClientCredentials credentials, params DelegatingHandler[] handlers) : this(handlers)
{
if (baseUri == null)
{
throw new System.ArgumentNullException("baseUri");
}
if (credentials == null)
{
throw new System.ArgumentNullException("credentials");
}
BaseUri = baseUri;
Credentials = credentials;
if (Credentials != null)
{
Credentials.InitializeServiceClient(this);
}
}
/// <summary>
/// Initializes a new instance of the TeamsConnectorClient class.
/// </summary>
/// <param name='baseUri'>
/// Optional. The base URI of the service.
/// </param>
/// <param name='credentials'>
/// Required. Subscription credentials which uniquely identify client subscription.
/// </param>
/// <param name='rootHandler'>
/// Optional. The http client handler used to handle http transport.
/// </param>
/// <param name='handlers'>
/// Optional. The delegating handlers to add to the http client pipeline.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown when a required parameter is null
/// </exception>
public TeamsConnectorClient(System.Uri baseUri, ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers)
{
if (baseUri == null)
{
throw new System.ArgumentNullException("baseUri");
}
if (credentials == null)
{
throw new System.ArgumentNullException("credentials");
}
BaseUri = baseUri;
Credentials = credentials;
if (Credentials != null)
{
Credentials.InitializeServiceClient(this);
}
}
/// <summary>
/// An optional partial-method to perform custom initialization.
///</summary>
partial void CustomInitialize();
/// <summary>
/// Initializes client properties.
/// </summary>
private void Initialize()
{
Teams = new TeamsOperations(this);
BaseUri = new System.Uri("https://api.botframework.com");
SerializationSettings = new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize,
ContractResolver = new ReadOnlyJsonContractResolver(),
Converters = new List<JsonConverter>
{
new Iso8601TimeSpanConverter()
}
};
DeserializationSettings = new JsonSerializerSettings
{
DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize,
ContractResolver = new ReadOnlyJsonContractResolver(),
Converters = new List<JsonConverter>
{
new Iso8601TimeSpanConverter()
}
};
CustomInitialize();
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше