Wednesday, December 11, 2013

How to correctly dispose CoreService client objects

Today I was doing some research in the WCF internals and I realized that the stories around the “using” statement are not all true. If you are a C# developer you are familiar with the “using” statement and you know that it will save lines of code since it will explicitly call the Dispose method in the object getting instantiated in the “using” statement (the object must implement the IDisposable interface).

The sentence above is not true for WCF clients like the CoreServiceClient or SessionAwareCoreServiceClient classes because they are typed WCF clients.

Why I cannot use “using” to safely dispose CoreService clients?

The answer is pretty simple the Dispose method calls the Close method and it might throw an exception if a Transport related exception happens. So it is not correct to use “using” because you are not managing exceptions correctly and eventually the underline communication object will remain undisposed.

Sample:

using (channel = new CoreServiceClient("basicHttp_2013")) {
    ComponentData component = (ComponentData)channel.Read("tcm:5-2051", new ReadOptions());
} // Dispose will be called here and an exception might happen.

// This code might not be reached
Console.WriteLine("I have sucessfully retrieved my component");


I know I have some samples where I was using “using” and having a try catch finally block in order to handle exceptions and try to close the channel, it might help but there are still some gaps.
Sample:

using (channel = new CoreServiceClient("basicHttp_2013")) {
    try {
        ComponentData component = (ComponentData)channel.Read("tcm:5-2051", new ReadOptions());
    }
    catch (Exception ex) { // This code will manage any exception that might happen in the try block       
 throw ex;
    }
    finally {
        if (channel.State != CommunicationState.Closed) {
            channel.Close(); // This code might throw an exception if there is a Network issue
        }
    }
               
} // Dispose will be called here and an exception might happen if there is a Network issue.

// This code might not be reached
Console.WriteLine("I have sucessfully retrieved my component");


What is the right way to Dispose a CoreService client object?

Microsoft recommends a way to do it, basically they don’t use the “using” statement and they use a classic way to manage exceptions as back in the .Net Framework 1.1 J

Solution:

CoreServiceClient channel = new CoreServiceClient("basicHttp_2013");
try {
    ComponentData component = (ComponentData)channel.Read("tcm:5-2051", new ReadOptions());

    channel.Close(); //This line might throw a network exception
               
    Console.WriteLine("I have sucessfully retrieved my component");
}
catch (CommunicationException ex) {
    channel.Abort(); //The channel is aborted and the resources released.
}
catch (TimeoutException ex) {
    channel.Abort(); //The channel is aborted and the resources released.
}
catch (Exception ex) {
    channel.Abort(); // The channel is aborted and the resources released.
}


I know it looks tedious but we can put it in an Extension method or a Utility so that we don’t have to write all these catch sentences all the time.

Tuesday, December 10, 2013

Tridion 2013 SP1 - Synchronizing components

Continuing with my posts about Tridion 2013 SP1, I will talk about the new Synchronization API. This API that is available via the Core Services making this API easy to use and very light.

The new synchronization functionality will analyze the component content and the schema and it will infer which operations need to be completed in order to keep them in sync.

Sample:

private static CoreServiceClient channel { get; set; }

using (channel = new CoreServiceClient("basicHttp_2013"))
    try {
        SynchronizeComponent("tcm:5-2051", true);
    }
    finally {
        if (channel.State != CommunicationState.Closed) {
            channel.Close();
        }
    }
}

public static void SynchronizeComponent(string componentId, bool updateAfterSynchronize) {
    Console.WriteLine("Starting Sychronization...");

    SynchronizeOptions options = new SynchronizeOptions() {
        SynchronizeFlags = SynchronizeFlags.All
    };

    SynchronizationResult result;
    if (updateAfterSynchronize) {
        result = channel.SynchronizeWithSchemaAndUpdate(componentId, options);
    }
    else {
        IdentifiableObjectData item = channel.Read(componentId, new ReadOptions());
        result = channel.SynchronizeWithSchema(item, options);
    }

    foreach(SynchronizationActionData action in result.SynchronizationActions) {
        Console.WriteLine();

        Console.WriteLine("Field Name: {0}", action.FieldName);
        Console.WriteLine("Field Index: {0}", action.FieldIndex);
        Console.WriteLine("Action Taken: {0}", action.SynchronizationActionApplied);

        Console.WriteLine();
    }

    Console.WriteLine("Sychronization completed.");
    Console.ReadLine();
}

The first thing we need to consider while writing a synchronizing program would be the Synchronization Options, basically the Synchronization flags.

SynchronizeFlags.All
SynchronizeFlags.ApplyDefaultValuesForMissingMandatoryFields
SynchronizeFlags.ApplyDefaultValuesForMissingNonMandatoryFields
SynchronizeFlags.ApplyFilterXsltToXhtmlFields
SynchronizeFlags.Basic
SynchronizeFlags.ConvertFieldType
SynchronizeFlags.FixNamespace
SynchronizeFlags.RemoveAdditionalValues
SynchronizeFlags.RemoveUnknownFields
SynchronizeFlags.UnknownByClient

You must be careful while using those flags since some of them like  RemoveUnknownFields that will lead you to lose data if is not used carefully.

Other important feature in this new API is the fact that this will return feedback in the form of SynchronizationActionData objects. As you can see in the sample above it will give you information like the FileName, FileIndex and the action that was taken.

You may also notice that in the sample above I am using two different methods SycnrhonizeWithSchema and SynchronizeWithSchemaAndUpdate, the difference is clear, the first one won’t check in any changes and the second one will check in the changes leading you to lose data if not used carefully.

Tridion 2013 SP1 - API improvements

Continuing with my Tridion 2013 SP1 posts I will talk about some API improvements. Below a list containing API improvements.

StreamDownload endpoint can now download external items

Sample:
private static StreamDownloadClient downloadChannel { get; set; }

using (downloadChannel = new StreamDownloadClient("streamDownload_basicHttp_2013")) {
    try {
Stream stream = downloadChannel.DownloadExternalBinaryContent("http://www.sdl.com/Content/themes/common/images/sdl-logo.png");
MemoryStream ms = new MemoryStream();

int b;
while ((b = stream.ReadByte()) != -1) {
ms.WriteByte((byte)b);
}

using (FileStream fs = new FileStream("C:\\sdl-logo.png", FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (BinaryWriter writer = new BinaryWriter(fs)) {
    try {
               writer.Write(ms.ToArray());
    } finally {
        writer.Close();
        fs.Close();
        ms.Close();
    }
}
    }
    finally {
        if (downloadChannel.State != CommunicationState.Closed) {
            downloadChannel.Close();
        }
    }
}

Schemas can be retrieved by Namespace

This is a change in both Core Services and TOM .NET APIs. In this post I will show a Core Service sample.

Sample:

private static CoreServiceClient channel { get; set; }

using (channel = new CoreServiceClient("basicHttp_2013"))
    try {
SchemaData schema = GetSchemaFromNamespace();
    }
    finally {
        if (channel.State != CommunicationState.Closed) {
            channel.Close();
        }
    }
}  

public static SchemaData GetSchemaFromNamespace() {
    LinkToSchemaData schema = channel.GetSchemasByNamespaceUri(PublicationId, "http://www.tridion.com/ContentManager/5.0/DefaultMultimediaSchema", null).FirstOrDefault();
    if (schema != null) {
        return (SchemaData)channel.Read(schema.IdRef, new ReadOptions());
    }
    return null;
}

This change is very important since now we can retrieve schemas by using namespaces (XML like functionality) without having the need to specify tcm uris or webdav urls.

Multimedia Components can be created without specifying a Multimedia Type

This is an small change but it will save you several lines of code J, if the file extension that you are using to create the multimedia component can be mapped to an existing multimedia type, then the API will do it for you.

Sample:

private static CoreServiceClient channel { get; set; }

using (channel = new CoreServiceClient("basicHttp_2013"))
    try {
SchemaData schema = GetSchemaFromNamespace();
CreateMultimediaComponent(schema);
    }
    finally {
        if (channel.State != CommunicationState.Closed) {
            channel.Close();
        }
    }
}  

public static void CreateMultimediaComponent(SchemaData schema) {
    ComponentData multimediaComponent = new ComponentData() {
        Id = TcmUri.UriNull.ToString(),
        Title = Guid.NewGuid().ToString(),
        Schema = new LinkToSchemaData() { IdRef = schema.Id },
        LocationInfo = new LocationInfo() { OrganizationalItem = new LinkToOrganizationalItemData() { IdRef = FolderId } },
        ComponentType = ComponentType.Multimedia,
        BinaryContent = new BinaryContentData() {
            Filename = "sdl-logo.png",
            UploadFromFile = "C:\\sdl-logo.png"
        }
    };

    channel.Create(multimediaComponent, new ReadOptions());
}

Decommissioning Publication Targets

This is a very important change, and it will allow us to mark items that were published to a publication target as unpublished to that publication target in one single operation. It is useful in case you have a publication target that is no longer used and can block other items to be deleted or moved.

Sample:

public static void DecommissioningPublicationTarget(string publicationTargetId) {
    channel.DecommissionPublicationTarget(publicationTargetId);
}

Tridion 2013 SP1 - Import Export API

Continuing with my posts related to the new Tridion 2013 SP1 release, in this opportunity I will talk about the new Import/Export API. Imagine that you want to execute Content Porter like functionality in your programs or scripts, it is now possible by using the Import/Export API.

The Import/Export API is a set of WCF services and endpoints with a set of operations that will allow you to export and import packages. Note that the next release of Content Porter will use the Import/Export API.

Developing a .NET Application that uses the Import/Export API

As the first step, we will need to add a reference to the Import/Export client dlls. You can find the DLLs in [TRIDION_HOME]\bin\client\ImportExport. Add a reference to the following DLLs.

Tridion.ContentManager.ImportExport.Client.dll
Tridion.ContentManager.ImportExport.Common.dll

The second step will be to add the WCF configuration settings to the configuration file, in this case I am using the app.config file.

Tridion.ContentManager.ImportExport.Client.dll.config

Merge the contents of the file above into your configuration file.

As the third step I will create an instance of the ImportExport service client.

ImportExportServiceClient channel = new ImportExportServiceClient("basicHttp_2013");


Creating an Export Package

There are three steps in order to create an export package, select items, define export instructions and create the export package.

In order to select items we will need to use the Selection class and its derived classes that are listed below.

Selection

ApprovalStatusesSelection
GroupsSelection
ItemsSelection
MultimediaTypesSelection
ProcessDefinitionsSelection
SubtreeSelection
TaxonomiesSelection

Sample:
Selection selection = new ItemsSelection(new string[] { "tcm:5-1053" });

In order to define export instructions we will use the ExportInstructions class

Sample:
ExportInstruction exportInstruction = new ExportInstruction() {
    BluePrintMode = BluePrintMode.ExportSharedItemsFromOwningPublication
};


In order to export items we will need to execute the StartExport method available in the ImportExportServiceClient class

Sample:
string processId = channel.StartExport(new Selection[] { selection }, exportInstruction);

Notice that the StartExport method returns the Import/Export process id that will be used later when we perform an import operation or to follow the process status.


Importing a Package

There are two steps in order to import a package, define import instructions and import the page.

In order to define import instructions we need to use the ImportInstruction class.

Sample:
ImportInstruction importInstructions = new ImportInstruction() {
    RunInTransaction = true,
    DiscoverPotentialProblems = true,
    LogLevel = LogLevel.Debug
};


In order to import a package we need to use the StartImport available in the ImportExportServiceClient class

Sample:
string packageFullPath = string.Format("{0}ImportExport\\Packages\\export_{1}.zip", ConfigurationSettings.GetTcmHomeDirectory(), processId);
channel.StartImport(packageFullPath, importInstructions);


This API is easy and very powerful and it will open a lot of possibilities regarding content management.