Kurzübersicht zur .NET-Migration.NET migration quick reference

gilt für: SDK v4APPLIES TO: SDK v4

Mit Version 4 des BotBuilder .NET SDK werden einige grundlegende Änderungen eingeführt, die sich auf die Erstellung von Bots auswirken.The BotBuilder .NET SDK v4 introduces several fundamental changes that affect how bots are authored. Dieser Leitfaden enthält eine Kurzübersicht über die allgemeinen Unterschiede zwischen der Durchführung von Aufgaben in v3- und v4-SDKs.The purpose of this guide is to provide a quick reference to highlight common differences between accomplishing tasks in the v3 and v4 SDKs.

  • Es wurde geändert, wie Informationen zwischen einem Bot und Kanälen übergeben werden.How information passes between a bot and channels has changed. In v3 haben Sie das Conversation-Objekt und die SendAsync-Methode verwendet, um eine Nachricht zu verarbeiten, und Autofac wurde sehr häufig zum Laden von verschiedenen Abhängigkeiten genutzt.In v3, you used the Conversation object and SendAsync method to process a message, and Autofac was used extensively for loading various dependencies. In v4 setzen Sie die Objekte Adapter und TurnContext ein, um eine Nachricht zu verarbeiten, und Sie können die Abhängigkeitsinjektionsbibliothek Ihrer Wahl verwenden.In v4, you use the Adapter and TurnContext objects to process a message, and you can use the dependency injection library of your choice.

  • Darüber hinaus wurden Dialoge und Botinstanzen weiter entkoppelt.Also, dialogs and bot instances have been further decoupled. In v3 wurden Dialoge in das Core SDK integriert, und der Stapel wurde intern verarbeitet. Untergeordnete Dialoge wurden mit den Methoden Call und Forward geladen.In v3, dialogs were built into the core SDK and the stack was handled internally, and child dialogs were loaded with the Call and Forward methods. In v4 übergeben Sie Dialoge nun als Argumente an Botinstanzen, um eine höhere Kompositionsflexibilität und Entwicklerkontrolle für den Dialogstapel zu erzielen, und die untergeordneten Dialoge werden mit den Methoden BeginDialogAsync und ReplaceDialogAsync geladen.In v4, you now pass dialogs into bot instances as arguments, providing greater compositional flexibility and developer control of the dialog stack, and child dialogs are loaded with the BeginDialogAsync and ReplaceDialogAsync methods.

  • Darüber hinaus wird in v4 eine ActivityHandler-Klasse bereitgestellt, die die Automatisierung der Verarbeitung unterschiedlicher Arten von Aktivitäten ermöglicht, z. B. vom Typ message, conversation update und event.Moreover, v4 provides an ActivityHandler class, which helps automate the handling of different types of activities, such as message, conversation update, and event activities.

Diese Verbesserungen führen zu Änderungen der Syntax zum Entwickeln von Bots in .NET, vor allem in Bezug auf das Erstellen von Botobjekten, Definieren von Dialogen und Codieren von Ereignisbehandlungslogik.These improvements result in changes in syntax for developing bots in .NET, especially around creating bot objects, defining dialogs, and coding event handling logic.

Im restlichen Teil dieses Themas werden die Konstrukte im .NET Bot Framework SDK v3 mit den Entsprechungen in v4 verglichen.The rest of this topic compares the constructs in the .NET Bot Framework SDK v3 to their equivalent in v4.

Verarbeiten eingehender NachrichtenTo process incoming messages

V3v3

[BotAuthentication]
public class MessagesController : ApiController
{
    public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
    {
        if (activity.GetActivityType() == ActivityTypes.Message)
        {
            await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
        }

        return Request.CreateResponse(HttpStatusCode.OK);
    }
}

v4v4

[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
    private readonly IBotFrameworkHttpAdapter Adapter;
    private readonly IBot Bot;

    public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
    {
        Adapter = adapter;
        Bot = bot;
    }

    [HttpPost]
    public async Task PostAsync()
    {
        await Adapter.ProcessAsync(Request, Response, Bot);
    }
}

Senden einer Nachricht an einen BenutzerTo send a message to a user

V3v3

await context.PostAsync("Hello and welcome to the help desk bot.");

v4v4

await turnContext.SendActivityAsync("Hello and welcome to the help desk bot.");

Laden eines StammdialogsTo load a root dialog

V3v3

await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());

v4v4

// Create a DialogExtensions class with a Run method.
public static class DialogExtensions
{
    public static async Task Run(
        this Dialog dialog,
        ITurnContext turnContext,
        IStatePropertyAccessor<DialogState> accessor,
        CancellationToken cancellationToken)
    {
        var dialogSet = new DialogSet(accessor);
        dialogSet.Add(dialog);

        var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);

        var results = await dialogContext.ContinueDialogAsync(cancellationToken);
        if (results.Status == DialogTurnStatus.Empty)
        {
            await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken);
        }
    }
}

// Call it from the ActivityHandler's OnMessageActivityAsync override
protected override async Task OnMessageActivityAsync(
    ITurnContext<IMessageActivity> turnContext,
    CancellationToken cancellationToken)
{
    // Run the Dialog with the new message Activity.
    await Dialog.Run(
        turnContext,
        ConversationState.CreateProperty<DialogState>("DialogState"),
        cancellationToken);
}

Starten eines untergeordneten DialogsTo start a child dialog

V3v3

context.Call(new NextDialog(), this.ResumeAfterNextDialog);

oderor

await context.Forward(new NextDialog(), this.ResumeAfterNextDialog, message);

v4v4

dialogContext.BeginDialogAsync("<child-dialog-id>", options);

oderor

dialogContext.ReplaceDialogAsync("<child-dialog-id>", options);

So beenden Sie einen DialogTo end a dialog

V3v3

context.Done(ReturnValue);

v4v4

await context.EndDialogAsync(ReturnValue);

Auffordern eines Benutzers zur EingabeTo prompt a user for input

V3v3

PromptDialog.Choice(
    context,
    this.OnOptionSelected,
    Options, PromptMessage,
    ErrorMessage,
    3,
    PromptStyle.PerLine);

v4v4

// In the dialog's constructor, register the prompt, and waterfall steps.
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
    FirstStepAsync,
    SecondStepAsync,
}));

// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);

// ...

// In the first step, invoke the prompt.
private async Task<DialogTurnResult> FirstStepAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    return await stepContext.PromptAsync(
        nameof(TextPrompt),
        new PromptOptions { Prompt = MessageFactory.Text("Please enter your destination.") },
        cancellationToken);
}

// In the second step, retrieve the Result from the stepContext.
private async Task<DialogTurnResult> SecondStepAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    var destination = (string)stepContext.Result;
}

Speichern von Informationen im DialogzustandTo save information to dialog state

V3v3

Alle Dialoge und die zugehörigen Felder wurden in v3 automatisch serialisiert.All dialogs, and their fields, were auto-serialized in V3.

v4v4

// StepContext values are auto-serialized in V4, and scoped to the dialog.
stepContext.values.destination = destination;

So schreiben Sie Zustandsänderungen in die PersistenzebeneTo write changes in state to the persistence layer

V3v3

Die Zustandsdaten werden am Ende des Turns standardmäßig automatisch gespeichert.State data is auto-saved by default at the end of the turn.

v4v4

// You now must explicitly save state changes before the end of the turn.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);

Erstellen und Registrieren des ZustandsspeichersTo create and register state storage

V3v3

// Autofac was used internally by the sdk, and state was automatic.
Conversation.UpdateContainer(
builder =>
{
    builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
    var store = new InMemoryDataStore();
    builder.Register(c => store)
        .Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
        .AsSelf()
        .SingleInstance();
});

v4v4

// Create the storage we'll be using for User and Conversation state.
// In-memory storage is great for testing purposes.
services.AddSingleton<IStorage, MemoryStorage>();

// Create the user state (used in this bot's Dialog implementation).
services.AddSingleton<UserState>();

// Create the conversation state (used by the Dialog system itself).
services.AddSingleton<ConversationState>();

// The dialog that will be run by the bot.
services.AddSingleton<MainDialog>();

// Create the bot as a transient. In this case the ASP.NET controller is expecting an IBot.
services.AddTransient<IBot, DialogBot>();

// In the bot's ActivityHandler implementation, call SaveChangesAsync after the OnTurnAsync completes.
public class DialogBot : ActivityHandler
{
    protected readonly Dialog Dialog;
    protected readonly BotState ConversationState;
    protected readonly BotState UserState;

    public DialogBot(ConversationState conversationState, UserState userState, Dialog dialog)
    {
        ConversationState = conversationState;
        UserState = userState;
    }

    public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        await base.OnTurnAsync(turnContext, cancellationToken);

        // Save any state changes that might have occurred during the turn.
        await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
        await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
    }

    protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
    {
        // Run the dialog, passing in the message activity for this turn.
        await Dialog.Run(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
    }
}

Abfangen eines Fehlers, der von einem Dialog ausgelöst wirdTo catch an error thrown from a dialog

V3v3

// Create a custom IPostToBot implementation to catch exceptions.
public sealed class CustomPostUnhandledExceptionToUser : IPostToBot
{
    private readonly IPostToBot inner;
    private readonly IBotToUser botToUser;
    private readonly ResourceManager resources;
    private readonly System.Diagnostics.TraceListener trace;

    public CustomPostUnhandledExceptionToUser(IPostToBot inner, IBotToUser botToUser, ResourceManager resources, System.Diagnostics.TraceListener trace)
    {
        SetField.NotNull(out this.inner, nameof(inner), inner);
        SetField.NotNull(out this.botToUser, nameof(botToUser), botToUser);
        SetField.NotNull(out this.resources, nameof(resources), resources);
        SetField.NotNull(out this.trace, nameof(trace), trace);
    }

    async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
    {
        try
        {
            await this.inner.PostAsync(activity, token);
        }
        catch (Exception ex)
        {
            try
            {
                // Log exception and send custom error message here.
                await this.botToUser.PostAsync("custom error message");
            }
            catch (Exception inner)
            {
                this.trace.WriteLine(inner);
            }

            throw;
        }
    }
}

// Register this using AutoFac, replacing the default PostUnhandledExceptionToUser.
builder
  .RegisterType<CustomPostUnhandledExceptionToUser>()
  .Keyed<IPostToBot>(typeof(PostUnhandledExceptionToUser));

v4v4

// Provide an error handler in your implementation of the BotFrameworkHttpAdapter.
public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
{
    public AdapterWithErrorHandler(
        ICredentialProvider credentialProvider,
        ILogger<BotFrameworkHttpAdapter> logger,
        ConversationState conversationState = null)
        : base(credentialProvider)
    {
        OnTurnError = async (turnContext, exception) =>
        {
            // Log any leaked exception from the application.
            logger.LogError($"Exception caught : {exception.Message}");

            // Send a catch-all apology to the user.
            await turnContext.SendActivityAsync("Sorry, it looks like something went wrong.");

            if (conversationState != null)
            {
                try
                {
                    // Delete the conversation state for the current conversation, to prevent the
                    // bot from getting stuck in a error-loop caused by being in a bad state.
                    // Conversation state is similar to "cookie-state" in a web page.
                    await conversationState.DeleteAsync(turnContext);
                }
                catch (Exception e)
                {
                    logger.LogError(
                        $"Exception caught on attempting to Delete ConversationState : {e.Message}");
                }
            }
        };
    }
}

Verarbeiten unterschiedlicher AktivitätstypenTo process different activity types

V3v3

// Within your MessageController, check the message type.
string messageType = activity.GetActivityType();
if (messageType == ActivityTypes.Message)
{
    await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else if (messageType == ActivityTypes.DeleteUserData)
{
}
else if (messageType == ActivityTypes.ConversationUpdate)
{
}
else if (messageType == ActivityTypes.ContactRelationUpdate)
{
}
else if (messageType == ActivityTypes.Typing)
{
}

v4v4

// In the bot's ActivityHandler implementation, override relevant methods.

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    // Handle message activities here.
}

protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
    // Handle conversation update activities in general here.
}

protected override Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
    // Handle event activities in general here.
}

Protokollieren aller AktivitätenTo log all activities

V3v3

IActivityLogger wurde verwendet.IActivityLogger was used.

builder.RegisterType<ActivityLoggerImplementation>().AsImplementedInterfaces().InstancePerDependency(); 

public class ActivityLoggerImplementation : IActivityLogger
{
    async Task IActivityLogger.LogAsync(IActivity activity)
    {
        // Store the activity.
    }
}

v4v4

Verwenden Sie ITranscriptLogger.Use ITranscriptLogger.

var transcriptMiddleware = new TranscriptLoggerMiddleware(new TranscriptLoggerImplementation(Configuration.GetSection("StorageConnectionString").Value));
adapter.Use(transcriptMiddleware);

public class TranscriptLoggerImplementation : ITranscriptLogger
{
    async Task ITranscriptLogger.LogActivityAsync(IActivity activity)
    {
        // Store the activity.
    }
}

Hinzufügen von BotzustandsspeicherTo add bot state storage

Die Schnittstelle zum Speichern von Benutzerdaten, Unterhaltungsdaten und privaten Unterhaltungsdaten hat sich geändert.The interface for storing user data, conversation data, and private conversation data has changed.

V3v3

Der Zustand wurde unter Verwendung einer IBotDataStore-Implementierung beibehalten und mit Autofac in das Dialogzustandssystem des SDK injiziert.State was persisted using an IBotDataStore implementation, and injecting it into the dialog state system of the SDK using Autofac. Microsoft stellte MemoryStorage-, DocumentDbBotDataStore-, TableBotDataStore- und SqlBotDataStore-Klassen im Microsoft.Bot.Builder.Azure bereit.Microsoft provided MemoryStorage, DocumentDbBotDataStore, TableBotDataStore, and SqlBotDataStore classes in Microsoft.Bot.Builder.Azure.

IBotDataStore wurde verwendet, um Daten dauerhaft zu speichern.IBotDataStore was used to persist data.

Task<bool> FlushAsync(IAddress key, CancellationToken cancellationToken);
Task<T> LoadAsync(IAddress key, BotStoreType botStoreType, CancellationToken cancellationToken);
Task SaveAsync(IAddress key, BotStoreType botStoreType, T data, CancellationToken cancellationToken);
var dbPath = ConfigurationManager.AppSettings["DocDbPath"];
var dbKey = ConfigurationManager.AppSettings["DocDbKey"];
var docDbUri = new Uri(dbPath);
var storage = new DocumentDbBotDataStore(docDbUri, dbKey);
builder.Register(c => storage)
                .Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
                .AsSelf()
                .SingleInstance();

v4v4

Die Speicherebene verwendet die IStorage-Schnittstelle; geben Sie beim Erstellen der einzelnen Zustandsverwaltungsobjekte für Ihren Bot das Speicherebenenobjekt an, beispielsweise UserState, ConversationState oder PrivateConversationState.The storage layer uses the IStorage interface, specify the storage-layer object when creating each state-management object for your bot, such as UserState, ConversationState, or PrivateConversationState. Das Zustandsverwaltungsobjekte stellt Schlüssel für die zugrunde liegende Speicherebene bereit und fungiert auch als Eigenschaften-Manager.The state-management object provides keys to the underlying storage layer and also acts as a property manager. Verwenden Sie beispielsweise IPropertyManager.CreateProperty<T>(string name), um einen Zustandseigenschaftenaccessor zu erstellen.For instance, use IPropertyManager.CreateProperty<T>(string name) to create a state property accessor. Diese Eigenschaftenaccessoren werden verwendet, um Werte aus dem zugrunde liegenden Speicher des Bot abzurufen und darin zu speichern.These property accessors are used to retrieve and store values into and out of the bot's underlying storage.

Verwenden Sie IStorage, um Daten dauerhaft zu speichern.Use IStorage to persist data.

Task DeleteAsync(string[] keys, CancellationToken cancellationToken = default(CancellationToken));
Task<IDictionary<string, object>> ReadAsync(string[] keys, CancellationToken cancellationToken = default(CancellationToken));
Task WriteAsync(IDictionary<string, object> changes, CancellationToken cancellationToken = default(CancellationToken));
var storageOptions = new CosmosDbPartitionedStorageOptions()
{
    AuthKey = configuration["cosmosKey"],
    ContainerId = configuration["cosmosContainer"],
    CosmosDbEndpoint = configuration["cosmosPath"],
    DatabaseId = configuration["cosmosDatabase"]
};

IStorage dataStore = new CosmosDbPartitionedStorage(storageOptions);
var conversationState = new ConversationState(dataStore);
services.AddSingleton(conversationState);

Hinweis

Bei Verwendung von CosmosDbPartitionedStorage sind Sie für das Erstellen einer Datenbank und das Angeben des Cosmos DB-Endpunkts, des Autorisierungsschlüssels und der Datenbank-ID verantwortlich (wie oben gezeigt).When using CosmosDbPartitionedStorage, you are responsible for creating a database and providing the Cosmos DB endpoint, authorization key and database ID as show above. Sie geben einfach eine ID für einen Container an. Ihr Bot erstellt diesen für Sie und stellt sicher, dass er zum Speichern des Botzustands richtig konfiguriert ist.You should simply specify an ID for a container - your bot will create it for you, ensuring it is configured correctly for storing bot state. Wenn Sie den Container selbst erstellen, sollten Sie sicherstellen, dass der Partitionsschlüssel auf /id festgelegt ist, und die CosmosDbPartitionedStorageOptions.ContainerId-Eigenschaft festlegen.If you do create the container yourself, ensure that the partition key is set to /id and set the CosmosDbPartitionedStorageOptions.ContainerId property.

Verwenden von FormFlowTo use Form Flow

V3v3

Microsoft.Bot.Builder.FormFlow wurde zum Core Bot Builder SDK hinzugefügt.Microsoft.Bot.Builder.FormFlow was included within the core Bot Builder SDK.

v4v4

Bot.Builder.Community.Dialogs.FormFlow ist jetzt eine Bot Builder-Communitybibliothek.Bot.Builder.Community.Dialogs.FormFlow is now a Bot Builder Community library. Die Quelle ist im Repository der Community verfügbar.The source is available on the community repository.

Verwenden von LuisDialogTo use LuisDialog

V3v3

Microsoft.Bot.Builder.Dialogs.LuisDialog wurde zum Core Bot Builder SDK hinzugefügt.Microsoft.Bot.Builder.Dialogs.LuisDialog was included within the core Bot Builder SDK.

v4v4

Bot.Builder.Community.Dialogs.Luis ist jetzt eine Bot Builder-Communitybibliothek.Bot.Builder.Community.Dialogs.Luis is now a Bot Builder Community library. Die Quelle ist im Repository der Community verfügbar.The source is available on the community repository.

Verwenden von QnA MakerTo use QnA Maker

V3v3

[Serializable]
[QnAMaker("QnAEndpointKey", "QnAKnowledgebaseId", <ScoreThreshold>, <TotalResults>, "QnAEndpointHostName")]
public class SimpleQnADialog : QnAMakerDialog
{
}

v4v4

public class QnABot : ActivityHandler
{
  private readonly IConfiguration _configuration;
  private readonly ILogger<QnABot> _logger;
  private readonly IHttpClientFactory _httpClientFactory;

  public QnABot(IConfiguration configuration, ILogger<QnABot> logger, IHttpClientFactory httpClientFactory)
  {
    _configuration = configuration;
    _logger = logger;
    _httpClientFactory = httpClientFactory;
  }

  protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
  {
    var httpClient = _httpClientFactory.CreateClient();

    var qnaMaker = new QnAMaker(new QnAMakerEndpoint
    {
      KnowledgeBaseId = _configuration["QnAKnowledgebaseId"],
      EndpointKey = _configuration["QnAEndpointKey"],
      Host = _configuration["QnAEndpointHostName"]
    },
    null,
    httpClient);

    _logger.LogInformation("Calling QnA Maker");

    // The actual call to the QnA Maker service.
    var response = await qnaMaker.GetAnswersAsync(turnContext);
    if (response != null && response.Length > 0)
    {
      await turnContext.SendActivityAsync(MessageFactory.Text(response[0].Answer), cancellationToken);
    }
    else
    {
      await turnContext.SendActivityAsync(MessageFactory.Text("No QnA Maker answers were found."), cancellationToken);
    }
  }
}