Connecting to the SharePoint 2013 REST API from C#

Today I was updating an internal application we use for grabbing lots of Terminology data from SharePoint lists, and exporting it as TBX files for import into CAT tools etc.

This was required as the SharePoint on which it was hosted previously was upgraded from 2010 to 2013.

A small job I thought.

Then I discovered the the ASMX Web Service in SharePoint I used to grab the data previously, are deprecated in SharePoint 2013, probably not a surprise to anyone in the know, but SharePoint happens to be one of my pet hates, so development of it is not something that I tend to keep up to date with.

Anyway, I had to re-jig our application to use the SharePoint REST API, and I thought I’d provide the code here for connecting, as it look a little bit of figuring out.

The below (after you fill in your SharePoint URL, username, password, domain, and name of the list you want to extract data from), will connect and pull back the list contents to an XmlDocument object that you can parse.

XmlNamespaceManager xmlnspm = new XmlNamespaceManager(new NameTable());
Uri sharepointUrl = new Uri("SHAREPOINT URL);

xmlnspm.AddNamespace("atom", "http://www.w3.org/2005/Atom");
xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
xmlnspm.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");

NetworkCredential cred = new System.Net.NetworkCredential("USERNAME", "PASSWORD", "DOMAIN");

HttpWebRequest listRequest = (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "_api/lists/getByTitle('" + "LIST NAME" + "')/items");
listRequest.Method = "GET";
listRequest.Accept = "application/atom+xml";
listRequest.ContentType = "application/atom+xml;type=entry";

listRequest.Credentials = cred;
HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse();
StreamReader listReader = new StreamReader(listResponse.GetResponseStream());
XmlDocument listXml = new XmlDocument();

listXml.LoadXml(listReader.ReadToEnd());

Localization of Email Campaign Content From Eloqua

Eloqua is a marketing automation platform, allowing marketers to easily create campaigns consisting of emails, landing pages, social media etc. via its ‘campaign canvas’.

Campaigns can be created via a WYSIWYG interface, allowing you to visualize marketing campaigns easily as you build them. It also integrates with CRM tools, automatically passing any lead data generated onto your sales staff.

My interest in Eloqua, and specifically its API, relates to the localization of email campaign content. This can be achieved manually by exporting the created email (as a standalone HTML file), then re-importing the localized versions post translation, creating a new version of the original, one for each language you have localized for.

Manual exporting of email content for localization is of course a perfectly valid approach, but the more languages you have, the more manual steps in this process, and the longer it takes, potentially tying up a resource.

The Eloqua REST API can be used to easily reduce the transactional costs related to localization of email campaign content. Using the API, you can quite easily automate the extraction of email content, and potentially send this content directly to your Translation Management System (TMS) such as GlobalSight or WorldServer, or straight to a translation vendor in the absence of a TMS.

The API documentation is pretty good. I also came across this samples project on GitHub released under the Apache license which I was able to use to knock up a proof of concept pretty quickly. It’s written in C# and includes functions to manipulate most asset types in Eloqua.

The email sample code in this library illustrates how to create, edit, and delete emails in Eloqua via the REST API. For example, it’s this easy to grab an Object that represents an email in Eloqua:

EmailClient client = new EmailClient(EloquaInstance, 
                         EloquaUsername, EloquaPassword, 
                         EloquaBaseUrl);

 try
 {
     Email email = client.GetEmail(emailID);
     return email;
 }
 catch (Exception ex)
 {
     // Handle Error...
 }

Some notes:

  • When retrieving the Email Object which represents an email in Eloqua, you need to specify the ID of the email in Eloqua. For automating the localization process, it could be difficult to determine this without user input. What I plan on doing is providing a nice UI so that users see only the emails that they have created in Eloqua (i.e. not all emails created ever), and can click on one and submit it for translation in one click.
  • The Email Object also contains other useful metadata like when the content was created, when it was last updated and by whom, the encoding, and the folder in which this email resides in Eloqua, useful for when you want to upload the localized versions.

So, that’s how easy it is to automate the retrieval of email content from Eloqua. The library I referenced also has support for other asset types like landing pages etc.

Next I plan on using ASP.NET Web API to turn this library into a HTTP service I can use to talk to Eloqua from other applications, such as the application that manages content submission/retrieval from our TMS.

GlobalSight Web Services API: Manipulating Workflows Programmatically

This is part four of my series of posts on the GlobalSight Web Services API. See below for the previous posts:

Here, I’d like to cover how you can manipulate workflows in GlobalSight via the API. For example, dispatching a workflow, accepting tasks in a workflow, or completing or moving workflows on to the next step.

This can be useful if you want to automate specific steps, or if you want to use an interface other than GlobalSight when dealing with workflows.

There are a couple of relevant functions that are pre-requisites when dealing with workflows programmatically:

  • getJobAndWorkflowInfo – This returns details about workflows in a particular job. The ID of the job in question is passed as a parameter to this function. This will return an XML response, detailing as well as the workflow information, some basic information about the job. We need this to get the ID of the workflow which we want to work with, within a particular job.
  • getAcceptedTasksInWorkflow – This will return the task information for a workflow, given the workflow ID (which we would have got from getJobAndWorkflowInfo). With the XML response here, we’ll be able to search for specific tasks in a workflow, and get the task ID – this is what is required in order to manipulate specific tasks in a workflow.

From the above, (I leave the parsing of the XML response as an exercise to the reader), we can begin to perform workflow tasks by using the task ID.

// Accept task
string acceptTask = client.acceptTask(auth, "63781");
Console.WriteLine("Accept Task Response: " + acceptTask);

// Send this worfklow to the 'Write to TM' step
string completeTask = client.completeTask(auth, "63781", "Write to TM");
Console.WriteLine("\n\nComplete Task Response: " + completeTask);

The above is a very simple example of accepting a particular task in a workflow (using the workflow ID), and sending it to the next step in the workflow via ‘Write to TM’.

Write to TM‘ here is the actual text on the arrow in the workflow diagram. I found this syntax strange, but it seems to work.

Capture

It should be noted that the user under which you are logged into the web service with must be on the list of those allowed to accept the task you are trying to accept, you will receive an error otherwise.

The GlobalSight Web Services API documentation has much more information the the types of actions you can perform on workflows, but the above should get you started.

GlobalSight Web Services API: Job Creation

This is the third in my series of posts on the GlobalSight Web Services API. See below for the previous posts:

In this post, I’m going to cover how to actually create jobs in the GlobalSight system via the Web Services API. Creating jobs via the user interface works fine, but if you want to automate the process, this is fully supported via the API. This can be useful if for example you wanted to create a better experience for creating jobs, or a totally different interface – useful if you have people not experienced with GlobalSight who may want to submit content translation or review jobs.

I’ll assume you’re all setup to interact with the API from C# (see the first post above for a basic introduction if you are not).

There are a couple of API calls relevant to job creation:

  • getFileProfileInfo – This returns the list of File Profiles currently available in the system. The response is XML format, listing all File Profiles available in the system, each having an ID, Name and Description. The File Profile is a required parmeter to both the uploadFile and createJob functions.
  • uploadFile – This facilitates upload of a single file to GlobalSight (note it does not actually create a job). This needs to be called once per file.
  • getUniqueJobName – This function essentially takes a job name, and adds a nine digit unique ID to the name. Each job in GlobalSight must have a unique name. If you already have some unique identifier in your job name, you will not need to call this function, but otherwise it is useful for ensuring that there aren’t any clashes between job names.
  • createJob – This is the function that actually creates the job in GlobalSight.

Let’s look at some simple code for job creation using the above listed functions.

			GS_API.AmbassadorService client = new GS_API.AmbassadorService();

			string jobName = "Demo Job (Ignore)";
			string textFilePath = @"C:\Users\Jimmy\Desktop\strings.txt";
			string fileProfileID = "37";
			byte[] fileContents = System.IO.File.ReadAllBytes(textFilePath);

			// Authenticate
			string auth = client.login("TestUser1", "password");

			// Here you would get the file profiles, and find the apt. one for this job
			// I'll leave this to the reader as an exercise, I've assigned a variable above

			// Next, lets ensure uniqueness of our job name
			string uniqueJobName = client.getUniqueJobName(auth, jobName);

			// Upload the file
			client.uploadFile(auth, uniqueJobName, "/files/strings.txt", fileProfileID, fileContents);

			// Create the Job - ensure to use the same job name as your call the uploadFile
			client.createJob(auth, uniqueJobName, "Demo Job", "/files/strings.txt", fileProfileID, "");

A couple of points to note:

  • Each file you upload needs to be converted to a byte array. C# provides the handy function I’ve used above for this purpose.
  • The third parameter to uploadFile, (in this case ‘files/strings.txt‘), is the location to upload the file on the GlobalSight server. There are a few folders created by default for any job, the language code, ‘webservice‘ which indicates the files were uploaded via the API, and a folder named after the job ID. The parameter above is the directory structure inside the job ID folder, this is whatever you want it to be. This is useful, as the files are exported in that same structure post translation, so you can for example retain the directory structure of a translation kit if you so wish. Here’s an example of how the file we uploaded above is stored on the GlobalSight server:

Capture

 

  • The job name you pass to createJob, must be identical to the one you passed to uploadFile. This is how GlobalSight knows which files relate to this job.
  • The final parameter in createJob is the target languages. Leave this empty (as I have above) and GlobalSight will assign all the languages contained in the Localization Profile that is associated with the File Profile you have specified.

That’s it, after you have run the above code, your job is now created in GlobalSight.

This was a very basic introduction to job creation in GlobalSight, showing how a single file can be submitted via the web services API.

I haven’t covered items such as error handling, or even creating jobs that have multiple files, not just one as in my simple example above – I will perhaps cover this in a future post.

GlobalSight Web Services API: Automate Translation Memory Upload

This is the second in my series of posts on the GlobalSight Web Services API. For a brief introduction, and the steps to get setup using the API from C#, see my previous post.

Translation Memories, in TMX format, can take a long time to upload to GlobalSight via the user interface, especially if you have a lot of large ones.

This can be quiet easily automated (and run overnight for example) via the GlobalSight Web Services API.

First, you need to call uploadTmxFile, which just uploads the TMX file to the GlobalSight server, then call importTmxFile, which does the actual import to the system:


GS_API.AmbassadorService client = new GS_API.AmbassadorService();

string demoTmxLocation = @"C:\TMX\demo.tmx";
string tmName = "Main";

// Authenticate
string auth = client.login("TestUser1", "password");

// We need to convert the TMX file to a byte array
byte[] tmxAsBytes = System.IO.File.ReadAllBytes(demoTmxLocation);

// Next, upload the TMX
client.uploadTmxFile(auth, "demo.tmx", tmName, tmxAsBytes);

// Finally, import the TMX
// The final parameter here can be merge, overwrite, or discard
client.importTmxFile(auth, tmName, "merge");

We found this to be up to 25 times faster than uploading TMX files via the user interface, outside of the obvious benefit of not tying a resource up uploading TMX files for a few days.

Next up, I’ll outline how to create translation/review jobs in GlobalSight using the API.

Interacting with the GlobalSight Web Services API from C#

GlobalSight is an open source Globalization Management System (GMS). It’s main purpose is to automate the flow of content to translators, and essentially streamline many of the tasks involved in the translation workflow, scoping, project management, translation, review etc. as well as centralizing translation memories.

Similar GMS systems would be SDL WorldServer (formally Idiom WorldServer before being acquired by SDL in 2008), and SDL TMS.

What differentiates GlobalSight (owned by WeLocalize), is that it is open source. The source code is freely available. It can be customized as you see fit. It also provides a very powerful web services API, that you can use to integrate other systems in your translation workflow with GlobalSight, that’s what I want to introduce here, specifically the steps to get setup from C# to interact with this API.

I’m going to assume you already have an instance of GlobalSight setup, I’d imagine you wouldn’t be interested in this API otherwise.

Setup Steps

  • First off, create a new C# Visual Studio project, a console application will suffice for this tutorial.
  • Next, we need to add a Service Reference to the API. To do this, follow the steps here, using the following URL:

https://<YOUR_SERVER_URL/globalsight/services/AmbassadorWebService?wsdl

Note – The service is called ‘AmbassadorWebService‘ because GlobalSight itself was previously called Ambassador, when it was owned by a company called GlobalSight. The product was renamed GlobalSight after it was taken over by WeLocalize. If you get to the point where you are looking at the (rather large) code base, you will see it littered with references to Ambassador.

  • Once you successfully add the service reference, try to build your project – you will notice it will fail (at time of writing at least anyway):

CaptureThere are duplicate entries in Reference.cs for these two functions, getAttributesByJobId and getProjectIdByFileProfileId.

To fix this issue, just double click on each of the above errors, and comment out the duplicate entries in Reference.cs. This will allow your project to build, but is a pain, as you need to do it each time you update your service reference.

Now that your project successfully builds, you are ready to use the API.

Authentication

Before trying to authenticate with the web service, you may need to do some manual configuration on your GlobalSight instance. The IP Filter feature is enabled by default – this will only allow IP’s on a white list to interact with GlobalSight via the API. There are two options here:

  • Disable the IP filter completely – not recommended.
  • Add the necessary IP ranges to the white list.

See the ‘IP Filter’ section of the web services documentation for more information on this.

Once you complete the above, we should be able to connect to the API via some C# code.

Before calling any operations, the login function must be called with a valid GlobalSight account. This function returns an authentication token which must then be passed as a parameter to all subsequent API calls.

Let’s connect, and call a simple function, getAllUsers, that will give us the list of all user currently in the system, and write it out to the console so that we can see the response:

GS_API.AmbassadorService client = new GS_API.AmbassadorService();

string auth = client.login("TestUser1", "password");
string userinfo = client.getAllUsers(auth);
Console.WriteLine(userinfo);
Console.ReadLine();

If you have done everything correctly, you will see an XML response in your console window detailing all the users currently setup in the system. Most API calls to retrieve information return an XML response, it’s just a matter of parsing it, and doing whatever you want with it then.

Conclusion

Here are my feelings so far, having used this API for the past 3 months to automate different tasks:

  • You can’t rely on the documentation on the Wiki – some function definitions are out of date, some now have extra parameters etc. There is not enough information in the documentation about what each call actually returns (i.e. the format of the XML). I found myself having to run commands to see what they actually return. The API documentation needs some love.
  • Some API call sequences (e.g. for job creation) are really difficult to figure out, e.g. you need to call uploadFile, then createJob. Again, making this clear in the documentation would be better.
  • Some functions expect paramters in XML format, but no example of this format is give. Documentation!
  • For an open source project, there doesn’t seem to be any community. The forums on globalsight.com are not particularly active, and run some really old forum software that is extremely frustrating to use. Maybe there is a community, and I’m just not aware, but it certainly looks like they don’t hang out at globalsight.com.
  • All this aside, the web services API is extremely powerful, and I seriously recommend looking into it if you use GlobalSight – some functions can save you days – e.g. upload of translation memories via the API, as opposed the web interface.

This covered a basic introduction to interacting with the GlobalSight web services API from C#.

In a future post, I’ll cover how to actually create GlobalSight jobs via this API. If there’s anything else you’d like to see covered in a future post, leave a comment below, and I’ll see what I can do.

Basic Software i18n Check List

Part of something my team is currently working on involves defining a standard approach to assessing the world-readiness of a piece of software, be it a brand new product or a legacy product that has existed for a long time.

If your codebase is not correctly internationalized, you will face many problems if you ever want to localize your application for another market.

Most of these are general items that could apply to any product on any platform, be a it a website or a mobile application.

Here’s my first draft of my list. This may help you if you are planning of localizing your application to sell in other markets.

Strings

  • Have all strings been externalized?
  • Have all strings been approved by someone who is a native English speaker?
  • Is there overuse of any abbreviations or acronyms, e.g. ‘App Info’?
  • Is there anywhere that you are concatenating strings together?
  • Punctuation is text too! Are you using any hard-coded punctuation marks (.,!?) these need to be externalized?
  • Have you identified all trademarks, tokens and other ‘special’ elements in your text and communicated to localisation that these shouldn’t be translated?
  • For mobile applications, is your text short enough to fit the potential small screen size of mobile devices? Consider string length expands generally 20% – 30% after translation. Some phrases expand by a lot more than 30%. Abbreviations and acronyms are the worst offenders here.
  • Try to keep resources that shouldn’t be translated separate from those that should.
  • Don’t embed HTML mark up in string resources.

Source Files

  • Source files are a standard file format – no need to invent new resource file formats. A standard resource file format will exist for the platform for which you are developing.
  • Source files are UNICODE.
  • You need to be able to generate your localizable file set in one step, it’s ideally handled by the build process.

General UI

  • Can input fields accept accented characters?
  • Can input fields accept double byte characters?
  • Is there room for buttons to expand if necessary?
  • Are there any UI elements like check boxes or radio buttons that are placed across the screen? It is better to place these vertically.
  • Try to put labels for controls above the control, rather than beside it.

Web Applications

  • Is server set up to send UTF-8 character set?
  • Are all buttons resizable?
  • Are there any mouse-over buttons with text in them?
  • Are other elements on the screen able to expand in size?
  • Are links to help and other user documentation appropriately localized?
  • For HTML based UI, never use absolute positioning.
  • NEVER use HTML tables to achieve layout.
  • Try to avoid using fixed widths with HTML layouts.
  • Don’t use the ‘align’ property to align items left or right.
  • Externalize CSS to a separate file – never use inline CSS.

Images

  • Is there text in any image?
  • Are there any images that depict hand, body or facial gestures? e.g, the thumbs up gesture is offensive in some cultures.
  • Are there any icons or images that are very specific to one region? e.g. an American type mailbox indicating an email.

SQL

  • Use the nvarchar type to store all text.
  • Can all insert and update statements handle unexpected apostrophes? (O’Sullivan, d’accord for example)?
  • Can all insert and update statements handle accented and double byte characters?
  • For MySql, ensure it was installed with UTF-8 as the default character set.

Numbers & Dates

  • Do any parts of your application format numbers using a thousand separator or a decimal point? If so the formatting string must be read from the OS.
  • Do any parts of your application format currencies? If so the formatting string must be read from the OS.
  • Does changing the application locale change all numbers appropriately?
  • Are all time and date formats read from the OS based on locale?
  • Does changing the application locale change all dates appropriately?

Regular Expressions

  • Do any regular expressions use the construct [a-zA-Z]? These are not safe from an i18n point of view.
  • Never use a regular expression to find a price, as the currency will be different for different languages.

Collation/Sorting

  • Do any parts of your application sort alphabetically? If so then they need to be tested for i18n awareness.

C# – Using Bing’s Translation Web Service

Microsoft’s Bing Translator provides a translation web service which can be called via C#. In this post I’ll outline the steps to use this web service, and create a simple application to perform translations.

First, you’ll need to create a valid application ID for your application. This is required in order to be able to call the web service. Head over to Bing’s Developer Centre and sign in with your Windows Live ID. Follow the steps to create a new ID for your application, it’s a simple process and shouldn’t take you any more than 2 minutes.

Next, create a new project in Visual Studio, (I’ve created a simple Windows forms application to demonstrate this, but you could just as easily create a simple console application). You’ll need to add a service reference to your application either way. Do this by right-clicking on your solution and selecting ‘Add Service Reference’. Under ‘Address’, add this:

http://api.microsofttranslator.com/V1/SOAP.svc

Under ‘namespace’, be sure to enter a descriptive name for the service reference. Now, for the code to perform translations (You’ll need to add in the application ID you created earlier in order for this to work):

// Translating from English to German

string textToTranslate = "Hello, world";
string sourceLanguage = "en";
string targetLanguage = "de"
string translatedText = "";

try
{
BingTranslatorService.LanguageServiceClient client = new BingTranslatorService.LanguageServiceClient();
translatedText = client.Translate("Your App ID", textToTranslate, sourceLanguage, targetLanguage);
MessageBox.Show(translatedText);
}
catch (Exception ex)
{
MessageBox.Show("An error has occurred: " + ex.ToString(), "Error");
}

‘BingTranslatorService’ above is whatever you called the service reference added earlier. I’ve created this sample application to show how easy it is to create a simple translation application:

Simple Translation Application

Download the full source code, here. You’ll need to add in your application ID in order for this to work properly.

Localization of C# Applications – Short Introduction

If you plan on releasing your C# application in non-English speaking markets, you will obviously want the UI to display localized strings. When developing applications using .NET, it’s relatively simple to achieve this. In this post, I’ll outline the steps involved in localizing a simple C# application.

.NET applications store string resources in .resx files. These are XML format, with the main advantage being they are human readable and can be opened in any text editor, unlike resource DLL files for example. The only item not human readable in a .resx file, may be an embedded object, like a Bitmap file for example.

Start off by creating a simple Windows Forms application from Visual Studio, it will create an initial form for us to work on.  Select the form and on the ‘Properties’ dialog look for the ‘Localizable’ property and set it to ‘true’. You may also notice the ‘Language’ property, leave this set to ‘Default’ for the moment.

Next, add a button to the form and add some text to it, something simple, for example:

Simple Localizable Form

If you take a look under your form in ‘Solution Explorer’, you will notice a .resx file has been created. It will be named FormName.resx, open this up and search for the string on your button and you will see how it is stored. Now to add the equivalent German strings (or any other language you fancy!).

Recall the forms ‘Language’ property mentioned earlier, you will find it under ‘Properties’ when you’ve got the form selected. In the dropdown, change the value to ‘German’. You will not notice any visible changes, but you can now edit the strings on the form to represent the German equivalents. Do this for as many languages as you want to. Once you’ve done this, take a look under your form in ‘Solution Explorer’, you will notice that a new .resx file has been added automatically, FormName.de.resx. This will contain your German strings. You should note here that you can also change the layout of the form to include any required changes, in the event some strings are longer in certain languages, e.g. Greek.

Now when your application is run on a German operating system, the strings displayed will be automatically taken from FormName.de.resx, rather than FormName.resx.

A note about locale selection

The UI language used in Windows is a function of the CurrentUICulture setting. In order to see the German strings actually display, you would need to install a German language pack, and change your regional settings, or we could just set our locale programmatically in our application.

In order to test your German strings display correctly, first add the following imports:

using System.Globalization;
using System.Threading;

Then add the following code to your form initialization function, (before InitializeComponent()):

// Sets the UI culture to German (Germany).
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");

This will make our application believe it’s running on a German locale. Now run your application, you should see your German strings displayed:

German Strings Displayed

This post outlined the very basics of localizing .NET applications. In future posts, I plan on expanding this a bit to advanced topics such as avoiding common internationalization issues.