Skip to content

Commit 1958a3f

Browse files
committed
Merge branch 'develop'
2 parents 6fe9ec0 + f5900a6 commit 1958a3f

File tree

194 files changed

+11451
-5009
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

194 files changed

+11451
-5009
lines changed

CSharp/Documentation/Forms.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@
350350
///
351351
/// Attribute | Purpose
352352
/// ----------| -------
353-
/// [Describe] | Change how a field or a value is shown in text.
353+
/// [Describe] | Change how a field or a value is shown in a template as a choice or in a card.
354354
/// [Numeric] | Provide limits on the values accepted in a numeric field.
355355
/// [Optional]| Mark a field as optional which means that one choice is not to supply a value.
356356
/// [Pattern] | Define a regular expression to validate a string field.
@@ -550,9 +550,12 @@
550550
///
551551
/// \subsection logic Adding Business Logic
552552
/// Sometimes there are complex interdependencies between fields or you need to
553-
/// add logic to setting or getting a value. Here we want to add support
554-
/// for including all toppings except some of them. To do this, we add a validation function which complements
555-
/// the list if the Everything enum value is included:
553+
/// add logic to setting or getting a value. A validation function allows manipulating the state and also returns a ValidateResult which can:
554+
/// * Return a feedback string describing why a value is not valid.
555+
/// * Return a transformed value.
556+
/// * Return a set of choices for clarifying a value.
557+
///
558+
/// Here we want to add support for including all toppings except some of them. To do this we complement the list if the Everything enum value is included:
556559
/// \dontinclude AnnotatedSandwichBot/AnnotatedSandwich.cs
557560
/// \skip nameof(Toppings)
558561
/// \until Message
@@ -793,13 +796,14 @@
793796
///
794797
/// %Extensions that are found in a property description as peers to the "type" property of a JSON Schema.
795798
/// * `DateTime:bool` -- Marks a field as being a DateTime field.
796-
/// * `Describe:string` -- Description of a field as described in <see cref="DescribeAttribute"/>.
799+
/// * `Describe:string|object` -- Description of a field as described in <see cref="DescribeAttribute"/>.
797800
/// * `Terms:[string,...]` -- Regular expressions for matching a field value as described in <see cref="TermsAttribute"/>.
798801
/// * `MaxPhrase:int` -- This will run your terms through <see cref="Language.GenerateTerms(string, int)"/> to expand them.
799-
/// * `Values:{ string: {Describe:string, Terms:[string, ...], MaxPhrase}, ...}` -- The string must be found in the types "enum" and this allows you to override the automatically generated descriptions and terms. If MaxPhrase is specified the terms are passed through <see cref="Language.GenerateTerms(string, int)"/>.
802+
/// * `Values:{ string: {Describe:string|object, Terms:[string, ...], MaxPhrase}, ...}` -- The string must be found in the types "enum" and this allows you to override the automatically generated descriptions and terms. If MaxPhrase is specified the terms are passed through <see cref="Language.GenerateTerms(string, int)"/>.
800803
/// * `Active:script` -- C# script with arguments (JObject state)->bool to test to see if field/message/confirm is active.
801804
/// * `Validate:script` -- C# script with arguments (JObject state, object value)->ValidateResult for validating a field value.
802805
/// * `Define:script` -- C# script with arguments (JObject state, Field&lt;JObject&gt; field) for dynamically defining a field.
806+
/// * `Next:script` -- C# script with arguments (object value, JObject state) for determining the next step after filling in a field.
803807
/// * `Before:[confirm|message, ...]` -- Messages or confirmations before the containing field.
804808
/// * `After:[confirm|message, ...]` -- Messages or confirmations after the containing field.
805809
/// * `{Confirm:script|[string, ...], ...templateArgs}` -- With Before/After define a confirmation through either C# script with argument (JObject state) or through a set of patterns that will be randomly selected with optional template arguments.

CSharp/Documentation/Routing.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ When your %bot receives a message Activity it will want to respond.
108108
newMessage.Conversation = conversation;
109109
newMessage.Recipient = userAccount;
110110
newMessage.Text = "Yo yo yo!";
111-
await connector.Conversations.SendToConversation((Activity)newMessage);
111+
await connector.Conversations.SendToConversationAsync((Activity)newMessage);
112112
~~~
113113
114114
\subsection multiplereplies Multiple replies
@@ -145,8 +145,7 @@ To initialize a **ConnectorClient** you use ServiceUrl persisted from previous m
145145
message.Conversation = new ConversationAccount(id: conversationId.Id);
146146
message.Text = "Hello";
147147
message.Locale = "en-Us";
148-
149-
await connector.Conversations.SendToConversation((Activity)message);
148+
await connector.Conversations.SendToConversationAsync((Activity)message);
150149
~~~
151150
152151
@@ -171,8 +170,8 @@ To initialize a **ConnectorClient** you use ServiceUrl persisted from previous m
171170
message.ChannelId = incomingMessage.ChannelId;
172171
message.Text = "Hey, what's up everyone?";
173172
message.Locale = "en-Us";
174-
await connector.Conversations.SendToConversation((Activity)message);
173+
await connector.Conversations.SendToConversationAsync((Activity)message);
175174
~~~
176175
177176
**/
178-
}
177+
}

CSharp/Library/Dialogs/BotToUser.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,24 +113,28 @@ private static string ButtonsToText(IList<Attachment> attachments)
113113
var builder = new StringBuilder();
114114
if (cardAttachments != null && cardAttachments.Any())
115115
{
116-
builder.AppendLine();
116+
builder.AppendLine();
117117
foreach (var attachment in cardAttachments)
118118
{
119119
string type = attachment.ContentType.Split('.').Last();
120-
if (type == "hero" || type == "thumbnail")
120+
if (type == "hero" || type == "thumbnail")
121121
{
122122
var card = (HeroCard)attachment.Content;
123+
if (!string.IsNullOrEmpty(card.Title))
124+
{
125+
builder.AppendLine(card.Title);
126+
}
127+
if (!string.IsNullOrEmpty(card.Subtitle))
128+
{
129+
builder.AppendLine(card.Subtitle);
130+
}
123131
if (!string.IsNullOrEmpty(card.Text))
124132
{
125133
builder.AppendLine(card.Text);
126134
}
127-
foreach(var button in card.Buttons)
135+
if (card.Buttons != null)
128136
{
129-
if (!string.IsNullOrEmpty(button.Value))
130-
{
131-
builder.AppendLine($"{ button.Value}. { button.Title}");
132-
}
133-
else
137+
foreach (var button in card.Buttons)
134138
{
135139
builder.AppendLine($"* {button.Title}");
136140
}

CSharp/Library/Dialogs/DialogModule.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ protected override void Load(ContainerBuilder builder)
8181
.AsSelf()
8282
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
8383

84+
// make the resumption cookie available for the lifetime scope
85+
86+
builder
87+
.RegisterType<ResumptionCookie>()
88+
.AsSelf()
89+
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
90+
8491
// components not marked as [Serializable]
8592
builder
8693
.RegisterType<MicrosoftAppCredentials>()
@@ -92,6 +99,11 @@ protected override void Load(ContainerBuilder builder)
9299
.As<IBotIdResolver>()
93100
.SingleInstance();
94101

102+
builder
103+
.RegisterType<LocalMutualExclusion<ResumptionCookie>>()
104+
.As<IScope<ResumptionCookie>>()
105+
.SingleInstance();
106+
95107
builder
96108
.Register(c => new ConnectorClientFactory(c.Resolve<IMessageActivity>(), c.Resolve<MicrosoftAppCredentials>()))
97109
.As<IConnectorClientFactory>()
@@ -174,6 +186,7 @@ protected override void Load(ContainerBuilder builder)
174186
};
175187

176188
IPostToBot outer = new PersistentDialogTask(makeInner, cc.Resolve<IMessageActivity>(), cc.Resolve<IConnectorClient>(), cc.Resolve<IBotToUser>(), cc.Resolve<IBotData>());
189+
outer = new SerializingDialogTask(outer, cc.Resolve<ResumptionCookie>(), c.Resolve<IScope<ResumptionCookie>>());
177190
outer = new PostUnhandledExceptionToUserTask(outer, cc.Resolve<IBotToUser>(), cc.Resolve<ResourceManager>(), cc.Resolve<TraceListener>());
178191
return outer;
179192
})

CSharp/Library/Dialogs/DialogTask.cs

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ async Task IDialogStack.Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T
212212
IDialogStack stack = this;
213213
stack.Call(child, resume);
214214
await stack.PollAsync(token);
215-
await (this as IPostToBot).PostAsync(item, token);
215+
IPostToBot postToBot = this;
216+
await postToBot.PostAsync(item, token);
216217
}
217218

218219
void IDialogStack.Done<R>(R value)
@@ -231,27 +232,14 @@ void IDialogStack.Wait<R>(ResumeAfter<R> resume)
231232
}
232233

233234
async Task IDialogStack.PollAsync(CancellationToken token)
234-
{
235-
await this.fiber.PollAsync(this);
236-
237-
// this line will throw an error if the code does not schedule the next callback
238-
// to wait for the next message sent from the user to the bot.
239-
this.fiber.Wait.ValidateNeed(Need.Wait);
240-
}
241-
242-
void IDialogStack.Reset()
243-
{
244-
this.store.Reset();
245-
this.store.Flush();
246-
this.fiber.Reset();
247-
}
248-
249-
async Task IPostToBot.PostAsync<T>(T item, CancellationToken token)
250235
{
251236
try
252237
{
253-
this.fiber.Post(item);
254238
await this.fiber.PollAsync(this);
239+
240+
// this line will throw an error if the code does not schedule the next callback
241+
// to wait for the next message sent from the user to the bot.
242+
this.fiber.Wait.ValidateNeed(Need.Wait);
255243
}
256244
catch
257245
{
@@ -261,9 +249,23 @@ async Task IPostToBot.PostAsync<T>(T item, CancellationToken token)
261249
finally
262250
{
263251
this.store.Save(this.fiber);
264-
this.store.Flush();
252+
this.store.Flush();
265253
}
266254
}
255+
256+
void IDialogStack.Reset()
257+
{
258+
this.store.Reset();
259+
this.store.Flush();
260+
this.fiber.Reset();
261+
}
262+
263+
async Task IPostToBot.PostAsync<T>(T item, CancellationToken token)
264+
{
265+
this.fiber.Post(item);
266+
IDialogStack stack = this;
267+
await stack.PollAsync(token);
268+
}
267269
}
268270

269271
public sealed class ReactiveDialogTask : IPostToBot
@@ -415,6 +417,28 @@ async Task IPostToBot.PostAsync<T>(T item, CancellationToken token)
415417
}
416418
}
417419

420+
public sealed class SerializingDialogTask : IPostToBot
421+
{
422+
private readonly IPostToBot inner;
423+
private readonly ResumptionCookie cookie;
424+
private readonly IScope<ResumptionCookie> scopeForCookie;
425+
426+
public SerializingDialogTask(IPostToBot inner, ResumptionCookie cookie, IScope<ResumptionCookie> scopeForCookie)
427+
{
428+
SetField.NotNull(out this.inner, nameof(inner), inner);
429+
SetField.NotNull(out this.cookie, nameof(cookie), cookie);
430+
SetField.NotNull(out this.scopeForCookie, nameof(scopeForCookie), scopeForCookie);
431+
}
432+
433+
async Task IPostToBot.PostAsync<T>(T item, CancellationToken token)
434+
{
435+
using (await this.scopeForCookie.WithScopeAsync(this.cookie, token))
436+
{
437+
await this.inner.PostAsync(item, token);
438+
}
439+
}
440+
}
441+
418442
public sealed class PostUnhandledExceptionToUserTask : IPostToBot
419443
{
420444
private readonly IPostToBot inner;

CSharp/Library/Dialogs/LuisDialog.cs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ public LuisIntentAttribute(string intentName)
7575
/// <returns>A task representing the completion of the intent processing.</returns>
7676
public delegate Task IntentHandler(IDialogContext context, LuisResult luisResult);
7777

78+
/// <summary>
79+
/// The handler for a LUIS intent.
80+
/// </summary>
81+
/// <param name="context">The dialog context.</param>
82+
/// <param name="message">The dialog message.</param>
83+
/// <param name="luisResult">The LUIS result.</param>
84+
/// <returns>A task representing the completion of the intent processing.</returns>
85+
public delegate Task IntentActivityHandler(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult luisResult);
86+
7887
/// <summary>
7988
/// An exception for invalid intent handlers.
8089
/// </summary>
@@ -121,7 +130,7 @@ public class LuisDialog<R> : IDialog<R>
121130

122131
/// <summary> Mapping from intent string to the appropriate handler. </summary>
123132
[NonSerialized]
124-
protected Dictionary<string, IntentHandler> handlerByIntent;
133+
protected Dictionary<string, IntentActivityHandler> handlerByIntent;
125134

126135
public ILuisService[] MakeServicesFromAttributes()
127136
{
@@ -178,23 +187,23 @@ protected virtual async Task MessageReceived(IDialogContext context, IAwaitable<
178187
{
179188
if (this.handlerByIntent == null)
180189
{
181-
this.handlerByIntent = new Dictionary<string, IntentHandler>(GetHandlersByIntent());
190+
this.handlerByIntent = new Dictionary<string, IntentActivityHandler>(GetHandlersByIntent());
182191
}
183192

184193
var message = await item;
185194
var messageText = await GetLuisQueryTextAsync(context, message);
186195
var tasks = this.services.Select(s => s.QueryAsync(messageText)).ToArray();
187196
var winner = this.BestResultFrom(await Task.WhenAll(tasks));
188197

189-
IntentHandler handler = null;
198+
IntentActivityHandler handler = null;
190199
if (winner == null || !this.handlerByIntent.TryGetValue(winner.BestIntent.Intent, out handler))
191200
{
192201
handler = this.handlerByIntent[string.Empty];
193202
}
194203

195204
if (handler != null)
196205
{
197-
await handler(context, winner?.Result);
206+
await handler(context, item, winner?.Result);
198207
}
199208
else
200209
{
@@ -208,7 +217,7 @@ protected virtual Task<string> GetLuisQueryTextAsync(IDialogContext context, IMe
208217
return Task.FromResult(message.Text);
209218
}
210219

211-
protected virtual IDictionary<string, IntentHandler> GetHandlersByIntent()
220+
protected virtual IDictionary<string, IntentActivityHandler> GetHandlersByIntent()
212221
{
213222
return LuisDialog.EnumerateHandlers(this).ToDictionary(kv => kv.Key, kv => kv.Value);
214223
}
@@ -221,17 +230,18 @@ internal static class LuisDialog
221230
/// </summary>
222231
/// <param name="dialog">The dialog.</param>
223232
/// <returns>An enumeration of handlers.</returns>
224-
public static IEnumerable<KeyValuePair<string, IntentHandler>> EnumerateHandlers(object dialog)
233+
public static IEnumerable<KeyValuePair<string, IntentActivityHandler>> EnumerateHandlers(object dialog)
225234
{
226235
var type = dialog.GetType();
227236
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
228237
foreach (var method in methods)
229238
{
230239
var intents = method.GetCustomAttributes<LuisIntentAttribute>(inherit: true).ToArray();
231-
Delegate created = null;
240+
IntentActivityHandler intentHandler = null;
241+
232242
try
233243
{
234-
created = Delegate.CreateDelegate(typeof(IntentHandler), dialog, method, throwOnBindFailure: false);
244+
intentHandler = (IntentActivityHandler)Delegate.CreateDelegate(typeof(IntentActivityHandler), dialog, method, throwOnBindFailure: false);
235245
}
236246
catch (ArgumentException)
237247
{
@@ -240,15 +250,35 @@ public static IEnumerable<KeyValuePair<string, IntentHandler>> EnumerateHandlers
240250
// https://github.com/Microsoft/BotBuilder/issues/435
241251
}
242252

243-
var intentHandler = (IntentHandler)created;
253+
// fall back for compatibility
254+
if (intentHandler == null)
255+
{
256+
try
257+
{
258+
var handler = (IntentHandler)Delegate.CreateDelegate(typeof(IntentHandler), dialog, method, throwOnBindFailure: false);
259+
260+
if (handler != null)
261+
{
262+
// thunk from new to old delegate type
263+
intentHandler = (context, message, result) => handler(context, result);
264+
}
265+
}
266+
catch (ArgumentException)
267+
{
268+
// "Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."
269+
// https://github.com/Microsoft/BotBuilder/issues/634
270+
// https://github.com/Microsoft/BotBuilder/issues/435
271+
}
272+
}
273+
244274
if (intentHandler != null)
245275
{
246276
var intentNames = intents.Select(i => i.IntentName).DefaultIfEmpty(method.Name);
247277

248278
foreach (var intentName in intentNames)
249279
{
250280
var key = string.IsNullOrWhiteSpace(intentName) ? string.Empty : intentName;
251-
yield return new KeyValuePair<string, IntentHandler>(intentName, intentHandler);
281+
yield return new KeyValuePair<string, IntentActivityHandler>(intentName, intentHandler);
252282
}
253283
}
254284
else

0 commit comments

Comments
 (0)