https://d226lax1qjow5r.cloudfront.net/blog/blogposts/how-to-add-machine-learning-to-facebook-messenger-dr/What-Kind-of-Dog-Is-That.png

"Hey Facebook, was ist das für ein Hund?" Hinzufügen von ML zu Messenger

Zuletzt aktualisiert am May 13, 2021

Lesedauer: 9 Minuten

Convolutional Neural Networks (CNNs) bieten einen leistungsstarken und skalierbaren Mechanismus für die Durchführung von Bildklassifizierungen. Es kann relativ schwierig sein, sie von Grund auf zu erstellen, zu trainieren und abzustimmen, was Tools wie TensorFlow und die Inception-Modelle so unverzichtbar für die Verbesserung unserer ML-Workflows macht.

Das heißt, für uns .NET-Leute ist das Ausführen von Python-Skripten aus einer In-App-Shell weniger als eine ideale Lösung, was die Veröffentlichung der ML.NET TensorFlow-Bibliothek so aufregend macht.

Was wäre, wenn ich Ihnen sagen würde, dass Sie mit nur ein paar hundert Zeilen C#-Code und ein wenig Konfiguration eine ASP.NET Core-Anwendung erstellen können, die ein leistungsstarkes CNN beherbergt, mit dem Sie so einfach interagieren können wie das Senden einer Bildnachricht an eine Facebook-Seite?

Mit einer so einfachen Ausbildung wie:

Training ImageTraining Image

Und eine einfache Klassifizierungsanfrage wie:

Classification RequestClassification Request

Nun, genau das werden wir tun - mit ML.NET werden wir einen leistungsstarken Klassifikator erstellen und dann mit der Messages API von Nexmo und Messenger einen leistungsstarken, einfach zu bedienenden Vektor für Training und Klassifizierung erstellen.

Lernziele

In diesem Lernprogramm werden wir:

  • Ein ML.NET TensorFlow Neuronales Netzwerk erstellen

  • Trainieren Sie das neuronale Netz, um verschiedene Hundetypen zu erkennen.

  • Erstellen Sie einen Nachrichtenvektor, um das Neuronale Netz zu bitten, Hunde zu klassifizieren, die es noch nie zuvor gesehen hat.

  • Erstellen Sie einen Lernvektor, damit das Neuronale Netz dynamisch neue Hundetypen lernen kann.

Voraussetzungen

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

Projekt einrichten

Das Wichtigste zuerst - öffnen wir Visual Studio, erstellen wir eine neue ASP.NET Core 3.0 API Anwendung und nennen wir sie MessagesTensorFlow. Jetzt fügen wir die folgenden NuGet-Pakete zur Lösung hinzu:

  • BouncyCastle

  • jose-jwt

  • Microsoft.ML

  • Microsoft.ML.ImageAnalytics

  • Microsoft.ML.TensorFlow

  • Newtonsoft.Json

Wir werden unser neuronales Netz mit dem Inception V1 Modell starten und es dann mit Bildern / Etiketten von der Festplatte füttern. Erstellen Sie einen Ordner unter dem MessagesTensorFlow-Verzeichnis mit dem Namen assets.

Laden Sie die Assets herunter und entpacken Sie das Modell Inception V1

Erstellen Sie außerdem unter Assets einen Ordner mit der Bezeichnung train und predict. Fügen Sie unter jedem dieser Verzeichnisse eine Datei tags.tsv hinzu. Ihre Verzeichnisstruktur sollte jetzt etwa so aussehen:

Directory structureDirectory structure

Gehen Sie nun zu jeder Datei und setzen Sie im Abschnitt Erweiterte Eigenschaften die Option In Ausgabeverzeichnis kopieren auf Kopieren, wenn neuer

Den Lernenden schaffen

Erstellen wir nun die Klasse, die unser neuronales Netzwerk enthalten soll. Erstellen Sie eine Datei namens TFEngine.cs.

Einfuhren

Fügen Sie die folgenden Importe am Anfang der Datei hinzu:

using Microsoft.ML;
using Microsoft.ML.Data;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;

Klasse einrichten

Dann fügen wir innerhalb der TFEngine-Klasse einige Pfade hinzu, damit wir auf alle Dateien zugreifen können, die wir in unser Modell aufnehmen werden. Sowie einige Einstellungen für die Verwaltung der Inception-Daten.

static readonly string _assetsPath = Path.Combine(Environment.CurrentDirectory, "assets");
static readonly string _imagesFolder = Path.Combine(_assetsPath, "train");
static readonly string _savePath = Path.Combine(_assetsPath, "predict");
static readonly string _trainTagsTsv = Path.Combine(_imagesFolder, "tags.tsv");
static readonly string _inceptionTensorFlowModel = Path.Combine(_assetsPath, "inception5h", "tensorflow_inception_graph.pb");

const int ImageHeight = 224;
const int ImageWidth = 224;
const float Mean = 117;
const bool ChannelsLast = true;

Wir werden diese Klasse auch als Singleton einrichten und jeweils nur einen Zugriff auf sie zulassen. Wir werden auch einen webClient für das Herunterladen der Bild-URLs hinzufügen.

static readonly object _lock = new object();

private static WebClient _client = new WebClient();
private static TFEngine _instance;
public static TFEngine Instance
{
    get
    {
        lock (_lock)
        {
            if (_instance == null)
            {
                _instance = new TFEngine();
            }
            return _instance;
        }

    }
}

private TFEngine()
{
    _mlContext = new MLContext();
    GenerateModel();
}

Wir werden auch einige Felder erstellen, um unsere Pipeline zu speichern, die zur Erstellung unseres Modells verwendet wird, das Modell, das zur Durchführung der Vorhersage verwendet wird, und den MLContext.

private IEstimator<ITransformer> _pipeline;
private ITransformer _model;
private MLContext _mlContext;

Als Nächstes fügen Sie eine Klasse ImageData hinzu, die die Bilddaten enthält, während sie das Modell durchlaufen

public class ImageData
{
    [LoadColumn(0)]
    public string ImagePath;

    [LoadColumn(1)]
    public string Label;
}

Erstellen Sie dann eine Struktur für die Vorhersagedaten, die aus dem Modell fließen:

public class ImagePrediction : ImageData
{
    public float[] Score;

    public string PredictedLabelValue;
}

Der Score ist ein Array, das die Wahrscheinlichkeiten enthält, die das neuronale Netz jedem möglichen Label zuordnet, und der PredictedLabelValue ist natürlich die Vorhersage des Netzes (das Element mit dem höchsten Score).

Modell Ausbildung

Jetzt ist es an der Zeit, unser Modell zu trainieren!

Hinzufügen einer Methode namens GenerateModel

public string ClassifySingleImage(string imageUrl)
{
    try
    {
        var filename = Path.Combine(_savePath, $"{Guid.NewGuid()}.jpg");
        _client.DownloadFile(imageUrl, filename);
        var imageData = new ImageData()
        {
            ImagePath = filename
        };

        var predictor = _mlContext.Model.CreatePredictionEngine<ImageData, ImagePrediction>(_model);
        var prediction = predictor.Predict(imageData);
        var response = $"I'm about {prediction.Score.Max() * 100}% sure that the image you sent me is a {prediction.PredictedLabelValue}";
        Console.WriteLine($"Image: {Path.GetFileName(imageData.ImagePath)} predicted as: {prediction.PredictedLabelValue} with score: {prediction.Score.Max() * 100} ");
        return response;
    }
    catch (Exception)
    {
        return "Something went wrong when trying to classify image";
    }
}

Dies ist der Kern dessen, was unseren Prädiktor zum Laufen bringt. Der Abschnitt "_pipeline =" ist eine Kette von Befehlen, die:

  • Laden Sie die Bilder von der Festplatte

  • Ändern Sie die Größe der Bilder für das Einlesen

  • Extrahieren und Vektorisieren der Pixel in den Bildern

  • Laden Sie das Inception-TensorFlow-Modell (im Wesentlichen unser vorgefertigtes neuronales Netz)

  • Erstellen Sie ein Trainingsmodell und lassen Sie die Trainingsdaten durchlaufen, um ein Vorhersagemodell zu erstellen, das wir verwenden können.

Klassifizierung eines einzelnen Bildes

Mit unserem trainierten Modell können wir nun eine Methode erstellen, die einen Dateinamen annimmt und eine Zeichenkette zurückgibt, die eine Vorhersage und das Vertrauen des Netzwerks in diese Vorhersage enthält. Diese Funktion nimmt eine imageUrl entgegen, speichert die Datei auf der Festplatte, klassifiziert das Bild und gibt eine Zeichenkette zurück, die die Vorhersage des Klassifizierers mit seiner Konfidenz enthält.

public string ClassifySingleImage(string imageUrl)
{
    try
    {
        var filename = Path.Combine(_savePath, $"{Guid.NewGuid()}.jpg");
        _client.DownloadFile(imageUrl, filename);
        var imageData = new ImageData()
        {
            ImagePath = filename
        };

        var predictor = _mlContext.Model.CreatePredictionEngine<ImageData, ImagePrediction>(_model);
        var prediction = predictor.Predict(imageData);
        var response = $"I'm about {prediction.Score.Max() * 100}% sure that the image you sent me is a {prediction.PredictedLabelValue}";
        Console.WriteLine($"Image: {Path.GetFileName(imageData.ImagePath)} predicted as: {prediction.PredictedLabelValue} with score: {prediction.Score.Max() * 100} ");
        return response;
    }
    catch (Exception)
    {
        return "Something went wrong when trying to classify image";
    }
}

Hinzufügen von Trainingsdaten

Die letzte Operation, um die wir die Tensor Flow Engine bitten werden, ist im Wesentlichen die Umkehrung der Vorhersage. Wir werden sie bitten, eine Bild-URL und eine Bezeichnung zu akzeptieren und sich selbst zu aktualisieren, um Bilder mit dieser Bezeichnung besser zu erkennen. AddTrainingImage speichert das bereitgestellte Bild auf der Festplatte, fügt Informationen über das Bild an die Datei tags.tsv an und generiert das Modell neu.

public string AddTrainingImage(string imageUrl, string label)
{
    try
    {
        var id = Guid.NewGuid();
        var fileName = Path.Combine(_imagesFolder, $"{id}.jpg");
        _client.DownloadFile(imageUrl, fileName);
        File.AppendAllText(_trainTagsTsv, $"{id}.jpg\t{label}" + Environment.NewLine);
        IDataView trainingData = _mlContext.Data.LoadFromTextFile<ImageData>(path: _trainTagsTsv, hasHeader: false);
        _model = _pipeline.Fit(trainingData);
        return $"I have trained myself to recognize the image you sent me as a {label}. Your teaching is appreciated";
    }
    catch (Exception)
    {
        return "something went wrong when trying to train on image";
    }
}

Verwendung der Messages API für die Klassifizierung und Schulung

Nachrichten Objekte

Als Nächstes fügen wir einige POCOs hinzu, um unsere Messaging-Daten zu speichern, wenn sie bei der Messages API ein- und ausgehen. Diese Objekte sind ziemlich ausführlich und tun nichts besonders Interessantes, abgesehen davon, dass sie die Serialisierung/Deserialisierung von JSON ermöglichen, also können Sie der Kürze halber einfach die folgenden Strukturen verwenden:

Interaktion mit der API

Durch die Erstellung dieser Strukturen können wir die Daten verwalten, die wir von der Messages API erhalten und an sie senden. Wir benötigen jedoch noch einen weiteren Schritt, um die API tatsächlich nutzen zu können - wir müssen ein JWT generieren, um unsere Anwendung bei der Messages API zu authentifizieren. Zu diesem Zweck müssen wir die folgenden Dateien erstellen.

  • TokenGenerator.cs

  • MessageSender.cs

JWT generieren

TokenGenerator wird eine statische Methode GenerateToken haben, die eine Liste von Claims und den privaten Schlüssel für Ihre Anwendung akzeptiert

using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;

namespace MessagesTensorFlow
{
    public class TokenGenerator
    {
        public static string GenerateToken(List<Claim> claims, string privateKey)
        {
            RSAParameters rsaParams;
            using (var tr = new StringReader(privateKey))
            {
                var pemReader = new PemReader(tr);
                var kp = pemReader.ReadObject();
                var privateRsaParams = kp as RsaPrivateCrtKeyParameters;
                rsaParams = DotNetUtilities.ToRSAParameters(privateRsaParams);
            }
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportParameters(rsaParams);
                Dictionary<string, object> payload = claims.ToDictionary(k => k.Type, v => (object)v.Value);
                return Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
            }
        }
    }
}

Dadurch wird ein JWT für Ihre Verwendung mit der Messages API generiert.

Erzeugen einer Anspruchsliste für JWT

In der MessageSender.cs haben wir eine Methode, um die Ansprüche für das JWT aus Ihrer appId zu generieren:

private static List<Claim> GetClaimsList(string appId)
{
    const int SECONDS_EXPIRY = 3600;
    var t = DateTime.UtcNow - new DateTime(1970, 1, 1);
    var iat = new Claim("iat", ((Int32)t.TotalSeconds).ToString(), ClaimValueTypes.Integer32); // Unix Timestamp for right now
    var application_id = new Claim("application_id", appId); // Current app ID
    var exp = new Claim("exp", ((Int32)(t.TotalSeconds + SECONDS_EXPIRY)).ToString(), ClaimValueTypes.Integer32); // Unix timestamp for when the token expires
    var jti = new Claim("jti", Guid.NewGuid().ToString()); // Unique Token ID
    var claims = new List<Claim>() { iat, application_id, exp, jti };

    return claims;
}

App-Einstellungen lesen und JWT erstellen

Dann haben wir eine weitere Methode, um die relevanten Elemente aus Ihrer Konfiguration zu lesen, die der Controller uns durch Dependency Injection übergibt, die Forderungsliste abruft und das JWT erstellt

private static string BuildJwt(IConfiguration config)
{
    var appId = config["Authentication:appId"];
    var priavteKeyPath = config["Authentication:privateKey"];
    string privateKey = "";
    using (var reader = File.OpenText(priavteKeyPath)) // file containing RSA PKCS1 private key
        privateKey = reader.ReadToEnd();

    var jwt = TokenGenerator.GenerateToken(GetClaimsList(appId), privateKey);
    return jwt;
}

Dies erfordert natürlich einige Einträge in Ihrer appsettings.json-Datei Fügen Sie das folgende Objekt zu Ihrer appsettings.json-Datei hinzu und füllen Sie es mit den entsprechenden Werten:

"Authentication": {
    "appId": "app_id",
    "privateKey": "path_to_key_file"
  }

Eine Nachricht senden

Jetzt werden wir das alles mit unserer SendMessage-Methode verbinden, die unsere Nachricht, toId, fromId und config entgegennimmt. Diese Methode generiert ein JWT und sendet eine Anfrage an die Messages API, um eine Nachricht mit dem Feedback unseres Klassifizierers an unseren Endbenutzer zu senden.

public static void SendMessage(string message, string fromId, string toId, IConfiguration config)
{
    const string MESSAGING_URL = @"https://api.nexmo.com/v0.1/messages";
    try
    {
        var jwt = BuildJwt(config);

        var requestObject = new MessageRequest()
        {
            to = new MessageRequest.To()
            {
                id = toId,
                type = "messenger"
            },
            from = new MessageRequest.From()
            {
                id = fromId,
                type = "messenger"
            },
            message = new MessageRequest.Message()
            {
                content = new MessageRequest.Message.Content()
                {
                    type = "text",
                    text = message
                },
                messenger = new MessageRequest.Message.Messenger()
                {
                    category = "RESPONSE"
                }
            }
        };
        var requestPayload = JsonConvert.SerializeObject(requestObject, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore });
        var httpWebRequest = (HttpWebRequest)WebRequest.Create(MESSAGING_URL);
        httpWebRequest.ContentType = "application/json";
        httpWebRequest.Accept = "application/json";
        httpWebRequest.Method = "POST";
        httpWebRequest.PreAuthenticate = true;
        httpWebRequest.Headers.Add("Authorization", "Bearer " + jwt);
        using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
        {
            streamWriter.Write(requestPayload);
        }
        using (var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse())
        {
            using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
            {
                var result = streamReader.ReadToEnd();
                Console.WriteLine(result);
                Console.WriteLine("Message Sent");
            }
        }
    }
    catch (Exception e)
    {
        Debug.WriteLine(e.ToString());
    }
}

Klassifizierungshandler

Da wir möchten, dass die Verarbeitung eingehender Webhooks asynchron erfolgt und sofort beantwortet wird, erstellen wir eine Datei ClassificationHandler.cs, um die Klassifizierungs-/Antwortvorgänge zu verarbeiten. Diese Datei wird einige kleine Strukturen enthalten, die es uns ermöglichen, eingehende Nachrichten zu entpacken, zu klassifizieren oder zu trainieren und zu beantworten.

Fügen Sie in ClassificationHandler.cs den folgenden Code hinzu:

public static void ClassifyAndRespond(object state)
{
    var request = state as ClassifyRequest;
    var response = TFEngine.Instance.ClassifySingleImage(request.imageUrl);
    MessageSender.SendMessage(response, request.toId, request.fromid, request.Configuration);
}

public static void AddTrainingData(object state)
{
    var request = state as TrainRequest;
    var response = TFEngine.Instance.AddTrainingImage(request.imageUrl, request.Label);
    MessageSender.SendMessage(response, request.toId, request.fromid, request.Configuration);
}
public class TrainRequest : Request
{
    public string Label { get; set; }
}
public class ClassifyRequest : Request{}
public abstract class Request
{
    public string imageUrl { get; set; }
    public string toId { get; set; }
    public string fromid { get; set; }

    public IConfiguration Configuration { get; set; }
}

Webhooks zur Behandlung eingehender Nachrichten

Aus der Sicht unseres Codes müssen wir als Letztes ein paar Controller erstellen, um die eingehenden Nachrichten und den Status von der Messages API zu verarbeiten.

Fügen Sie im Ordner "Controllers" 2 "API controller - Empty" mit den Namen "InboundController" und "StatusController" hinzu.

Status Controller

Der Status-Controller wird den Status der Nachrichten unserer Anwendung bereitstellen, während sie durch die API fließen. Um den Überblick zu behalten, fügen wir dem Status-Controller eine post-Methode hinzu, um den Statusinhalt in die Debug-Konsole zu schreiben:

[HttpPost]
public HttpStatusCode Post([FromBody]StatusMessage message)
{
    Debug.WriteLine(JsonConvert.SerializeObject(message));
    return HttpStatusCode.NoContent;
}

Eingehender Controller

Der Inbound Controller wird die eingehenden Nachrichten von unserem Webhook verwalten.

Klasse einrichten

Zunächst richten wir es ein, indem wir ein Wörterbuch für die ausstehenden Trainingsbezeichnungen erstellen, ein Konfigurationsobjekt für den Controller, um auf die Konfiguration zuzugreifen, und indem wir die Konfiguration durch Dependency Injecting in den Konstruktor des Inbound-Controllers einfügen:

public static Dictionary<string, string> _pendingTrainLabels = new Dictionary<string, string>();
public IConfiguration Configuration { get; set; }
public InboundController(IConfiguration configuration)
{
    Configuration = configuration;
}

Behandlung eingehender Nachrichten

Als nächstes schreiben wir den eigentlichen InboundMessage-Handler. Dieser Handler wird eine POST-Anfrage sein. Er prüft, ob die Nachricht einen Text enthält. Ist dies der Fall, wird geprüft, ob das erste Wort in der Nachricht "train" lautet. Ist dies der Fall, wird der Rest der Nachricht als Trainings-Label gespeichert, und wenn der Benutzer das nächste Mal eine Nachricht mit einem Bild sendet, wird der Klassifikator mit diesem Bild und Label trainiert.

Bei jeder anderen Bildnachricht klassifiziert es einfach das Bild und sendet das Ergebnis der Klassifizierung an den Absender der Nachricht zurück.

In beiden Fällen wird ein WorkItem im ThreadPool gestartet, wobei eines dieser praktischen ClassificationHandler-Anforderungsobjekte übergeben wird, die wir zuvor generiert haben. Dadurch wird der Controller entsperrt, um einen Status an die Messages-Api zurückzusenden (in diesem Fall 204, um mitzuteilen, dass die Nachricht eingegangen ist)

[HttpPost]
public HttpStatusCode Post([FromBody]InboundMessage message)
{
    const string TRAIN = "train";
    try
    {
        Debug.WriteLine(JsonConvert.SerializeObject(message));
        if (!string.IsNullOrEmpty(message.message.content.text))
        {
            var split = message.message.content.text.Split(new[] { ' ' }, 2);
            if (split.Length > 1)
            {
                if (split[0].ToLower() == TRAIN)
                {
                    var label = split[1];
                    var requestor = message.from.id;
                    if (!_pendingTrainLabels.ContainsKey(requestor))
                    {
                        _pendingTrainLabels.Add(requestor, label);
                    }
                    else
                    {
                        _pendingTrainLabels[requestor] = label;
                    }
                }
            }
        }
        if (_pendingTrainLabels.ContainsKey(message.from.id) && message.message.content?.image?.url != null)
        {
            ThreadPool.QueueUserWorkItem(ClassificationHandler.AddTrainingData, new ClassificationHandler.TrainRequest()
            {
                toId = message.to.id,
                fromid = message.from.id,
                imageUrl = message.message.content.image.url,
                Label = _pendingTrainLabels[message.from.id],
                Configuration = Configuration
            });
            _pendingTrainLabels.Remove(message.from.id);
        }
        else
        {
            ThreadPool.QueueUserWorkItem(ClassificationHandler.ClassifyAndRespond,
            new ClassificationHandler.ClassifyRequest()
            {
                toId = message.to.id,
                fromid = message.from.id,
                imageUrl = message.message.content.image.url,
                Configuration = Configuration
            });
        }

        return HttpStatusCode.NoContent;
    }
    catch (Exception ex)
    {
        return HttpStatusCode.NoContent;
    }
}

Saatgut mit ein paar Daten.

Sie können beliebige Bilder und Tags hinzufügen, um den Anfang zu machen. Der Einfachheit halber werde ich nur mit einem Bild beginnen - einem Bild meines Hundes (passenderweise Zero genannt).

Training dataTraining data

Ich werde dieses Bild in das Verzeichnis assets/train legen.

Da Zero ein Whippet ist, füge ich in der Datei tags.tsv im Ordner assets/train den Dateinamen "zero.jpg", gefolgt von einem Tabulator, gefolgt von der Bezeichnung "whippet", gefolgt von einer neuen Zeile ein

zero.jpg    whippet

Prüfung

Wenn das erledigt ist, muss man es nur noch in Betrieb nehmen, dem Internet aussetzen und es testen. Ich verwende ngrok und IIS express, um es zu testen.

IIS Express-Konfiguration

Gehen Sie zunächst auf die Registerkarte Debug in den Projekteigenschaften und suchen Sie nach der App Url - insbesondere nach dem Port, der verwendet werden soll - ich deaktiviere das Kontrollkästchen SSL aktivieren für Tests.

DebugDebug

Dann starten Sie die Website von Visual Studio mit IIS Express - Sie sehen den Port in der Adressleiste des Browsers, die aufpoppt - in meinem Beispiel habe ich alle Wetter-Controller-Zeug, das aus der Box kommt gereinigt, so dass ich eine 404, wenn ich es abfeuern - das ist in Ordnung, da dies wirklich nur als Web-Service zu hören und zu beantworten, um Webhooks handeln. Es gibt keine get-Anforderungen, um eine Seite zurück zu Ihrem Webbrowser zu propagieren.

Ngrok verwenden, um den Port für das Internet freizugeben

Damit die Messages API die Nachrichten weiterleiten kann, müssen wir die Site dem Internet aussetzen - zu Testzwecken verwenden wir ngrok um unseren IIS-Express-Port freizugeben. Öffnen Sie Ihre Befehlszeile und verwenden Sie diesen Befehl, ersetzen Sie ihn durch Ihre Portnummer.

ngrok http --host-header="localhost:" http://localhost:

Dieser Befehl erzeugt eine Ausgabe wie diese:

Command OutputCommand Output

Konfigurieren der Webhooks

Mit dem http-Link, den wir gerade von ngrok erhalten haben, können Sie die URL erstellen, die der Webhook zurückrufen wird - Sie können in der Route der Controller sehen, die wir gerade erstellt haben, wie die Route aussehen wird:

RouteRoute

Es wird so funktionieren, dass es http://dc0feb1d.ngrok.io/api/Status für Statusmeldungen und http://dc0feb1d.ngrok.io/api/Inbound für eingehende Nachrichten

HINWEIS: Der erste Teil der URL (dc0feb1d) ändert sich jedes Mal, wenn Sie ngrok auf der kostenlosen Ebene neu starten.

Wir werden diese Callback-URLs verwenden, um unsere Webhooks bei Nexmo zu registrieren.

Gehen Sie zu ${CUSTOMER_DASHBOARD_URL} und melden Sie sich bei Ihrem Nexmo Account an

Gehen Sie zu Nachrichten und Versand -> Ihre Applications und wählen Sie die Schaltfläche "Bearbeiten" für Ihre Application

Ändern Sie auf dem Bearbeitungsbildschirm die Felder Status-URL und Eingangs-URL auf die oben angegebenen Werte und klicken Sie auf die blaue Schaltfläche "Speichern" in der unteren rechten Ecke.

Und das war's. Jetzt haben Sie einen Klassifikator / Lerner, den Sie über Messenger mit Bildern füttern können.

Teilen Sie:

https://a.storyblok.com/f/270183/384x384/73d57fd8eb/stevelorello.png
Steve LorelloVonage Ehemalige

Ehemaliger .NET Developer Advocate @Vonage, polyglotter Software-Ingenieur, AI/ML