Introduction
This section is the C# API Reference for the Migration SDK. It documents the .NET types (classes, interfaces, and their members) you use when building migration applications in C# or any .NET language.
Below you’ll find requirements, installation, and a minimal getting-started example. For concepts, configuration, and samples, see Articles and Code Samples.
Prerequisites
.NET Runtime 8.0 or later.
Hardware requirements
The Migration SDK downloads copies of content items onto the machine that the Migration SDK is installed on. Make sure there's enough disk space on the machine to sequentially download content during the migration process.
Installation
To use the Migration SDK for .NET, download the .NET NuGet package. You can import the .nupkg file locally into a .NET project and reference it in your code.
Examples to get started
The following code samples are for writing a simple migration app using the Migration SDK. For details on configuring and customizing the Migration SDK to your specific needs, see Articles and Code Samples.
namespace Csharp.ExampleApplication
{
public static class Program
{
public static async Task Main(string[] args)
{
// Set the DOTNET_ENVIRONMENT environment variable to the name of the environment.
// This loads the appsettings.<DOTNET_ENVIRONMENT>.json config file.
// If no DOTNET_ENVIRONMENT is set, appsettings.json will be used
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((ctx, services) =>
{
services
.Configure<MyMigrationApplicationOptions>(ctx.Configuration)
.Configure<EmailDomainMappingOptions>(ctx.Configuration.GetSection("tableau:emailDomainMapping"))
.Configure<UnlicensedUsersMappingOptions>(ctx.Configuration.GetSection("tableau:unlicensedUsersMapping"))
.AddTableauMigrationSdk(ctx.Configuration.GetSection("tableau:migrationSdk"))
.AddCustomizations()
.AddHostedService<MyMigrationApplication>();
})
.Build();
await host.RunAsync();
}
/// <summary>
/// Registers services required for using the Tableau Migration SDK customizations.
/// </summary>
/// <param name="services">The service collection to register services with.</param>
/// <returns>The same service collection as the <paramref name="services"/> parameter.</returns>
public static IServiceCollection AddCustomizations(this IServiceCollection services)
{
services.AddScoped<CustomContext>();
services.AddScoped<SetMigrationContextHook>();
services.AddScoped<EmailDomainMapping>();
services.AddScoped<UnlicensedUsersMapping>();
services.AddScoped<ProjectRenameMapping>();
services.AddScoped<ChangeProjectMapping<IWorkbook>>();
services.AddScoped<ChangeProjectMapping<IDataSource>>();
services.AddScoped<DefaultProjectsFilter>();
services.AddScoped<UnlicensedUsersFilter>();
services.AddScoped<SharedCustomViewFilter>();
services.AddScoped<DataSourceConnectionPulled>();
services.AddScoped(typeof(UpdatePermissionsHook<,>));
services.AddScoped(typeof(BulkLoggingHook<>));
services.AddScoped<MigratedTagTransformer<IPublishableDataSource>>();
services.AddScoped<MigratedTagTransformer<IPublishableWorkbook>>();
services.AddScoped<EncryptExtractsTransformer<IPublishableDataSource>>();
services.AddScoped<EncryptExtractsTransformer<IPublishableWorkbook>>();
services.AddScoped(typeof(SimpleScheduleStartAtTransformer<>));
services.AddScoped<CustomViewExcludeDefaultUserTransformer>();
services.AddScoped<ActionUrlXmlTransformer>();
services.AddScoped<FlowConnectionServerJsonTransformer>();
services.AddScoped<ModifyPermissionsTransformer>();
services.AddScoped<LogMigrationActionsHook>();
services.AddScoped(typeof(LogMigrationBatchesHook<>));
return services;
}
}
}
namespace Csharp.ExampleApplication
{
internal sealed class MyMigrationApplication : IHostedService
{
private readonly Stopwatch _timer;
private readonly IHostApplicationLifetime _appLifetime;
private IServerToCloudMigrationPlanBuilder _planBuilder;
private readonly IMigrator _migrator;
private readonly MyMigrationApplicationOptions _options;
private readonly ILogger<MyMigrationApplication> _logger;
private readonly MigrationManifestSerializer _manifestSerializer;
public MyMigrationApplication(
IHostApplicationLifetime appLifetime,
IMigrationPlanBuilder planBuilder,
IMigrator migrator,
IOptions<MyMigrationApplicationOptions> options,
ILogger<MyMigrationApplication> logger,
MigrationManifestSerializer manifestSerializer)
{
_timer = new Stopwatch();
_appLifetime = appLifetime;
// You can choose to assign an instance of the ServerToCloudMigrationPlanBuilder to help you
// add your own filters, mappings, transformers or hooks.
// Refer to the Articles section of this documentation for more details.
_planBuilder = planBuilder.ForServerToCloud();
_migrator = migrator;
_options = options.Value;
_logger = logger;
_manifestSerializer = manifestSerializer;
}
public async Task StartAsync(CancellationToken cancel)
{
var executablePath = Assembly.GetExecutingAssembly().Location;
var currentFolder = Path.GetDirectoryName(executablePath);
if (currentFolder is null)
{
throw new Exception("Could not get the current folder path.");
}
var manifestPath = $"{currentFolder}/manifest.json";
var startTime = DateTime.UtcNow;
_timer.Start();
// Use the methods on your plan builder to add configuration and make customizations.
_planBuilder = _planBuilder
.FromSourceTableauServer(_options.Source.ServerUrl, _options.Source.SiteContentUrl, _options.Source.AccessTokenName, Environment.GetEnvironmentVariable("TABLEAU_MIGRATION_SOURCE_TOKEN") ?? string.Empty)
.ToDestinationTableauCloud(_options.Destination.ServerUrl, _options.Destination.SiteContentUrl, _options.Destination.AccessTokenName, Environment.GetEnvironmentVariable("TABLEAU_MIGRATION_DESTINATION_TOKEN") ?? string.Empty)
.ForServerToCloud()
.WithTableauIdAuthenticationType()
// You can add authentication type mappings here
.WithTableauCloudUsernames<EmailDomainMapping>();
var validationResult = _planBuilder.Validate();
if (!validationResult.Success)
{
_logger.LogError("Migration plan validation failed. {Errors}", validationResult.Errors);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
_appLifetime.StopApplication();
}
// Add mappings
_planBuilder.Mappings.Add<UnlicensedUsersMapping, IUser>();
_planBuilder.Mappings.Add<ProjectRenameMapping, IProject>();
_planBuilder.Mappings.Add<ChangeProjectMapping<IDataSource>, IDataSource>();
_planBuilder.Mappings.Add<ChangeProjectMapping<IWorkbook>, IWorkbook>();
// Add filters
_planBuilder.Filters.Add<DefaultProjectsFilter, IProject>();
_planBuilder.Filters.Add<UnlicensedUsersFilter, IUser>();
_planBuilder.Filters.Add<SharedCustomViewFilter, ICustomView>();
// Add pulled hooks
_planBuilder.Hooks.Add<DataSourceConnectionPulled>();
// Add post-publish hooks
_planBuilder.Hooks.Add<UpdatePermissionsHook<IPublishableDataSource, IDataSourceDetails>>();
_planBuilder.Hooks.Add<UpdatePermissionsHook<IPublishableWorkbook, IWorkbookDetails>>();
_planBuilder.Hooks.Add<BulkLoggingHook<IUser>>();
// Add transformers
_planBuilder.Transformers.Add<MigratedTagTransformer<IPublishableDataSource>, IPublishableDataSource>();
_planBuilder.Transformers.Add<MigratedTagTransformer<IPublishableWorkbook>, IPublishableWorkbook>();
_planBuilder.Transformers.Add<EncryptExtractsTransformer<IPublishableDataSource>, IPublishableDataSource>();
_planBuilder.Transformers.Add<EncryptExtractsTransformer<IPublishableWorkbook>, IPublishableWorkbook>();
_planBuilder.Transformers.Add<SimpleScheduleStartAtTransformer<ICloudExtractRefreshTask>, ICloudExtractRefreshTask>();
_planBuilder.Transformers.Add<CustomViewExcludeDefaultUserTransformer, IPublishableCustomView>();
_planBuilder.Transformers.Add<ActionUrlXmlTransformer, IPublishableWorkbook>();
_planBuilder.Transformers.Add<FlowConnectionServerJsonTransformer, IPublishableFlow>();
_planBuilder.Transformers.Add<ModifyPermissionsTransformer, IPermissionSet>();
// Add initialize migration hooks
_planBuilder.Hooks.Add<SetMigrationContextHook>();
// Add migration action completed hooks
_planBuilder.Hooks.Add<LogMigrationActionsHook>();
// Add batch migration completed hooks
_planBuilder.Hooks.Add<LogMigrationBatchesHook<IUser>>();
_planBuilder.Hooks.Add<LogMigrationBatchesHook<IProject>>();
_planBuilder.Hooks.Add<LogMigrationBatchesHook<IDataSource>>();
_planBuilder.Hooks.Add<LogMigrationBatchesHook<IWorkbook>>();
_planBuilder.Hooks.Add<LogMigrationBatchesHook<ICloudExtractRefreshTask>>();
// Load the previous manifest if possible
var prevManifest = await LoadManifestAsync(manifestPath, cancel);
// Build the plan
var plan = _planBuilder.Build();
// Execute the migration
var result = await _migrator.ExecuteAsync(plan, prevManifest, cancel);
_timer.Stop();
// Save the manifest
await _manifestSerializer.SaveAsync(result.Manifest, manifestPath);
PrintResult(result);
_logger.LogInformation($"Migration Started: {startTime}");
_logger.LogInformation($"Migration Finished: {DateTime.UtcNow}");
_logger.LogInformation($"Elapsed: {_timer.Elapsed}");
Console.WriteLine("Press any key to exit");
Console.ReadKey();
_appLifetime.StopApplication();
}
public Task StopAsync(CancellationToken cancel) => Task.CompletedTask;
/// <summary>
/// Prints the result to console.
/// You can replace this with a logging based method of your choice.
/// </summary>
/// <param name="result">The migration result.</param>
private void PrintResult(MigrationResult result)
{
_logger.LogInformation($"Result: {result.Status}");
// Logging any errors from the manifest.
if (result.Manifest.Errors.Any())
{
_logger.LogError("## Errors detected! ##");
foreach (var error in result.Manifest.Errors)
{
_logger.LogError(error, "Processing Error.");
}
}
foreach (var type in MigrationPipelineContentType.GetMigrationPipelineContentTypes(result.Manifest.PipelineProfile))
{
var contentType = type.ContentType;
_logger.LogInformation($"## {contentType.Name} ##");
// Manifest entries can be grouped based on content type.
foreach (var entry in result.Manifest.Entries.ForContentType(contentType))
{
_logger.LogInformation($"{contentType.Name} {entry.Source.Location} Migration Status: {entry.Status}");
if (entry.Errors.Any())
{
_logger.LogError($"## {contentType.Name} Errors detected! ##");
foreach (var error in entry.Errors)
{
_logger.LogError(error, "Processing Error.");
}
}
if (entry.Destination is not null)
{
_logger.LogInformation($"{contentType.Name} {entry.Source.Location} migrated to {entry.Destination.Location}");
}
}
}
}
private async Task<MigrationManifest?> LoadManifestAsync(string manifestFilepath, CancellationToken cancel)
{
var manifest = await _manifestSerializer.LoadAsync(manifestFilepath, cancel);
if (manifest is not null)
{
ConsoleKey key;
do
{
Console.Write($"Existing Manifest found at {manifestFilepath}. Should it be used? [Y/n] ");
key = Console.ReadKey().Key;
Console.WriteLine(); // make Console logs prettier
} while (key is not ConsoleKey.Enter && key is not ConsoleKey.Y && key is not ConsoleKey.N);
if (key is ConsoleKey.N)
{
return null;
}
_logger.LogInformation($"Using previous manifest from {manifestFilepath}");
return manifest;
}
return null;
}
}
}
namespace Csharp.ExampleApplication.Config
{
public sealed class MyMigrationApplicationOptions
{
public EndpointOptions Source { get; set; } = new();
public EndpointOptions Destination { get; set; } = new();
}
}
namespace Csharp.ExampleApplication.Config
{
public class EndpointOptions
{
public Uri ServerUrl { get; set; } = TableauSiteConnectionConfiguration.Empty.ServerUrl;
public string SiteContentUrl { get; set; } = string.Empty;
public string AccessTokenName { get; set; } = string.Empty;
// Access token configuration should use a secure configuration system.
public string AccessToken { get; set; } = string.Empty;
}
}
{
"source": {
"serverUrl": "http://server",
"siteContentUrl": "",
"accessTokenName": "my-server-token-name",
"accessToken": "my-secret-server-pat"
},
"destination": {
"serverUrl": "https://pod.online.tableau.com",
"siteContentUrl": "site-name",
"accessTokenName": "my-cloud-token-name",
"accessToken": "my-secret-cloud-pat"
}
}
Suggested Reading