CmdPal: Add IFormContent2 support (#50178)

When you create an action in adaptive cards, you can assign an ID to
that action. That ID helps identify what the user activated. It might
_not_ be a part of the action JSON. Sometimes it comes through as a
separate piece of data.

If we wanted to be more technically correct (and we do), then our
`SubmitForm` should also accept an action ID.

This adds an update to `IFormContent` to do just that.
`IFormContent2` adds `SubmitAction` which is just `SubmitForm`, but with
an ID. If you don't manually implement `SubmitAction` in the next
version of the toolkit, we'll forward it along to the old `SubmitForm`.

Built it and ran the sample locally. 

Closes: _future work item_
This commit is contained in:
Mike Griese
2026-08-28 16:47:16 -05:00
committed by GitHub
parent 428044b477
commit 7bf87a308b
6 changed files with 51 additions and 3 deletions

View File

@@ -172,7 +172,9 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
var model = _formModel.Unsafe!;
if (model != null)
{
var result = model.SubmitForm(inputString, dataString);
var result = model is IFormContent2 form2
? form2.SubmitAction(action.Id, inputString, dataString)
: model.SubmitForm(inputString, dataString);
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
}
}

View File

@@ -88,6 +88,7 @@ functionality.
- [Addenda V: Extra content types](#addenda-v-extra-content-types)
- [Image content](#image-content)
- [Plain text content](#plain-text-content)
- [Addenda VI: Adaptive Card Actions](#addenda-vi-adaptive-card-actions)
- [Class diagram](#class-diagram)
- [Future considerations](#future-considerations)
- [Arbitrary parameters and arguments](#arbitrary-parameters-and-arguments)
@@ -2424,6 +2425,22 @@ interface IPlainTextContent requires IContent {
}
```
## Addenda VI: Adaptive Card Actions
Adaptive Cards supports setting multiple actions on a card. Those actions can be
identified by an `id` property on the action. That `id` is not necessarily
encoded in the JSON payload of the action.
For us to properly support the gammut of AC scenarios, we need to be able to
pass the `id` of the action back to the extension. This is a relatively simple
addition to the `IForm` interface.
```csharp
interface IFormContent2 requires IFormContent {
ICommandResult SubmitAction(String actionId, String inputs, String data);
}
```
## Class diagram
This is a diagram attempting to show the relationships between the various types we've defined for the SDK. Some elements are omitted for clarity. (Notably, `IconData` and `IPropChanged`, which are used in many places.)

View File

@@ -256,6 +256,7 @@ internal sealed partial class SampleContentForm : FormContent
"actions": [
{
"type": "Action.Submit",
"id": "submit-form",
"title": "Submit",
"data": {
"id": "1234567890"
@@ -277,6 +278,7 @@ internal sealed partial class SampleContentForm : FormContent
"actions": [
{
"type": "Action.Submit",
"id": "submit-comment",
"title": "OK"
}
]
@@ -368,6 +370,9 @@ internal sealed partial class SampleContentForm : FormContent
// Application.Current.GetService<ILocalSettingsService>().SaveSettingAsync("GlobalHotkey", formInput["hotkey"]?.ToString() ?? string.Empty);
return CommandResult.GoHome();
}
public override CommandResult SubmitAction(string actionId, string inputs, string data) =>
CommandResult.ShowToast($"Submitted action: {actionId}");
}
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]

View File

@@ -78,7 +78,7 @@ public abstract partial class CommandProvider :
/// <returns>an array of objects that implement all the leaf interfaces we support</returns>
public object[] GetApiExtensionStubs()
{
return [new SupportCommandsWithProperties()];
return [new SupportCommandsWithProperties(), new SupportFormActions()];
}
/// <summary>
@@ -90,4 +90,21 @@ public abstract partial class CommandProvider :
{
public IDictionary<string, object>? GetProperties() => null;
}
private sealed partial class SupportFormActions : IFormContent2
{
public string TemplateJson => string.Empty;
public string DataJson => string.Empty;
public string StateJson => string.Empty;
public ICommandResult SubmitForm(string inputs, string data) => CommandResult.KeepOpen();
public ICommandResult SubmitAction(string actionId, string inputs, string data) => CommandResult.KeepOpen();
#pragma warning disable CS0067
public event Windows.Foundation.TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;
#pragma warning restore CS0067
}
}

View File

@@ -4,7 +4,7 @@
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class FormContent : BaseObservable, IFormContent
public partial class FormContent : BaseObservable, IFormContent2
{
public virtual string DataJson { get; set => SetProperty(ref field, value); } = string.Empty;
@@ -15,4 +15,6 @@ public partial class FormContent : BaseObservable, IFormContent
public virtual ICommandResult SubmitForm(string inputs, string data) => SubmitForm(inputs);
public virtual ICommandResult SubmitForm(string inputs) => CommandResult.KeepOpen();
public virtual ICommandResult SubmitAction(string actionId, string inputs, string data) => SubmitForm(inputs, data);
}

View File

@@ -490,5 +490,10 @@ namespace Microsoft.CommandPalette.Extensions
Boolean WrapWords { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFormContent2 requires IFormContent {
ICommandResult SubmitAction(String actionId, String inputs, String data);
}
}