Útmutató: Regisztrációk tömeges exportálása és módosítása
Vannak olyan forgatókönyvek, amelyekben nagy számú regisztrációt kell létrehoznia vagy módosítania egy értesítési központban. Ilyen forgatókönyvek például a kötegelt számítások utáni címkefrissítések, vagy egy meglévő leküldéses implementáció migrálása a Notification Hubs használatára.
Ez a témakör azt ismerteti, hogyan használhatja a Notification Hubs tömeges támogatását nagy számú művelet elvégzésére egy értesítési központban, vagy hogyan exportálhatja az összes regisztrációt.
High-Level Flow
A Batch-támogatás célja, hogy támogassa a több millió regisztrációt tartalmazó, hosszan futó feladatokat. Ennek a skálázásnak az eléréséhez a kötegelés támogatása az Azure Storage-t használja a feladatok részleteinek és kimenetének tárolására. Tömeges frissítési műveletek esetén a felhasználónak létre kell hoznia egy fájlt egy blobtárolóban, amelynek tartalma a regisztrációs frissítési műveletek listája. A feladat indításakor a felhasználó megad egy URL-címet a bemeneti blobhoz, valamint egy kimeneti könyvtár url-címét (szintén egy blobtárolóban). A feladat elindítása után a felhasználó ellenőrizheti az állapotot a feladat indításakor megadott URL-cím lekérdezésével. Vegye figyelembe, hogy egy adott feladat csak egy adott típusú (létrehozási, frissítési vagy törlési) műveletet hajthat végre. Az exportálási műveletek hasonló módon hajthatók végre.
Importálás
Telepítés
Ez a szakasz a következőket feltételezi:
Egy kiépített értesítési központ.
Egy Azure Storage blobtároló.
Az Azure Storage és Azure Service Bus NuGet-csomagokra mutató hivatkozások.
Bemeneti fájl létrehozása és tárolása blobban
A bemeneti fájlok soronként egy XML-ben szerializált regisztrációlistát tartalmaznak. Az Azure SDK használatával az alábbi példakód bemutatja, hogyan szerializálhatja a regisztrációkat, és töltheti fel őket a blobtárolóba.
private static void SerializeToBlob(CloudBlobContainer container, RegistrationDescription[] descriptions)
{
StringBuilder builder = new StringBuilder();
foreach (var registrationDescription in descriptions)
{
builder.AppendLine(RegistrationDescription.Serialize());
}
var inputBlob = container.GetBlockBlobReference(INPUT_FILE_NAME);
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(builder.ToString())))
{
inputBlob.UploadFromStream(stream);
}
}
Fontos
Az előző kód szerializálja a regisztrációkat a memóriában, majd feltölti a teljes streamet egy blobba. Ha néhány megabájtnál több fájlt töltött fel, tekintse meg az Azure Blob útmutatóját a lépések végrehajtásához; például blokkblobok.
URL-jogkivonatok létrehozása
A bemeneti fájl feltöltése után létre kell hoznia azokat az URL-címeket, amelyeket meg kell adnia az értesítési központnak mind a bemeneti fájlhoz, mind a kimeneti könyvtárhoz. Vegye figyelembe, hogy két különböző blobtárolót használhat a bemenethez és a kimenethez.
static Uri GetOutputDirectoryUrl(CloudBlobContainer container)
{
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy
{
SharedAccessExpiryTime = DateTime.UtcNow.AddDays(1),
Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List | SharedAccessBlobPermissions.Read
};
string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);
return new Uri(container.Uri + sasContainerToken);
}
static Uri GetInputFileUrl(CloudBlobContainer container, string filePath)
{
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy
{
SharedAccessExpiryTime = DateTime.UtcNow.AddDays(1),
Permissions = SharedAccessBlobPermissions.Read
};
string sasToken = container.GetBlockBlobReference(filePath).GetSharedAccessSignature(sasConstraints);
return new Uri(container.Uri + "/" + filePath + sasToken);
}
Feladat küldése
A két bemeneti és kimeneti URL-címmel most elindíthatjuk a kötegelt feladatot.
NotificationHubClient client = NotificationHubClient.CreateClientFromConnectionString(CONNECTION_STRING, HUB_NAME);
var createTask = client.SubmitNotificationHubJobAsync(
new NotificationHubJob {
JobType = NotificationHubJobType.ImportCreateRegistrations,
OutputContainerUri = outputContainerSasUri,
ImportFileUri = inputFileSasUri
}
);
createTask.Wait();
var job = createTask.Result;
long i = 10;
while (i > 0 && job.Status != NotificationHubJobStatus.Completed)
{
var getJobTask = client.GetNotificationHubJobAsync(job.JobId);
getJobTask.Wait();
job = getJobTask.Result;
Thread.Sleep(1000);
i--;
}
A bemeneti és kimeneti URL-címek mellett ez a példa egy NotificationHubJob
objektumot tartalmazó JobType
objektumot is létrehoz, amely a következők egyike lehet:
ImportCreateRegistrations
ImportUpdateRegistrations
ImportDeleteRegistrations
A hívás befejezése után az értesítési központ folytatja a feladatot, és a GetNotificationHubJobAsync hívásával ellenőrizheti annak állapotát.
A feladat befejezését követően a kimeneti könyvtárban található alábbi fájlokkal vizsgálhatja meg az eredményeket:
/<hub>/<jobid>/Failed.txt
/<hub>/<jobid>/Output.txt
Ezek a fájlok tartalmazzák a köteg sikeres és sikertelen műveleteinek listáját. A fájlformátum .cvs, amelyben minden sor tartalmazza az eredeti bemeneti fájl sorszámát és a művelet kimenetét (általában a létrehozott vagy frissített regisztrációs leírást).
Exportálás
A regisztráció exportálása hasonló az importáláshoz, a következő különbségekkel:
Csak a kimeneti URL-címre van szüksége.
Létre kell hoznia egy ExportRegistrations típusú NotificationHubJob feladatot.
Teljes mintakód
Az alábbiakban egy teljes körűen működő minta található, amely a regisztrációkat egy értesítési központba importálja.
using Microsoft.ServiceBus.Notifications;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace ConsoleApplication1
{
class Program
{
private static string CONNECTION_STRING = "---";
private static string HUB_NAME = "---";
private static string INPUT_FILE_NAME = "CreateFile.txt";
private static string STORAGE_ACCOUNT = "---";
private static string STORAGE_PASSWORD = "---";
private static StorageUri STORAGE_ENDPOINT = new StorageUri(new Uri("---"));
static void Main(string[] args)
{
var descriptions = new[]
{
new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMkUxREQFBlVTTkMwMQ"),
new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMjUxREQFBlVTTkMwMQ"),
new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMhUxREQFBlVTTkMwMQ"),
new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMdUxREQFBlVTTkMwMQ"),
};
//write to blob store to create an input file
var blobClient = new CloudBlobClient(STORAGE_ENDPOINT, new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials(STORAGE_ACCOUNT, STORAGE_PASSWORD));
var container = blobClient.GetContainerReference("testjobs");
container.CreateIfNotExists();
SerializeToBlob(container, descriptions);
// TODO then create Sas
var outputContainerSasUri = GetOutputDirectoryUrl(container);
var inputFileSasUri = GetInputFileUrl(container, INPUT_FILE_NAME);
//Lets import this file
NotificationHubClient client = NotificationHubClient.CreateClientFromConnectionString(CONNECTION_STRING, HUB_NAME);
var createTask = client.SubmitNotificationHubJobAsync(
new NotificationHubJob {
JobType = NotificationHubJobType.ImportCreateRegistrations,
OutputContainerUri = outputContainerSasUri,
ImportFileUri = inputFileSasUri
}
);
createTask.Wait();
var job = createTask.Result;
long i = 10;
while (i > 0 && job.Status != NotificationHubJobStatus.Completed)
{
var getJobTask = client.GetNotificationHubJobAsync(job.JobId);
getJobTask.Wait();
job = getJobTask.Result;
Thread.Sleep(1000);
i--;
}
}
private static void SerializeToBlob(CloudBlobContainer container, RegistrationDescription[] descriptions)
{
StringBuilder builder = new StringBuilder();
foreach (var registrationDescription in descriptions)
{
builder.AppendLine(RegistrationDescription.Serialize());
}
var inputBlob = container.GetBlockBlobReference(INPUT_FILE_NAME);
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(builder.ToString())))
{
inputBlob.UploadFromStream(stream);
}
}
static Uri GetOutputDirectoryUrl(CloudBlobContainer container)
{
//Set the expiry time and permissions for the container.
//In this case no start time is specified, so the shared access signature becomes valid immediately.
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy
{
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(4),
Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List | SharedAccessBlobPermissions.Read
};
//Generate the shared access signature on the container, setting the constraints directly on the signature.
string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);
//Return the URI string for the container, including the SAS token.
return new Uri(container.Uri + sasContainerToken);
}
static Uri GetInputFileUrl(CloudBlobContainer container, string filePath)
{
//Set the expiry time and permissions for the container.
//In this case no start time is specified, so the shared access signature becomes valid immediately.
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy
{
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(4),
Permissions = SharedAccessBlobPermissions.Read
};
//Generate the shared access signature on the container, setting the constraints directly on the signature.
string sasToken = container.GetBlockBlobReference(filePath).GetSharedAccessSignature(sasConstraints);
//Return the URI string for the container, including the SAS token.
return new Uri(container.Uri + "/" + filePath + sasToken);
}
static string GetJobPath(string namespaceName, string notificationHubPath, string jobId)
{
return string.Format(CultureInfo.InvariantCulture, @"{0}//{1}/{2}/", namespaceName, notificationHubPath, jobId);
}
}
}