Solution staging, with asynchronous import and export

Have you ever run into the situation during the import or export of a large solution where the operation times out? If so, you may be a candidate for performing the solution import/export asynchronously. This topic describes how to initiate the asynchronous import or export using the Dataverse SDK for .NET and Web APIs.

Staging a solution

In comparison to importing a solution where the solution is imported and available in the environment right away, staging breaks the import process into more controllable phases. The staging process imports the solution as a "holding" solution where the administrator can decide when to make the staged solution available to users, or to perform an upgrade (in the case of a solution upgrade) in the target environment. Part of the staging process is validation of the staged solution. In this way you can stage the solution, know that the solution is valid, and schedule when to apply that solution or upgrade to the target environment.

Operation Web API Dataverse SDK for .NET
Stage a solution StageSolution StageSolutionRequest

The result of staging the solution will be a collection of validation results indicating success or failure and (if successful) a StageSolutionUploadId to be used in the ImportSolutionAsync call. See the import solution Web API sample code above for an example of how this is done.

public static StageSolutionResults StageSolution(
    IOrganizationService service,
    string solutionFilePath)
{
  // Stage the solution
  var req = new StageSolutionRequest();

  byte[] fileBytes = File.ReadAllBytes(solutionFilePath);
  req["CustomizationFile"] = fileBytes;
  var res = service.Execute(req);

  return (res["StageSolutionResults"] as StageSolutionResults);
}

Solution import

ImportSolution is the action (or message) that performs the synchronous import operation. To execute the import operation asynchronously use ImportSolutionAsync.

Operation Web API Dataverse SDK for .NET
Import a solution ImportSolutionAsync ImportSolutionAsyncRequest

Now let's take a look at some example code that demonstrates ImportSolutionAsync.

public static ImportSolutionAsyncResponse ImportSolution(
    IOrganizationService service,
    StageSolutionResults stagingResults,
    Dictionary<string,Guid> connectionIds,
    Dictionary<string,string> envarValues )
{
    // Import the staged solution
    var componentDetails = stagingResults.SolutionComponentsDetails;

    // TODO These are not referenced in the code but are usefull to explore
    var missingDependencies = stagingResults.MissingDependencies;   // Contains missing dependencies
    var solutionDetails = stagingResults.SolutionDetails;           // Contains solution details

    var connectionReferences = componentDetails.Where(x => string.Equals(x.ComponentTypeName, "connectionreference"));
    var envVarDef = componentDetails.Where(x => string.Equals(x.ComponentTypeName, "environmentvariabledefinition"));
    var envVarValue = componentDetails.Where(x => string.Equals(x.ComponentTypeName, "environmentvariablevalue"));

    var componentParams = new EntityCollection();

    // Add each connection reference to the component parmameters entity collection.
    foreach (var conn in connectionReferences)
    {
        var e = new Entity("connectionreference")
        {
            ["connectionreferencelogicalname"] = conn.Attributes["connectionreferencelogicalname"].ToString(),
            ["connectionreferencedisplayname"] = conn.Attributes["connectionreferencedisplayname"].ToString(),
            ["connectorid"] = conn.Attributes["connectorid"].ToString(),
            ["connectionid"] = connectionIds[conn.ComponentName]
        };
        componentParams.Entities.Add(e);
    }
            
    // Add each environment variable to the component parmameters entity collection.
    foreach (var value in envVarValue)
    {
        var e = new Entity("environmentvariablevalue")
        {
            ["schemaname"] = value.Attributes["schemaname"].ToString(),
            ["value"] = envarValues[value.ComponentName]
        };

        if (value.Attributes.ContainsKey("environmentvariablevalueid"))
        {
            e["environmentvariablevalueid"] = value.Attributes["environmentvariablevalueid"].ToString();
        }
        componentParams.Entities.Add(e);
    }

    // Import the solution
    var importSolutionReq = new ImportSolutionAsyncRequest();
    importSolutionReq.ComponentParameters = componentParams;
    importSolutionReq.SolutionParameters = new SolutionParameters { StageSolutionUploadId = stagingResults.StageSolutionUploadId };
    var response = service.Execute(importSolutionReq) as ImportSolutionAsyncResponse;

    return (response);
}

ImportSolutionAsync shares many input parameters with ImportSolution but adds ComponentParameters and SolutionParameters. ComponentParameters can be used to overwrite the component data in the solution's customization XML file. SolutionParameters can be used to pass the StageSolutionUploadId of a staged solution as was shown in the example Web API code. More information: Staging a solution

The response returned from ImportSolutionAsync contains ImportJobKey and AsyncOperationId. The ImportJobKey value can be used to obtain the import result and the AsyncOperationId value can be used to track the import job status.

public static void CheckImportStatus(
    IOrganizationService service,
    Guid asyncOperationId,
    Guid importJobKey)
{
    // Get solution import status
    var finished = false;
    Entity asyncOperation = null;
    // Wait until the async job is finished
    while (!finished)
    {
        asyncOperation = service.Retrieve("asyncoperation", asyncOperationId, new ColumnSet("statecode", "statuscode"));
        OptionSetValue statecode = (OptionSetValue)asyncOperation["statecode"];
        if (statecode.Value == 3)
        {
            finished = true;
        }
        else
        {
            Thread.Sleep(10000);
        }
    }
    // Solution import completed successfully
    OptionSetValue statuscode = (OptionSetValue)asyncOperation["statuscode"];
    if (statuscode.Value == 30)
    {
        Console.WriteLine("The solution import completed successfully.");
    }
    else if (asyncOperation["statuscode"].ToString() == "31")  // Solution import failed
    {
        Console.WriteLine("The solution import failed.");
        var getLogReq = new RetrieveFormattedImportJobResultsRequest { ImportJobId = importJobKey };
        var importJob = service.Execute(getLogReq) as RetrieveFormattedImportJobResultsResponse;
        // TODO Do something with the import job results
    }
}

Solution export

ExportSolution is the action (or message) that performs the synchronous export operation. To execute the export operation asynchronously use ExportSolutionAsync.

Operation Web API Dataverse SDK for .NET
Export a solution ExportSolutionAsync ExportSolutionAsyncRequest
Download an exported solution file DownloadSolutionExportData DownloadSolutionExportDataRequest

Now let's take a look at some example code that demonstrates ExportSolutionAsync.

// Where 'service' is a pre-configured IOrganizationService instance.
var service = (OrganizationServiceProxy)xsc.CreateOrganizationService();

var req = new OrganizationRequest("ExportSolutionAsync");
req.Parameters.Add("SolutionName", "ExportSolutionAsyncTest");
req.Parameters.Add("Managed", false);
var response = service.Execute(req);

In the response are the AsyncOperationId and ExportJobId parameter values. Use the AsyncOperationId in the response to verify the success (statecode == 3; statuscode == 30) of the asynchronous job. Next, use the DownloadSolutionExportData action (or message) with the ExportJobId value from the export response to download the exported solution file, which is returned in the ExportSolutionFile parameter.

// Where 'service' is a pre-configured IOrganizationService instance.
var service = (OrganizationServiceProxy)xsc.CreateOrganizationService();

var req = new OrganizationRequest("DownloadSolutionExportData");
req.Parameters.Add("ExportJobId", Guid.Parse("a9089b53-a1c7-ea11-a813-000d3a14420d");
var response = service.Execute(req);

See Also

Sample: Solution staging with asynchronous import