Cyotek Development Bloghttps://devblog.cyotek.com/tag/rest/atom.xml2021-02-11T17:15:09ZMigrating from Azure translation API version 2 to 3urn:uuid:78c834ff-f60c-4299-99ca-bd299f9af9622019-04-11T18:31:15Z2019-04-11T18:31:15Z<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/translatetext-2a.png" class="gallery" title="Updated sample project working with the version 3 API" ><img src="https://images.cyotek.com/image/thumbnail/devblog/translatetext-2a.png" alt="Updated sample project working with the version 3 API" decoding="async" loading="lazy" /></a><figcaption>Updated sample project working with the version 3 API</figcaption></figure>
<p>Almost two years ago I wrote a post describing how to <a href="/post/translating-text-with-azure-cognitive-services">translate
text using Azure cognitive services</a>, however the API it uses
is to be switched off and so I needed to migrate from the
version 2 API to version 3.</p>
<p>Whilst most of the code I post on this blog is used in one form
or another, I've been using the <code>TranslationClient</code> client
presented in that article as-is for the past two years. <em>OK, I
changed the namespace. But otherwise it's identical.</em></p>
<blockquote>
<p>Although I have finally stopped using older classes such as
<code>HttpWebRequest</code> in favour of <code>HttpClient</code> and
<code>async</code>/<code>await</code>, so far I haven't updated existing code to
make use of them. As I noted above, I'm still using the
<code>TranslationClient</code> class introduced in my previous blog post
and at this time I simply want to retrofit the class to use
the V3 API.</p>
<p>This also means I'm still not using any of the extra features
offered by the API even though it probably makes more sense to
combine some of the functionality now (as Microsoft have done
with the API's themselves), however as I want the new class to
be a drop in replacement for the old I have left this as an
exercise for a future blog post</p>
</blockquote>
<p>The official migration documentation can be found on
<a href="https://docs.microsoft.com/en-us/azure/cognitive-services/translator/migrate-to-v3" rel="external nofollow noopener">Microsoft's site</a>.</p>
<h2 id="dependencies">Dependencies</h2>
<p>At first glance, the biggest change between v2 and v3 is the
output format. Previously it was XML, now JSON. This is a bit
of a double edged sword as while JSON is the standard these
days, XML parsing is built into the .NET framework and JSON is
not (yet).</p>
<p><a href="https://www.newtonsoft.com/json" rel="external nofollow noopener">JSON.net</a> is a fine library for working with JSON, but
thanks to the way NuGet works it quickly spread like a plague
though my application libraries, and so I ended up blanket
purging it. Instead, for some time I've been using a modified
version of the fantastic <a href="https://github.com/toptensoftware/PetaJson" rel="external nofollow noopener">PetaJson</a> which is a single <code>.cs</code>
file I embed in any projects that require JSON support.</p>
<p>The switch from XML to JSON does mean that a reference to
<code>System.Runtime.Serialization</code> is no longer required which is a
plus.</p>
<h2 id="new-end-points">New end points</h2>
<p>I'm already only using a limited subset of functionality via
three separate version 2 API's. In version 3, two of these have
been consolidated into one. The following table outlines the
different endpoints</p>
<table>
<thead>
<tr>
<th>v2 Method</th>
<th>v3 Method</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Translate</code></td>
<td><code>translate</code></td>
</tr>
<tr>
<td><code>GetLanguageNames</code></td>
<td><code>languages</code></td>
</tr>
<tr>
<td><code>GetLanguagesForTranslate</code></td>
<td><code>languages</code></td>
</tr>
</tbody>
</table>
<p>In addition, the base URI has changed from
<code>https://api.microsofttranslator.com/v2/http.svc/</code> to
<code>https://api.cognitive.microsofttranslator.com/</code>.</p>
<h3 id="regional-end-points">Regional end points</h3>
<p>Although you can simply use the default base URI above and have
Azure choose an appropriate data centre, you can optionally
specify a specific region as follows.</p>
<table>
<thead>
<tr>
<th>Region</th>
<th>Base URL</th>
</tr>
</thead>
<tbody>
<tr>
<td>North America</td>
<td><code>api-nam.cognitive.microsofttranslator.com</code></td>
</tr>
<tr>
<td>Europe</td>
<td><code>api-eur.cognitive.microsofttranslator.com</code></td>
</tr>
<tr>
<td>Asia Pacific</td>
<td><code>api-apc.cognitive.microsofttranslator.com</code></td>
</tr>
</tbody>
</table>
<h2 id="specifying-an-api-version">Specifying an API version</h2>
<p>All requests to the API (apart from the initial authentication)
need to include the <code>api-version</code> query parameter, although
currently the only supported value is <code>3.0</code>. Failure to include
this will result in a <code>400</code> status code along with a body
similar to the following</p>
<figure class="lang-json highlight"><figcaption><span>json</span></figcaption><pre class="code">
<span class="symbol">{</span><span class="string">&quot;error&quot;</span><span class="symbol">:</span><span class="symbol">{</span><span class="string">&quot;code&quot;</span><span class="symbol">:</span><span class="number">400021</span><span class="symbol">,</span><span class="string">&quot;message&quot;</span><span class="symbol">:</span><span class="string">&quot;The API version parameter is not valid.&quot;</span><span class="symbol">}</span><span class="symbol">}</span>
</pre>
</figure>
<h2 id="authentication">Authentication</h2>
<p>I'm using the same code to obtain an authentication token as I
was for the version 2 API, as far as I know this isn't going to
be removed - please see the <a href="/post/translating-text-with-azure-cognitive-services#creating-a-login-token">original article</a> for details.</p>
<blockquote>
<p>According to the <a href="https://docs.microsoft.com/en-us/azure/cognitive-services/translator/reference/v3-0-reference" rel="external nofollow noopener">reference</a> instead of generating an
access token from your API key, you can pass the key directly
via the <code>Ocp-Apim-Subscription-Key</code>. Given that this was also
supported in the v2 API I'm not sure why I choose the more
convoluted method of generating an access token, something
else to potentially refactor away in a future update,
especially given the fact that exact code has had a bug in it
for over two years now.</p>
</blockquote>
<h2 id="two-year-old-bugs-and-why-you-shouldnt-blindly-ignore-resharper">Two-year old bugs and why you shouldn't blindly ignore ReSharper</h2>
<p>Imagine my surprise when the first thing that happened after
changing URI constants was the program crashed in a place I
wasn't expecting! As it turns out, there was a bug in the
original code and which just happened to have worked up until
now.</p>
<p>When requesting an API token, the token is the body of the
response. The class has a private <code>GetResponseString</code> string
method for pulling this out (and incidentally is also useful for
debugging purposes). This method checks to see if a character
set is defined on the <code>HttpWebResponse</code> (via the <code>CharacterSet</code>
property) and if so uses that to read text appropriately,
otherwise falls back to UTF-8.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="comment">// WARNING! Broken code below</span>

<span class="keyword">private</span> <span class="keyword">string</span> GetResponseString<span class="symbol">(</span>HttpWebResponse response<span class="symbol">)</span>
<span class="symbol">{</span>
 Encoding encoding<span class="symbol">;</span>
 <span class="keyword">string</span> result<span class="symbol">;</span>

 <span class="comment">// ReSharper disable once AssignNullToNotNullAttribute</span>
 encoding <span class="symbol">=</span> <span class="symbol">!</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>response<span class="symbol">.</span>CharacterSet<span class="symbol">)</span> <span class="symbol">?</span> Encoding<span class="symbol">.</span>UTF<span class="number">8</span> <span class="symbol">:</span> Encoding<span class="symbol">.</span>GetEncoding<span class="symbol">(</span>response<span class="symbol">.</span>CharacterSet<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> response<span class="symbol">.</span>GetResponseStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>StreamReader reader <span class="symbol">=</span> <span class="keyword">new</span> StreamReader<span class="symbol">(</span>stream<span class="symbol">,</span> encoding<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> reader<span class="symbol">.</span>ReadToEnd<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>At least, that was the theory. In reality, if a character set is
present UFT-8 is always used, and if not present it tries to use
the null object and crashes. ReSharper very helpfully warns you
of this very thing with its &quot;<em>Possible 'null' assignment to
entity marked with 'NotNull' attribute</em>&quot; warning, and I
completely ignored as I'm so used to seeing it with various file
API's that evidently I treat it as noise without paying
attention.</p>
<p><em>Oops.</em> Well, it's fixed now!</p>
<h2 id="getting-the-list-of-languages">Getting the list of languages</h2>
<p>The <code>GetLanguagesForTranslate</code> API has been replaced with
<code>languages</code> and rather than returning a simple list of language
codes, it now returns a little bit more - at the most basic
level it includes the name (native and localised) and the
language direction.</p>
<figure class="lang-json highlight"><figcaption><span>json</span></figcaption><pre class="code">
<span class="symbol">{</span>
 <span class="string">&quot;translation&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;af&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;name&quot;</span><span class="symbol">:</span> <span class="string">&quot;Afrikaans&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;nativeName&quot;</span><span class="symbol">:</span> <span class="string">&quot;Afrikaans&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;dir&quot;</span><span class="symbol">:</span> <span class="string">&quot;ltr&quot;</span>
 <span class="symbol">}</span><span class="symbol">,</span>
 <span class="string">&quot;ar&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;name&quot;</span><span class="symbol">:</span> <span class="string">&quot;Arabic&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;nativeName&quot;</span><span class="symbol">:</span> <span class="string">&quot;العربية&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;dir&quot;</span><span class="symbol">:</span> <span class="string">&quot;rtl&quot;</span>
 <span class="symbol">}</span><span class="symbol">,</span>
 <span class="string">&quot;bg&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;name&quot;</span><span class="symbol">:</span> <span class="string">&quot;Bulgarian&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;nativeName&quot;</span><span class="symbol">:</span> <span class="string">&quot;Български&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;dir&quot;</span><span class="symbol">:</span> <span class="string">&quot;ltr&quot;</span>
 <span class="symbol">}</span><span class="symbol">,</span>

 <span class="symbol">!</span> SNIP <span class="symbol">!</span>

 <span class="string">&quot;yue&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;name&quot;</span><span class="symbol">:</span> <span class="string">&quot;Cantonese (Traditional)&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;nativeName&quot;</span><span class="symbol">:</span> <span class="string">&quot;粵語 (繁體中文)&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;dir&quot;</span><span class="symbol">:</span> <span class="string">&quot;ltr&quot;</span>
 <span class="symbol">}</span><span class="symbol">,</span>
 <span class="string">&quot;zh-Hans&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;name&quot;</span><span class="symbol">:</span> <span class="string">&quot;Chinese Simplified&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;nativeName&quot;</span><span class="symbol">:</span> <span class="string">&quot;简体中文&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;dir&quot;</span><span class="symbol">:</span> <span class="string">&quot;ltr&quot;</span>
 <span class="symbol">}</span><span class="symbol">,</span>
 <span class="string">&quot;zh-Hant&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;name&quot;</span><span class="symbol">:</span> <span class="string">&quot;Chinese Traditional&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;nativeName&quot;</span><span class="symbol">:</span> <span class="string">&quot;繁體中文&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;dir&quot;</span><span class="symbol">:</span> <span class="string">&quot;ltr&quot;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Using the <code>scope</code> query parameter, you specify a comma separated
list of group information to return. The available group names
are <code>translation</code>, <code>transliteration</code> and <code>dictionary</code>. As I'm
only interested in translations, that is the only scope I'll
provide. As an aside, if you omit this parameter it will act as
if you had specified all scopes.</p>
<p>Our original <code>GetLanguages</code> function changes to this</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> GetLanguages<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> results<span class="symbol">;</span>
 HttpWebRequest request<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CheckToken<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 request <span class="symbol">=</span> WebRequest<span class="symbol">.</span>CreateHttp<span class="symbol">(</span><span class="string">&quot;https://api.cognitive.microsofttranslator.com/languages?api-version=3.0&amp;scope=translation&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Headers<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;Authorization&quot;</span><span class="symbol">,</span> <span class="string">&quot;Bearer &quot;</span> <span class="symbol">+</span> _authorizationToken<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Accept <span class="symbol">=</span> <span class="string">&quot;application/json&quot;</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>HttpWebResponse response <span class="symbol">=</span> <span class="symbol">(</span>HttpWebResponse<span class="symbol">)</span>request<span class="symbol">.</span>GetResponse<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> response<span class="symbol">.</span>GetResponseStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>StreamReader reader <span class="symbol">=</span> <span class="keyword">new</span> StreamReader<span class="symbol">(</span>stream<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetResponseEncoding<span class="symbol">(</span>response<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">&gt;&gt;&gt;</span> jsonEntities<span class="symbol">;</span>
 Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">&gt;&gt;</span> languages<span class="symbol">;</span>

 jsonEntities <span class="symbol">=</span> <span class="keyword">new</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">&gt;&gt;&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 Json<span class="symbol">.</span>ParseInto<span class="symbol">(</span>reader<span class="symbol">,</span> jsonEntities<span class="symbol">)</span><span class="symbol">;</span>

 results <span class="symbol">=</span> jsonEntities<span class="symbol">.</span>TryGetValue<span class="symbol">(</span><span class="string">&quot;translation&quot;</span><span class="symbol">,</span> <span class="keyword">out</span> languages<span class="symbol">)</span> <span class="symbol">?</span> languages<span class="symbol">.</span>Keys<span class="symbol">.</span>ToArray<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">:</span> <span class="keyword">new</span> <span class="keyword">string</span><span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> results<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>I have to admit, I'm not a fan of this awful &quot;dictionary of
dictionary of dictionaries&quot; nonsense. But at the <code>translations</code>
element is an object with language codes as property names
rather than an array, offhand I'm not sure how I'd get that
converted into a strongly typed keyed collection, regardless of
if using PetaJson or JSON.net - I will be revisiting this in a
future post.</p>
<p>I'm also not a fan of having to load the entire JSON string into
parsed objects and then discard most of it. PetaJSON has a
<code>Reader</code> class which behaves very much like <code>XmlReader</code> and
ideally I should have used that to walk the JSON.</p>
<blockquote>
<p>In the above code, I've left in place the obtaining and
setting an authentication token. However, unlike the v2 API,
authentication is <em>not</em> required for using the <code>/languages</code>
API. It is still required for actions that requiring billing,
such as the <code>/translate</code> API itself.</p>
</blockquote>
<h2 id="getting-language-names">Getting language names</h2>
<p>As I've laboriously noted above, in the v3 API, Microsoft
combined the original <code>GetLanguagesForTranslate</code> and
<code>GetLanguageNames</code> into a single API call and so getting the
actual names for each language is a simple case of taking the
above code and pulling out a little more information from the
nest of <del>vipers</del> dictionaries.</p>
<figure class="lang-json highlight"><figcaption><span>json</span></figcaption><pre class="code">
<span class="symbol">{</span>
 <span class="string">&quot;translation&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;ar&quot;</span><span class="symbol">:</span> <span class="symbol">{</span>
 <span class="string">&quot;name&quot;</span><span class="symbol">:</span> <span class="string">&quot;Arabic&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;nativeName&quot;</span><span class="symbol">:</span> <span class="string">&quot;العربية&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;dir&quot;</span><span class="symbol">:</span> <span class="string">&quot;rtl&quot;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Remembering that the JSON output includes <code>name</code>, <code>nativeName</code>
and <code>dir</code> attributes; this time around, we're interested in
pulling out the <code>name</code> field. This is the display name in the
requested locale (<code>nativeName</code> is the display name in the locale
of the language itself). But how do you specify the requested
locale? In v2, you used the <code>locale</code> query parameter but for v3
it is done by setting the <code>Accept-Language</code> header.</p>
<p>There's also another important difference - with the v2 API, you
made a <code>POST</code> and the body had a list of the languages for which
you wanted localised names for. However, for v3 there is no such
filtering available, it will return localised names for all
supported languages.</p>
<p>As I'm trying to keep the same behaviour that means I'm going to
need to add this filtering myself (although by the time I'd
finished this article I was questioning my reasoning for not
just rewriting the class from scratch in a modern fashion and
forcing our internal application deal with it).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> GetLocalizedLanguageNames<span class="symbol">(</span><span class="keyword">string</span> locale<span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> languages<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> results<span class="symbol">;</span>
 HttpWebRequest request<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CheckToken<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 request <span class="symbol">=</span> WebRequest<span class="symbol">.</span>CreateHttp<span class="symbol">(</span><span class="string">&quot;https://api.cognitive.microsofttranslator.com/languages?api-version=3.0&amp;scope=translation&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Headers<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;Authorization&quot;</span><span class="symbol">,</span> <span class="string">&quot;Bearer &quot;</span> <span class="symbol">+</span> _authorizationToken<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Headers<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;Accept-Language&quot;</span><span class="symbol">,</span> locale<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Accept <span class="symbol">=</span> <span class="string">&quot;application/json&quot;</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>HttpWebResponse response <span class="symbol">=</span> <span class="symbol">(</span>HttpWebResponse<span class="symbol">)</span>request<span class="symbol">.</span>GetResponse<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> response<span class="symbol">.</span>GetResponseStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>StreamReader reader <span class="symbol">=</span> <span class="keyword">new</span> StreamReader<span class="symbol">(</span>stream<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetResponseEncoding<span class="symbol">(</span>response<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">&gt;&gt;&gt;</span> jsonEntities<span class="symbol">;</span>
 Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">&gt;&gt;</span> responseLanguages<span class="symbol">;</span>

 jsonEntities <span class="symbol">=</span> <span class="keyword">new</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">&gt;&gt;&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 Json<span class="symbol">.</span>ParseInto<span class="symbol">(</span>reader<span class="symbol">,</span> jsonEntities<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>jsonEntities<span class="symbol">.</span>TryGetValue<span class="symbol">(</span><span class="string">&quot;translation&quot;</span><span class="symbol">,</span> <span class="keyword">out</span> responseLanguages<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 results <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">string</span><span class="symbol">[</span>languages<span class="symbol">.</span>Length<span class="symbol">]</span><span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> languages<span class="symbol">.</span>Length<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">&gt;</span> languageData<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>responseLanguages<span class="symbol">.</span>TryGetValue<span class="symbol">(</span>languages<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">,</span> <span class="keyword">out</span> languageData<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 results<span class="symbol">[</span>i<span class="symbol">]</span> <span class="symbol">=</span> languageData<span class="symbol">[</span><span class="string">&quot;name&quot;</span><span class="symbol">]</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 results <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">string</span><span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> results<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>I <em>really</em> don't like this code. Too late for second guessing
now though!</p>
<h2 id="translating-text">Translating text</h2>
<p>The final part of this migration exercise is the actual text
translation. Again, there's some small differences from v2 but
nothing too troublesome.</p>
<p>Firstly, the text to translate is no longer a query parameter,
but part of the body text as a JSON object. This makes sense in
a way as for v3, Microsoft merged the <code>Translate</code> and
<code>TranslateArray</code> API's into one. But it still means it's slightly
more awkward to use.</p>
<p>The body JSON is simple enough and looks like this</p>
<figure class="lang-json highlight"><figcaption><span>json</span></figcaption><pre class="code">
<span class="symbol">[</span>
 <span class="symbol">{</span><span class="string">&quot;Text&quot;</span><span class="symbol">:</span> <span class="string">&quot;Hello World&quot;</span><span class="symbol">}</span>
<span class="symbol">]</span>
</pre>
</figure>
<blockquote>
<p>Note that for some reason the <code>Text</code> attribute is in title
case rather than lower case in all the other examples</p>
</blockquote>
<p>The language to convert from and to are still specified via the
<code>from</code> and <code>to</code> query parameters as with v2.</p>
<p>The response is a JSON array, similar to the following.</p>
<figure class="lang-json highlight"><figcaption><span>json</span></figcaption><pre class="code">
<span class="symbol">[</span>
 <span class="symbol">{</span>
 <span class="string">&quot;translations&quot;</span><span class="symbol">:</span> <span class="symbol">[</span>
 <span class="symbol">{</span>
 <span class="string">&quot;text&quot;</span><span class="symbol">:</span> <span class="string">&quot;Hallo Welt&quot;</span><span class="symbol">,</span>
 <span class="string">&quot;to&quot;</span><span class="symbol">:</span> <span class="string">&quot;de&quot;</span>
 <span class="symbol">}</span>
 <span class="symbol">]</span>
 <span class="symbol">}</span>
<span class="symbol">]</span>
</pre>
</figure>
<p>However, it can include a <em>great</em> deal more information
depending on if you use auto detection, transliteration and
more. I'm not covering any of that here in my 1:1 conversion.</p>
<p>As I don't really want to manually write JSON and deal with
having to escape text, I'll create an interim object and use
PetaJson to write it out. I've made it private for now as it is
only used inside of this method. It was also at this point I
threw up my hands in disgust at more dictionary of dictionaries
and wrote a few limited POCO's for the response output that I'm
interested in.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">partial</span> <span class="keyword">class</span> TranslationClient
<span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">class</span> TextInput
 <span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">string</span> _text<span class="symbol">;</span>

 <span class="keyword">public</span> TextInput<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span> <span class="symbol">}</span>

 <span class="keyword">public</span> TextInput<span class="symbol">(</span><span class="keyword">string</span> text<span class="symbol">)</span>
 <span class="symbol">{</span>
 _text <span class="symbol">=</span> text<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Json<span class="symbol">(</span><span class="string">&quot;Text&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">string</span> Text
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _text<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span> <span class="symbol">{</span> _text <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 
 <span class="keyword">private</span> <span class="keyword">class</span> TranslationResult
 <span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">string</span> _targetLanguage<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">string</span> _text<span class="symbol">;</span>

 <span class="symbol">[</span>Json<span class="symbol">(</span><span class="string">&quot;to&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">string</span> TargetLanguage
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _targetLanguage<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span> <span class="symbol">{</span> _targetLanguage <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Json<span class="symbol">(</span><span class="string">&quot;text&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">string</span> Text
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _text<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span> <span class="symbol">{</span> _text <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">class</span> TranslateResponse
 <span class="symbol">{</span>
 <span class="keyword">private</span> TranslationResult<span class="symbol">[</span><span class="symbol">]</span> _translations<span class="symbol">;</span>

 <span class="keyword">public</span> TranslationResult<span class="symbol">[</span><span class="symbol">]</span> Translations
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _translations<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span> <span class="symbol">{</span> _translations <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>With the helpers in place, I can now expand the <code>Translate</code>
method to work with the v3 API</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">string</span> Translate<span class="symbol">(</span><span class="keyword">string</span> text<span class="symbol">,</span> <span class="keyword">string</span> <span class="keyword">from</span><span class="symbol">,</span> <span class="keyword">string</span> to<span class="symbol">)</span>
<span class="symbol">{</span>
 HttpWebRequest request<span class="symbol">;</span>
 <span class="keyword">string</span> result<span class="symbol">;</span>
 <span class="keyword">string</span> queryString<span class="symbol">;</span>
 TranslateResponse<span class="symbol">[</span><span class="symbol">]</span> responses<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CheckToken<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 queryString <span class="symbol">=</span> <span class="string">&quot;?api-version=3.0&amp;from=&quot;</span> <span class="symbol">+</span> <span class="keyword">from</span> <span class="symbol">+</span> <span class="string">&quot;&amp;to=&quot;</span> <span class="symbol">+</span> to<span class="symbol">;</span>

 request <span class="symbol">=</span> WebRequest<span class="symbol">.</span>CreateHttp<span class="symbol">(</span><span class="string">&quot;https://api.cognitive.microsofttranslator.com/translate&quot;</span> <span class="symbol">+</span> queryString<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Headers<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;Authorization&quot;</span><span class="symbol">,</span> <span class="string">&quot;Bearer &quot;</span> <span class="symbol">+</span> _authorizationToken<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>ContentType <span class="symbol">=</span> <span class="string">&quot;application/json&quot;</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Accept <span class="symbol">=</span> <span class="string">&quot;application/json&quot;</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Method <span class="symbol">=</span> WebRequestMethods<span class="symbol">.</span>Http<span class="symbol">.</span>Post<span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> request<span class="symbol">.</span>GetRequestStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>TextWriter writer <span class="symbol">=</span> <span class="keyword">new</span> StreamWriter<span class="symbol">(</span>stream<span class="symbol">,</span> Encoding<span class="symbol">.</span>UTF<span class="number">8</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Json<span class="symbol">.</span>Write<span class="symbol">(</span>writer<span class="symbol">,</span> <span class="keyword">new</span><span class="symbol">[</span><span class="symbol">]</span> <span class="symbol">{</span> <span class="keyword">new</span> TextInput<span class="symbol">(</span>text<span class="symbol">)</span> <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">using</span> <span class="symbol">(</span>HttpWebResponse response <span class="symbol">=</span> <span class="symbol">(</span>HttpWebResponse<span class="symbol">)</span>request<span class="symbol">.</span>GetResponse<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> response<span class="symbol">.</span>GetResponseStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>StreamReader reader <span class="symbol">=</span> <span class="keyword">new</span> StreamReader<span class="symbol">(</span>stream<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetResponseEncoding<span class="symbol">(</span>response<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 responses <span class="symbol">=</span> Json<span class="symbol">.</span>Parse<span class="symbol">&lt;</span>TranslateResponse<span class="symbol">[</span><span class="symbol">]</span><span class="symbol">&gt;</span><span class="symbol">(</span>reader<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 result <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>responses <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> responses<span class="symbol">.</span>Length <span class="symbol">==</span> <span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 TranslateResponse translation<span class="symbol">;</span>

 translation <span class="symbol">=</span> responses<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>translation<span class="symbol">.</span>Translations <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> translation<span class="symbol">.</span>Translations<span class="symbol">.</span>Length <span class="symbol">==</span> <span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> translation<span class="symbol">.</span>Translations<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">.</span>Text<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Much more complicated than the previous version! Still it works.
Doesn't it?</p>
<h2 id="wait-the-output-is-different">Wait, the output is different?</h2>
<p>After I had the conversion complete, I noticed that one of the
variations of Klingon wasn't listed in the language list any
more. Curious, I ran the original application and back it
popped. At first I thought they might have been combined with
the new script support but this doesn't seem to be the case.
Fortunately, no user has asked for our software to be in
Klingon, so I can ignore this omission!</p>
<p>I also noted the codes for Chinese have changed - in v2 they are
<code>zh-CHS</code> (Simplified) and <code>zh-CHT</code> (Traditional), but in v3 they
are now <code>zh-Hans</code> and <code>zh-Hant</code>. Apparently the latter is the
proper way of doing things now, but this a breaking change for
me as various shell scripts and data files refer to the old
style and will need changing.</p>
<p>Even more oddly however, the first part of the &quot;Major-General's
Song&quot; that defaults in the demonstration program now translates
differently in the two versions</p>
<p>English Text:</p>
<figure class="lang-text highlight"><figcaption><span>text</span></figcaption><pre class="code">
I am the very model of a modern Major-General,
I&#39;ve information vegetable, animal, and mineral,
I know the kings of England, and I quote the fights historical
From Marathon to Waterloo, in order categorical;a
I&#39;m very well acquainted, too, with matters mathematical,
I understand equations, both the simple and quadratical,
About binomial theorem I&#39;m teeming with a lot o&#39; news,
With many cheerful facts about the square of the hypotenuse.
</pre>
</figure>
<p>German Translation (version 2 API):</p>
<figure class="lang-text highlight"><figcaption><span>text</span></figcaption><pre class="code">
Ich bin sehr Modell modern Major-General,
Ich habe Informationen Gem&#252;se, Tiere und Mineralien,
Ich kenne die K&#246;nige von England, und ich zitiere die historischen K&#228;mpfe
Vom Marathon zu Waterloo in Reihenfolge kategorische; ein
Ich bin sehr gut, auch mit mathematischen Fragen kennen,
Ich verstehe Gleichungen, einfache und quadratischem,
&#220;ber binomiale Theorem bin ich mit viel o-Nachrichten nur so wimmelt,
Mit vielen fr&#246;hlichen Fakten &#252;ber das Quadrat der Hypotenuse.
</pre>
</figure>
<p>German Translation (version 3 API):</p>
<figure class="lang-text highlight"><figcaption><span>text</span></figcaption><pre class="code">
Ich bin das Vorbild eines modernen Generalstabs,
Ich habe Informationen pflanzliche, tierische und mineralische,
Ich kenne die K&#246;nige von England, und ich zitiere die K&#228;mpfe historisch
Von Marathon bis Waterloo, in der Reihenfolge kategorisch; ein
Ich bin sehr gut mit den Fragen mathematisch,
Ich verstehe Gleichungen, sowohl die einfachen als auch die quadratischen,
&#220;ber binomiale Theorem Ich bin voller viel o &#39; News,
Mit vielen fr&#246;hlichen Fakten &#252;ber das Quadrat der Hypotenuse.
</pre>
</figure>
<p>I have no idea as to why this is, I assume it's because
according to the documentation it uses &quot;neural machine
translation by default&quot;, although it doesn't seem to state how
to disable it.</p>
<p>In the end, I updated the demonstration program to include both
the v2 and v3 classes so you I could toggle between them to
easily see the differences.</p>
<h2 id="to-be-continued">To be continued</h2>
<p>Attached to this post is an upgraded demonstration project which
is a little more robust than the methods above, it is also
available on our GitHub page. Note that you will need to use
your own API key, the one in the demonstration program has been
invalidated.</p>
<p>I'm really not a fan of the new code and have made a note on my
blog Todo list to revisit this topic in the future and rewrite
it properly using modern techniques, and also to investigate
some of the additional functionality the translation API offers.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2019-04-11 - First published</li>
<li>2020-11-22 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/migrating-from-azure-translation-api-version-2-to-3 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comAnnouncing MantisSharp, a .NET client for using the MantisBT REST APIurn:uuid:333839c8-ea35-4ed7-b124-2eac848bb2d22017-07-10T18:28:52Z2017-07-10T18:28:52Z<p>I've released a new open source project named MantisSharp, a
simple .NET client for working with the recently introduced REST
API for <a href="https://mantisbt.org/" rel="external nofollow noopener">Mantis Bug Tracker</a>.</p>
<p>The library is just getting started and is missing various
functions (hello documentation!) but it seems to be usable - as
well as the WinForms sample browser that I was using for
development testing, I also tested it in an ASP.NET MVC
application, both locally and then remotely using the
development version of cyotek.com.</p>
<p>It's probably not ready for prime time, I need to add docs,
samples and finally get serious about using await/async, plus
get a .NET Standard build done. But I think it's getting off to
a good start.</p>
<p>The GitHub repository can be found at
<a href="https://github.com/cyotek/MantisSharp">https://github.com/cyotek/MantisSharp</a> - the <a href="https://github.com/cyotek/MantisSharp/blob/master/README.md" rel="external nofollow noopener">readme</a> has
lots of extra details so I'm not going to repeat it here.</p>
<h2 id="why-create-this-library">Why create this library?</h2>
<p>Originally I wanted to use the MantisBT REST API to
automatically generate the product roadmaps on cyotek.com -
currently these are manual, and looking at the last modification
dates on the content entries shows the latest update was in
2015. Ouch. As I've been properly planning releases in our
MantisBT instance, it made sense to use that data. However, I
don't want to open access (anonymous or otherwise) to the
MantisBT instance itself, hence deciding to use the new API they
added recently.</p>
<p>I wasn't planning create a full blown library, I thought I'd
just load the JSON into a <code>dynamic</code> and grab what I needed that
way. But that untyped code offended me so much (and oddly enough
there didn't seem to be another client out there from a <em>very</em>
brief check of NuGet) that in the end it was inevitable.</p>
<p>Assuming more than just me uses this library I'd love to hear
your feedback.</p>
<h2 id="getting-started">Getting Started</h2>
<p>As well as the source, you can grab precompiled binaries via a
NuGet package</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
Install-Package MantisSharp -Pre
</pre>
</figure>
<p>The package includes builds for .NET 3.5, 4.0, 4.5 and 4.6. 4.7
will follow when I pave my machine and get the Creators Update,
.NET Standard will follow as soon as I actually add it as a
target and resolve any API issues.</p>
<p>Then just create an instance of the <code>MantisClient</code>, passing the
base URI where your MantisBT installation is hosted, along with
an API key. Also note that by default the REST API is disabled
and needs to be explicitly switched on for external access.
(There's a <a href="https://github.com/cyotek/MantisSharp/wiki" rel="external nofollow noopener">wiki page</a> which tells you how).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
MantisClient client <span class="symbol">=</span> <span class="keyword">new</span> MantisClient<span class="symbol">(</span><span class="string">&quot;YOUR_MANTIS_URI&quot;</span><span class="symbol">,</span> <span class="string">&quot;YOUR_API_KEY&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

<span class="comment">// list all projects</span>
<span class="keyword">foreach</span> <span class="symbol">(</span>Project project <span class="keyword">in</span> client<span class="symbol">.</span>GetProjects<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
<span class="symbol">{</span>
 Console<span class="symbol">.</span>WriteLine<span class="symbol">(</span>project<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="comment">// list all issues</span>
<span class="keyword">foreach</span> <span class="symbol">(</span>Issue issue <span class="keyword">in</span> client<span class="symbol">.</span>GetIssues<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
<span class="symbol">{</span>
 Console<span class="symbol">.</span>WriteLine<span class="symbol">(</span>issue<span class="symbol">.</span>Summary<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="comment">// list issues for a single project</span>
<span class="keyword">var</span> issues <span class="symbol">=</span> client<span class="symbol">.</span>GetIssues<span class="symbol">(</span><span class="number">4</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// or pass in a Project reference</span>

<span class="comment">// get a single issue</span>
Issue issue <span class="symbol">=</span> client<span class="symbol">.</span>GetIssue<span class="symbol">(</span><span class="number">52</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="known-issues">Known Issues</h2>
<p>There's still outstanding work to do, some of which is detailed
in the readme. I also haven't done much testing yet, and our
MantisBT database is currently quite small, so I don't know how
the library will perform under bigger databases.</p>
<h2 id="examples">Examples</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/mantissharp-1b.png" class="gallery" title="An example of the WinForms demonstration application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/mantissharp-1b.png" alt="An example of the WinForms demonstration application" decoding="async" loading="lazy" /></a><figcaption>An example of the WinForms demonstration application</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/mantissharp-1a.png" class="gallery" title="An example of creating a roadmap type page using the REST API" ><img src="https://images.cyotek.com/image/thumbnail/devblog/mantissharp-1a.png" alt="An example of creating a roadmap type page using the REST API" decoding="async" loading="lazy" /></a><figcaption>An example of creating a roadmap type page using the REST API</figcaption></figure><h2 id="update-history">Update History</h2>
<ul>
<li>2017-07-10 - First published</li>
<li>2020-11-22 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/announcing-mantissharp-a-net-client-for-using-the-mantisbt-rest-api .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comTranslating text with Azure cognitive servicesurn:uuid:3f0d87b9-4550-4739-a22b-99341a20d7912021-02-11T17:15:09Z2017-05-05T18:41:10Z<p>Some time ago, I used the Bing Translator API to help create
localization for some of our products. As Microsoft recently
retired the Data Market used to provide this service it was high
time to migrate to the replacement Cognitive Services API hosted
on Azure. This article covers using the basics of Azure
cognitive services to translate text using simple HTTP requests.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/translatetext-1c.png" class="gallery" title="Sample project demonstrating the use of the cognitive services API" ><img src="https://images.cyotek.com/image/thumbnail/devblog/translatetext-1c.png" alt="Sample project demonstrating the use of the cognitive services API" decoding="async" loading="lazy" /></a><figcaption>Sample project demonstrating the use of the cognitive services API</figcaption></figure><h2 id="getting-started">Getting started</h2>
<p>I'm going to assume you've already signed up for the Text
Translation Cognitive Services API. If you haven't, you can find
a step by step guide on the <a href="https://docs.microsofttranslator.com/text-translate.html#getting-started" rel="external nofollow noopener">API documentation</a> site. Just as
with the original version, there's a free tier where you can
translate 2 million characters per month.</p>
<p>Once you have created your API service, display the <strong>Keys</strong>
page and copy one of the keys for use in your application (it
doesn't matter which one you choose).</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/translatetext-1b.png" class="gallery" title="Manage keys page in the Azure Portal" ><img src="https://images.cyotek.com/image/thumbnail/devblog/translatetext-1b.png" alt="Manage keys page in the Azure Portal" decoding="async" loading="lazy" /></a><figcaption>Manage keys page in the Azure Portal</figcaption></figure>
<blockquote>
<p>Remember that these keys should be kept secret. Don't paste
them in screenshots as I have above (unless you regenerated
the key after taking the screenshot!), don't commit them to
public code repositories - treat them as any other password.
<em>&quot;Keep it secret, keep it safe&quot;</em>.</p>
</blockquote>
<h2 id="creating-a-login-token">Creating a login token</h2>
<p>The first thing we need to do generate an authentication token.
We do this by sending a <code>POST</code> request to Microsoft's
authentication API along with a custom
<code>Ocp-Apim-Subscription-Key</code> header that contains the API key we
copied earlier.</p>
<blockquote>
<p>Note: When using the <code>HttpWebRequest</code> object, you <strong>must</strong> set
the <code>ContentLength</code> to be zero even though we're not actually
setting any body content. If the header isn't present the
authentication server will throw a <code>411</code> (Length Required)
HTTP exception.</p>
</blockquote>
<p>Assuming we have passed a valid API key, the response body will
contain a token we can use with subsequent requests.</p>
<p>Tokens are only valid for 10 minutes and it is recommended you
renew these after 8 or so minutes. For this reason, I store the
current time so that future requests can compare the stored time
against the current and automatically renew the token if
required.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">string</span> _authorizationKey<span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">string</span> _authorizationToken<span class="symbol">;</span>
<span class="keyword">private</span> DateTime _timestampWhenTokenExpires<span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> RefreshToken<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 HttpWebRequest request<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>_authorizationKey<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException<span class="symbol">(</span><span class="string">&quot;Authorization key not set.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 request <span class="symbol">=</span> WebRequest<span class="symbol">.</span>CreateHttp<span class="symbol">(</span><span class="string">&quot;https://api.cognitive.microsoft.com/sts/v1.0/issueToken&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Method <span class="symbol">=</span> WebRequestMethods<span class="symbol">.</span>Http<span class="symbol">.</span>Post<span class="symbol">;</span>
 request<span class="symbol">.</span>Headers<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;Ocp-Apim-Subscription-Key&quot;</span><span class="symbol">,</span> _authorizationKey<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>ContentLength <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> <span class="comment">// Must be set to avoid 411 response</span>

 <span class="keyword">using</span> <span class="symbol">(</span>HttpWebResponse response <span class="symbol">=</span> <span class="symbol">(</span>HttpWebResponse<span class="symbol">)</span>request<span class="symbol">.</span>GetResponse<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _authorizationToken <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetResponseString<span class="symbol">(</span>response<span class="symbol">)</span><span class="symbol">;</span>

 _timestampWhenTokenExpires <span class="symbol">=</span> DateTime<span class="symbol">.</span>UtcNow<span class="symbol">.</span>AddMinutes<span class="symbol">(</span><span class="number">8</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="using-the-token">Using the token</h2>
<p>For all subsequent requests in this article, we'll be sending
the token with the request. This is done via the <code>Authorization</code>
header which needs to be set with the string <code>Bearer &lt;TOKEN&gt;</code>.</p>
<h2 id="getting-available-languages">Getting available languages</h2>
<p>The translation API can translate a reasonable range of
languages (including for some reason Klingon), but it can't
translate all languages. Therefore, if you're building a
solution that uses the translation API it's probably a good idea
to find out what languages are available. This can be done by
calling the <code>GetLanguagesForTranslate</code> service method.</p>
<p>Rather annoyingly the translation API doesn't use
straightforward JSON objects but instead the ancient XML
serialization dialect (it appears to be a WCF service rather
than newer WebAPI) which seems an odd choice in this day and age
of easily consumed JSON services. Still, at least it means I can
create a self contained example project without needing external
packages.</p>
<p>First we create the <code>HttpWebRequest</code> object and assign our
<code>Authorization</code> header. Next, we set the value of the <code>Accept</code>
header to be <code>application/xml</code>. The API call actually seems to
ignore this header and always return XML regardless, but at
least if it changes in future to support multiple outputs our
existing code is explicit in what it wants.</p>
<p>The body of the response contains a XML document similar to the
following</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">ArrayOfstring</span> <span class="name">xmlns</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://schemas.microsoft.com/2003/10/Serialization/Arrays</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>af<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>ar<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>bn<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="comment">&lt;!-- SNIP --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>ur<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>vi<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>cy<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">ArrayOfstring</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>You could parse it yourself, but I usually don't like the
overhead of having to work with name-spaced XML documents.
Fortunately, I can just use the <code>DataContractSerializer</code> to
parse it for me.</p>
<blockquote>
<p>In order to use the <code>DataContractSerializer</code> class you need to
have a reference to <code>System.Runtime.Serialization</code> in your
project.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> GetLanguages<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 HttpWebRequest request<span class="symbol">;</span>
 <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> results<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CheckToken<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 request <span class="symbol">=</span> WebRequest<span class="symbol">.</span>CreateHttp<span class="symbol">(</span><span class="string">&quot;https://api.microsofttranslator.com/v2/http.svc/GetLanguagesForTranslate&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Headers<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;Authorization&quot;</span><span class="symbol">,</span> <span class="string">&quot;Bearer &quot;</span> <span class="symbol">+</span> _authorizationToken<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Accept <span class="symbol">=</span> <span class="string">&quot;application/xml&quot;</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>WebResponse response <span class="symbol">=</span> request<span class="symbol">.</span>GetResponse<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> response<span class="symbol">.</span>GetResponseStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 results <span class="symbol">=</span> <span class="symbol">(</span><span class="symbol">(</span>List<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">&gt;</span><span class="symbol">)</span><span class="keyword">new</span> DataContractSerializer<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>List<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">&gt;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ReadObject<span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToArray<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> results<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="getting-language-names">Getting language names</h2>
<p>The previous section obtains a list of ISO language codes, but
generally you would probably want to present something more
friendly to end-users. We can obtain localized language names
via the <code>GetLanguageNames</code> method.</p>
<p>This time we need to perform a <code>POST</code>, and include a custom body
containing the language codes we wish to retrieve friendly names
for, along with a query string argument that specifies which
language to use for the friendly names.</p>
<p>The body should be XML similar to the following. This is
identical to the output of the <code>GetLanguagesForTranslate</code> call
above.</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">ArrayOfstring</span> <span class="name">xmlns</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://schemas.microsoft.com/2003/10/Serialization/Arrays</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>af<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>ar<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>bn<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="comment">&lt;!-- SNIP --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>ur<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>vi<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>cy<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">ArrayOfstring</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>The response body will be a string array where each element
contains the friendly language name of the matching element from
the request body. The following example is a sample of output
when German (<code>de</code>) friendly names are requested.</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">ArrayOfstring</span> <span class="name">xmlns</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://schemas.microsoft.com/2003/10/Serialization/Arrays</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>Afrikaans<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>Arabisch<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>Bangla<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="comment">&lt;!-- SNIP --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>Urdu<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>Vietnamesisch<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">string</span><span class="symbol">&gt;</span>Walisisch<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">ArrayOfstring</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>Previously we used the <code>DataContractSerializer</code> deserialize the
response body and we can use the same class to serialize the
request body too. We also have to specify the <code>Content-Type</code> of
the data we're transmitting. And of course make sure we include
the <code>locale</code> query string argument in the posted URI.</p>
<blockquote>
<p>If you forget to set the <code>Content-Type</code> header then according
to the <a href="https://docs.microsofttranslator.com/text-translate.html#!/default/post_GetLanguageNames" rel="external nofollow noopener">documentation</a> you'd probably expect it to return
400 (Bad Request). Somewhat curiously, it returns 200 (OK)
with a 500-esque HTML error message in the body. So don't
forget to set the content type!</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> GetLocalizedLanguageNames<span class="symbol">(</span><span class="keyword">string</span> locale<span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> languages<span class="symbol">)</span>
<span class="symbol">{</span>
 HttpWebRequest request<span class="symbol">;</span>
 <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> results<span class="symbol">;</span>
 DataContractSerializer serializer<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CheckToken<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 
 serializer <span class="symbol">=</span> <span class="keyword">new</span> DataContractSerializer<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span><span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 request <span class="symbol">=</span> WebRequest<span class="symbol">.</span>CreateHttp<span class="symbol">(</span><span class="string">&quot;https://api.microsofttranslator.com/v2/http.svc/GetLanguageNames?locale=&quot;</span> <span class="symbol">+</span> locale<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Headers<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;Authorization&quot;</span><span class="symbol">,</span> <span class="string">&quot;Bearer &quot;</span> <span class="symbol">+</span> _authorizationToken<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Accept <span class="symbol">=</span> <span class="string">&quot;application/xml&quot;</span><span class="symbol">;</span>
 request<span class="symbol">.</span>ContentType <span class="symbol">=</span> <span class="string">&quot;application/xml&quot;</span><span class="symbol">;</span> <span class="comment">// must be set to avoid invalid 200 response</span>
 request<span class="symbol">.</span>Method <span class="symbol">=</span> WebRequestMethods<span class="symbol">.</span>Http<span class="symbol">.</span>Post<span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> request<span class="symbol">.</span>GetRequestStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 serializer<span class="symbol">.</span>WriteObject<span class="symbol">(</span>stream<span class="symbol">,</span> languages<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">using</span> <span class="symbol">(</span>WebResponse response <span class="symbol">=</span> request<span class="symbol">.</span>GetResponse<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> response<span class="symbol">.</span>GetResponseStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 results <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span><span class="symbol">)</span>serializer<span class="symbol">.</span>ReadObject<span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> results<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="translating-phrases">Translating phrases</h2>
<p>The final piece of the puzzle is to actually translate a string.
We can do this using the <code>Translate</code> service method, which is a
simple enough method to use - you pass the text, source language
and output language as query string parameters, and the
translation will be returned in the response body as an XML
string.</p>
<blockquote>
<p>You can also specify a category for the translation. I believe
this is for use with Microsoft's Translation Hub so as of yet
I haven't tried experimenting with this parameter.</p>
</blockquote>
<p>The following example is a the response returned when requesting
a translation of <code>Hello World!</code> from English (<code>en</code>) to German
(<code>de</code>).</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">string</span> <span class="name">xmlns</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://schemas.microsoft.com/2003/10/Serialization/</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>Hallo Welt!<span class="symbol">&lt;/</span><span class="name">string</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>The request is similar to other examples in this article. The
only point to note is that as the <code>text</code> query string argument
will contain user enterable content, I'm encoding it using
<code>Uri.EscapeDataString</code> to account for any special characters.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">string</span> Translate<span class="symbol">(</span><span class="keyword">string</span> text<span class="symbol">,</span> <span class="keyword">string</span> <span class="keyword">from</span><span class="symbol">,</span> <span class="keyword">string</span> to<span class="symbol">)</span>
<span class="symbol">{</span>
 HttpWebRequest request<span class="symbol">;</span>
 <span class="keyword">string</span> result<span class="symbol">;</span>
 <span class="keyword">string</span> queryString<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CheckToken<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 queryString <span class="symbol">=</span> <span class="keyword">string</span><span class="symbol">.</span>Concat<span class="symbol">(</span><span class="string">&quot;text=&quot;</span><span class="symbol">,</span> Uri<span class="symbol">.</span>EscapeDataString<span class="symbol">(</span>text<span class="symbol">)</span><span class="symbol">,</span> <span class="string">&quot;&amp;from=&quot;</span><span class="symbol">,</span> <span class="keyword">from</span><span class="symbol">,</span> <span class="string">&quot;&amp;to=&quot;</span><span class="symbol">,</span> to<span class="symbol">)</span><span class="symbol">;</span>

 request <span class="symbol">=</span> WebRequest<span class="symbol">.</span>CreateHttp<span class="symbol">(</span><span class="string">&quot;https://api.microsofttranslator.com/v2/http.svc/Translate?&quot;</span> <span class="symbol">+</span> queryString<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Headers<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;Authorization&quot;</span><span class="symbol">,</span> <span class="string">&quot;Bearer &quot;</span> <span class="symbol">+</span> _authorizationToken<span class="symbol">)</span><span class="symbol">;</span>
 request<span class="symbol">.</span>Accept <span class="symbol">=</span> <span class="string">&quot;application/xml&quot;</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>WebResponse response <span class="symbol">=</span> request<span class="symbol">.</span>GetResponse<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> response<span class="symbol">.</span>GetResponseStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">)</span>_stringDataContractSerializer<span class="symbol">.</span>ReadObject<span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="other-api-methods">Other API methods</h2>
<p>The <code>GetLanguagesForTranslate</code>, <code>GetLanguageNames</code> and
<code>Translate</code> API methods above describe the basics of using the
translation services. The service API does offer additional
functionality, such as the ability to translate multiple strings
at once or to return multiple translations for a single string
or even to try and detect the language of a piece of text. These
are for use in more advanced scenarios that what I'm currently
interested in and so I haven't looked further into these
methods.</p>
<h2 id="sample-application">Sample application</h2>
<p>The code samples in this article are both overly verbose (lots
of duplicate setup and processing code) and functionally lacking
(no checking of status codes or handling of errors). The
download sample accompanying this article includes a more robust
<code>TranslationClient</code> class that can be easily used to add the
basics of the translation APIs to your own applications.</p>
<p>Note that unlike most of my other articles/samples this one
won't run out the box - the keys seen in the application and
screenshots have been revoked, and you'll need to substitute the
ones you get when you created your service using the Azure
Portal.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2017-05-05 - First published</li>
<li>2020-11-22 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/translating-text-with-azure-cognitive-services .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comSending SMS messages with Twiliourn:uuid:df7f7c44-f463-4adc-9a6b-e20e79497af92015-07-25T07:46:40Z2015-07-24T19:11:38Z<p>Last week I attended the <a href="http://nebytes.net/" rel="external nofollow noopener">NEBytes</a> technology user group for
the first time. Despite the fact I didn't actually say more than
two words (speaking to a real live human is only marginally
easier than flying without wings) I did enjoy the two talks that
were given.</p>
<p>The first of these was for <a href="https://www.twilio.com/" rel="external nofollow noopener">Twilio</a>, a platform for text
messaging and Voice over IP (VoIP). This platform provides you
with the ability to send and receive SMS messages, or even
create convoluted telephone call services where you can prompt
the user with options, capture input, record messages, redirect
to other phones... and all fairly painlessly. I can see all
sorts of interesting uses for the services they offer. Oh, and
the prices seem reasonable as well.</p>
<p>All of this is achieved using a simple REST API which is pretty
impressive.</p>
<p>My immediate use case for this is for alert notifications as,
like any technology, sometimes emails fail or are not
accessible. I also added two factor authentication to cyotek.com
in under 5 minutes which I thought was neat (although in
fairness, with the Identity Framework all I had to do was fill
in the blanks for the <code>Smsservice</code> and uncomment some
boilerplate code).</p>
<p>In this article, I'll show you just how incredibly easy it is to
send text messages.</p>
<h2 id="getting-an-account">Getting an account</h2>
<p>The first thing you need is a Twilio account - so go sign up.
You don't need to shell out any money at this stage, the example
program I will present below will work perfectly well with their
trial account and not cost a penny.</p>
<p>Once you've signed up you'll need to validate a real phone
number of your own for security purposes, and then you'll need
to buy a phone number that you will use for your SMS services.</p>
<blockquote>
<p>You get one phone number for free with your trial account.
When you are ready to upgrade to a unrestricted account, each
phone number you buy costs $1 a month (yes, that's one
dollar), then $0.0075 to receive a SMS message or $0.04 to
send one. (<em>Prices correct at time of writing</em>). For high
volume businesses, short codes are also available, but these
are very expensive.</p>
</blockquote>
<p>You'll need to get your API credentials too - this is slightly
hidden, but if you go to your Twilio <a href="https://www.twilio.com/user/account/" rel="external nofollow noopener">account portal</a> and
look in the upper right section of the page there is a link
titled <strong>Show API Credentials</strong> - click this to get your
<em>Account SID</em> and <em>Auth Token</em>.</p>
<h2 id="creating-a-simple-application">Creating a simple application</h2>
<p>Twilio offers client libraries for a raft of languages, and
support for .NET is no exception by using the
<a href="https://github.com/twilio/twilio-csharp" rel="external nofollow noopener">twilio-csharp</a> client,
which of course has a NuGet package. Lots of packages actually,
but we just need the core.</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
Install-Package Twilio
</pre>
</figure>
<p>Now you're set!</p>
<p>To send a message, you create an instance of the
<code>TwilioRestClient</code> using your Account SID and Auth Token and
call <code>SendSmsMessage</code> with your Twilio phone number, the number
of the phone to send the message to, and of course the message
itself. And that's pretty much it.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">static</span> <span class="keyword">void</span> Main<span class="symbol">(</span><span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> args<span class="symbol">)</span>
<span class="symbol">{</span>
 SendSms<span class="symbol">(</span><span class="string">&quot;077xxxxxxxx&quot;</span><span class="symbol">,</span> <span class="string">&quot;Sending messages couldn&#39;t be simpler!&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> SendSms<span class="symbol">(</span><span class="keyword">string</span> to<span class="symbol">,</span> <span class="keyword">string</span> message<span class="symbol">)</span>
<span class="symbol">{</span>
 TwilioRestClient client<span class="symbol">;</span>
 <span class="keyword">string</span> accountSid<span class="symbol">;</span>
 <span class="keyword">string</span> authToken<span class="symbol">;</span>
 <span class="keyword">string</span> fromNumber<span class="symbol">;</span>

 accountSid <span class="symbol">=</span> <span class="string">&quot;DF8A228F5D66403E973E714324D5816D&quot;</span><span class="symbol">;</span> <span class="comment">// no, these are not real</span>
 authToken <span class="symbol">=</span> <span class="string">&quot;942CA384E3CC4107A10BA58177ACF88B&quot;</span><span class="symbol">;</span>
 fromNumber <span class="symbol">=</span> <span class="string">&quot;+44191xxxxxxx&quot;</span><span class="symbol">;</span>

 client <span class="symbol">=</span> <span class="keyword">new</span> TwilioRestClient<span class="symbol">(</span>accountSid<span class="symbol">,</span> authToken<span class="symbol">)</span><span class="symbol">;</span>

 client<span class="symbol">.</span>SendSmsMessage<span class="symbol">(</span>fromNumber<span class="symbol">,</span> to<span class="symbol">,</span> message<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>SendSmsMessage</code> method returns a <code>SMSMessage</code> object which
has various attributes relating to the sent message - such as
the cost of sending it.</p>
<p>Apologies for the less-than-perfect photo, but the image below
shows my Lumia 630 with the received message.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/twilio-sendsms.png" class="gallery" title="Not the best photo in the world, but here is a sample message" ><img src="https://images.cyotek.com/image/thumbnail/devblog/twilio-sendsms.png" alt="Not the best photo in the world, but here is a sample message" decoding="async" loading="lazy" /></a><figcaption>Not the best photo in the world, but here is a sample message</figcaption></figure>
<blockquote>
<p>Sharp eyes will note that the message is prefixed with <em>Sent
from your Twilio trial account -</em> this prefix is only for
trial accounts, and there will be no adjustment of your
messages once you've upgraded.</p>
</blockquote>
<h2 id="simple-apis-arent-so-simple">Simple API's aren't so simple</h2>
<p>There's one fairly awkward caveat with this library however -
exception handling. I did a test using invalid credentials, and
to my surprise nothing happened when I ran the sample program. I
didn't receive a SMS message of course, but neither did the
sample program crash.</p>
<p>This is because for whatever reason, the client doesn't raise an
exception if the call fails. Instead, it is essentially
returned as a result code. I mentioned above that the
<code>SendSmsMessage</code> return a <code>SMSMessage</code> object. This object has a
property named <code>RestException</code>. If the value of this property is
<code>null</code>, everything is fine, if not, then your request wasn't
successful.</p>
<p>I really don't like this behaviour, as it means now I'm
responsible for checking the response every time I send a
message, instead of the client throwing an exception and forcing
me to deal with issues.</p>
<p>The other thing that irks me with this library is that the
<code>RestException</code> class has <code>Status</code> and <code>Code</code> properties, which
are the HTTP status code and Twilio status code respectively.
But for some curious reason, these numeric properties are
defined as strings, and so if you want to process them you'll
have to both convert them to integers and make sure that the
underlying value is a number in the first place.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> SendSms<span class="symbol">(</span><span class="keyword">string</span> to<span class="symbol">,</span> <span class="keyword">string</span> message<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="symbol">...</span> <span class="symbol">&lt;</span>snip<span class="symbol">&gt;</span> <span class="symbol">...</span>
 SMSMessage result<span class="symbol">;</span>

 <span class="symbol">...</span> <span class="symbol">&lt;</span>snip<span class="symbol">&gt;</span> <span class="symbol">...</span>

 result <span class="symbol">=</span> client<span class="symbol">.</span>SendSmsMessage<span class="symbol">(</span>fromNumber<span class="symbol">,</span> to<span class="symbol">,</span> message<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>result<span class="symbol">.</span>RestException <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ApplicationException<span class="symbol">(</span>result<span class="symbol">.</span>RestException<span class="symbol">.</span>Message<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Although I don't recommend you use <code>ApplicationException</code>!
Something like this may be more appropriate:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">if</span> <span class="symbol">(</span>result<span class="symbol">.</span>RestException <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> httpStatus<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">int</span><span class="symbol">.</span>TryParse<span class="symbol">(</span>result<span class="symbol">.</span>RestException<span class="symbol">.</span>Status<span class="symbol">,</span> <span class="keyword">out</span> httpStatus<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 httpStatus <span class="symbol">=</span> <span class="number">500</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">throw</span> <span class="keyword">new</span> HttpException<span class="symbol">(</span>httpStatus<span class="symbol">,</span> result<span class="symbol">.</span>RestException<span class="symbol">.</span>Message<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>There's also a <code>Status</code> property on the underlying <code>SMSMessage</code>
class which can be <code>failed</code>. Hopefully the <code>RestException</code>
property is always set for failed statuses otherwise that's
something else you'd have to remember to check.</p>
<p>However you choose to do it, you probably should ensure that you
do check for a failed / exception response, especially if the
messages are important (for example two-factor authentication
codes).</p>
<h2 id="long-codes-vs-short-codes">Long Codes vs Short Codes</h2>
<p>By default, Twilio uses long codes (also known as &quot;normal&quot; phone
numbers). According to their docs, these are rate limited to 1
message per second. I did a sample test where I spammed 10
messages one after another. I received the first 5 right away,
and the next five about a minute later. So if you have a high
volume service, it's possible that your messages may be slightly
delayed. One the plus side, it does seem to be fire and forget,
you don't need to manually queue messages yourself and they
don't get lost.</p>
<p>Twilio also supports short codes (e.g. send STOP to 123456 to
opt out of this list you never opted into in the first place),
which are suitable for high traffic - 30 messages a second
apparently. However, these are very expensive and have to be
leased from the mobile operators, a process which takes several
weeks.</p>
<h2 id="advanced-scenarios">Advanced Scenarios</h2>
<p>As I mentioned in my intro, there's a lot more to Twilio than
just sending SMS messages, although for me personally that's
going to be a big part of it. But you can also read and process
messages, in other words when someone sends a SMS to your Twilio
phone number, it will call a custom HTTP endpoint in your
application code, where you can then read the message and
process it. This too is something I will find value in, and I'll
cover that in another post.</p>
<p>And then there's some pretty impressive options for working with
real phone calls (along with the worst robot sounding voice in
history). Not entirely sure I will cover this as it's not
immediately something I'd make use of.</p>
<p>Take a look at their <a href="https://www.twilio.com/docs" rel="external nofollow noopener">documentation</a> to see how to use their
API's to build SMS/VoIP functionality into your services.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2015-07-24 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/sending-sms-messages-with-twilio .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com