<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:blogChannel="http://backend.userland.com/blogChannelModule" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:pingback="http://madskills.com/public/xml/rss/module/pingback/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
  <channel>
    <title>DotNetNerd's blog</title>
    <description>- because I love development</description>
    <link>https://blog.dotnetnerd.dk/</link>
    <docs>http://www.rssboard.org/rss-specification</docs>
    <generator>BlogEngine.NET 2.0.0.36</generator>
    <language>en-US</language>
    <blogChannel:blogRoll>https://blog.dotnetnerd.dk/opml.axd</blogChannel:blogRoll>
    <dc:creator>Christian Holm Nielsen</dc:creator>
    <dc:title>DotNetNerd's blog</dc:title>
    <geo:lat>0.000000</geo:lat>
    <geo:long>0.000000</geo:long>
    <item>
      <title>Azure identity - managed identities and roles</title>
      <description>&lt;p&gt;&lt;img style="float: right; margin-left: 10px; margin-bottom: 10px; height: 250px;" src="https://blog.dotnetnerd.dk/image.axd?picture=2023%2f6%2fidentity-1-300x300.jpg" alt="secret identity" /&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;For a while I have actually not had to do much configuration of app registrations, managed identities and so on, simply because I often join teams who have other people doing that part. So it has been fun and at time challenging to get back into it, as I have had to lately. So to help others, and maybe a future me I just want to write down a few notes, that will help me remember a few key details.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;First of all when we are talking about a setup with a frontend application and backend api app registration, the app roles and claim mapping should be done in the backend api, with roles being defined as part of the app registration, and the mapping from e.g. groups being done in enterprise application. The roles and claims will then become available in the access token, once a user logs in using the clientid for the frontend application, that uses the backend API.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;In most we cases we will then also need to have services that should be able to call our API. Authenticating using managed identity makes this quite simple, with the caviat being that roles can only be assigned using a script.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;;"&gt;To run e.g. a Functions app that should be allowed access you need to configure a managed identity and add the role to it using a powershell script like below. User assigned managed identity can be used if you wish to use it for multiple services, however things like access to key vault using keyvault references require a system managed identity - so you will likely need to either use a system managed identity and configure it for each service, or have both kinds of managed identity configured.&lt;br /&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;$objectIdForManagedIdentity = "insert object (principal) id from managed identity"
$enterpriseApplicationObjectId = "insert enterprise applications object id"
$roleIdGuid = "insert role id"

$uri = "https://graph.microsoft.com/v1.0/servicePrincipals/$objectIdForManagedIdentity/appRoleAssignments"
$body = @{
    principalId = $objectIdForManagedIdentity
    resourceId = $enterpriseApplicationObjectId
    appRoleId = $roleIdGuid
} | ConvertTo-Json
az rest --method POST --url $uri --body $body&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;After that when fetching a token using DefaultAzureCredentials the .default scope should be used and the ManagedIdentityClientId should be set to ensure it uses the user assigned identity.&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2023/06/23/Azure-identity-managed-identities-and-roles.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2023/06/23/Azure-identity-managed-identities-and-roles.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=048a0adb-885a-48d4-8cb5-222a18cc04a7</guid>
      <pubDate>Fri, 23 Jun 2023 07:41:00 +0000</pubDate>
      <category>Technology</category>
      <dc:publisher>DotNetNerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=048a0adb-885a-48d4-8cb5-222a18cc04a7</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=048a0adb-885a-48d4-8cb5-222a18cc04a7</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2023/06/23/Azure-identity-managed-identities-and-roles.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=048a0adb-885a-48d4-8cb5-222a18cc04a7</wfw:commentRss>
    </item>
    <item>
      <title>Putting API Managemement in front of blob storage</title>
      <description>&lt;p&gt;A nice and simple way to expose static files is through Azure blob storage. If you are already using API Management you might want to have requests to through there, in order to ensure you can move it to somewhere else in the future. It requires a few steps to get it to work though.&lt;/p&gt;
&lt;p&gt;First of all Managed Identities should be enabled in API management and Access Control (IAM) should be configured for the container to allow API management to access the file.&amp;nbsp;In API management the endpoint is added with authentication-managed-identity policy to allow authentication is passes through. After that a number of headers should be removed and the x-ms-version, which is required to do AD authentication, should be set when forwarding the request from API Management to the blob storage endpoint.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In my case I also wanted to avoid the .json extension in the endpoint, so the configuration ended up looking something like this.&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;&amp;lt;policies&amp;gt;
    &amp;lt;inbound&amp;gt;        
        &amp;lt;set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" /&amp;gt;
        &amp;lt;set-header name="Sec-Fetch-Site" exists-action="delete" /&amp;gt;
        &amp;lt;set-header name="Sec-Fetch-Mode" exists-action="delete" /&amp;gt;
        &amp;lt;set-header name="Sec-Fetch-Dest" exists-action="delete" /&amp;gt;
        &amp;lt;set-header name="Accept" exists-action="delete" /&amp;gt;
        &amp;lt;set-header name="Accept-Encoding" exists-action="delete" /&amp;gt;
        &amp;lt;set-header name="Referer" exists-action="delete" /&amp;gt;
        &amp;lt;set-header name="X-Forwarded-For" exists-action="delete" /&amp;gt;
        &amp;lt;set-header name="x-ms-version" exists-action="override"&amp;gt;
            &amp;lt;value&amp;gt;@{string version = "2017-11-09"; return version;}&amp;lt;/value&amp;gt;
        &amp;lt;/set-header&amp;gt;        
        &amp;lt;rewrite-uri template="/settings.json" copy-unmatched-params="true" /&amp;gt;
        &amp;lt;authentication-managed-identity resource="https://storage.azure.com/" /&amp;gt;
    &amp;lt;/inbound&amp;gt;
    &amp;lt;backend&amp;gt;
        &amp;lt;base /&amp;gt;
    &amp;lt;/backend&amp;gt;
    &amp;lt;outbound&amp;gt;
        &amp;lt;base /&amp;gt;
    &amp;lt;/outbound&amp;gt;
    &amp;lt;on-error&amp;gt;
        &amp;lt;base /&amp;gt;
    &amp;lt;/on-error&amp;gt;
&amp;lt;/policies&amp;gt;&lt;/pre&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2023/02/13/Putting-API-Managemement-in-front-of-blob-storage.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2023/02/13/Putting-API-Managemement-in-front-of-blob-storage.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=840e0486-d995-4e71-ac0e-aef02a47293b</guid>
      <pubDate>Mon, 13 Feb 2023 09:12:00 +0000</pubDate>
      <category>Snippets</category>
      <category>Technology</category>
      <dc:publisher>DotNetNerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=840e0486-d995-4e71-ac0e-aef02a47293b</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=840e0486-d995-4e71-ac0e-aef02a47293b</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2023/02/13/Putting-API-Managemement-in-front-of-blob-storage.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=840e0486-d995-4e71-ac0e-aef02a47293b</wfw:commentRss>
    </item>
    <item>
      <title>Updating API management from Azure Devops Pipeline</title>
      <description>&lt;p&gt;Recently I needed to update Azure Management API based on the swagger specifications when our services were deployed. It seems like a pretty standard thing that there would be&amp;nbsp; a task for, but it turned out to require a bit of Powershell - it i still fairly simple though.&lt;/p&gt;
&lt;p&gt;A general function to acccomplish it could look like this bit of code.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;[CmdletBinding()]
Param(
    [string] [Parameter(Mandatory=$true)] $ResourceGroupName,
    [string] [Parameter(Mandatory=$true)] $ServiceName,
    [string] [Parameter(Mandatory=$true)] $ApiName,
    [string] [Parameter(Mandatory=$true)] $SpecificationFilePath
)

$apiMgmtContext = New-AzApiManagementContext -ResourceGroupName $ResourceGroupName -ServiceName $ServiceName
$api = Get-AzApiManagementApi -Context $apiMgmtContext -ApiId $ApiName

if ($null -eq $api) {
    Write-Error "Failed to get API with name $ApiName"
    exit(1)
}

$apiVersionSetId = $api.ApiVersionSetId.Substring($api.ApiVersionSetId.LastIndexOf("/")+1)
$apiVersionSet = Get-AzApiManagementApiVersionSet -Context $apiMgmtContext -ApiVersionSetId $apiVersionSetId

Import-AzApiManagementApi -Context $apiMgmtContext `
                        -SpecificationUrl $SpecificationFilePath `
                        -SpecificationFormat 'OpenApi' `
                        -Path $api.Path `
                        -ApiId $api.ApiId `
                        -ServiceUrl $api.ServiceUrl`
                        -ApiVersionSetId $apiVersionSet.Id&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;This can of course be used locally, but to run it from a release pipeline, the cleanest way I have found, is to add it to a separate repository, and include it as an artifact. From there we just need a Azure Powershell build step, configured as shown by the YAML below.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;variables:
  swaggerUrl: 'https://my_appservice.azurewebsites.net/swagger/1.0.0/swagger.json'

steps:
- task: AzurePowerShell@5
  displayName: 'Azure PowerShell script: Update API Management'
  inputs:
    azureSubscription: 'xxx'
    ScriptPath: '$(System.DefaultWorkingDirectory)/_MyCompany.BuildScripts/UpdateApiManagement.ps1'
    ScriptArguments: '-ResourceGroupName "resource_group_name" -ServiceName "management_api_service_name" -ApiName "unique_api_id" -SpecificationFilePath $(swaggerUrl)'
    azurePowerShellVersion: LatestVersion&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;And that is all that is required, to automate it and make it update along with the build. Nice and easy.&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2022/12/14/Updating-API-management-f4rom-Azure-Devops-Pipeline.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2022/12/14/Updating-API-management-f4rom-Azure-Devops-Pipeline.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=40472ff9-77a5-4e6a-96e9-76eb56ff6e00</guid>
      <pubDate>Wed, 14 Dec 2022 12:32:00 +0000</pubDate>
      <category>Architecture</category>
      <category>Snippets</category>
      <dc:publisher>DotNetNerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=40472ff9-77a5-4e6a-96e9-76eb56ff6e00</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=40472ff9-77a5-4e6a-96e9-76eb56ff6e00</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2022/12/14/Updating-API-management-f4rom-Azure-Devops-Pipeline.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=40472ff9-77a5-4e6a-96e9-76eb56ff6e00</wfw:commentRss>
    </item>
    <item>
      <title>Rolling my own OAuth 1.0 client</title>
      <description>&lt;p&gt;OAuth is one of those things where I always wonder about how bad the state of libraries etc are. It seems like a problem pretty much everyone will tackle, but I am still not able to find a simple, library that works well with .NET Core HttpClient without being a whole framework in itself or at least very framework dependent. Recently I needed just that for OAuth 1.0a, but ended up rolling my own. It is just the very basics, but I wanted to put it out there, because I guess others might have use for it, or I will some time in the future.&lt;/p&gt;
&lt;p&gt;public class OAuth1    &lt;br /&gt; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Random random = new Random();&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string SignatureBase(string method, string url, Dictionary&amp;lt;string, string&amp;gt; oauthParams)    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return method + "&amp;amp;" + Uri.EscapeDataString(url) + "&amp;amp;" + Uri.EscapeDataString(string.Join("&amp;amp;", oauthParams.OrderBy(p =&amp;gt; p.Key).Select(p =&amp;gt; $"{p.Key}={p.Value}")));     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string Signature(string consumerSecret, string tokenSecret, string signatureBase)    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var encoding = new ASCIIEncoding();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string key = Uri.EscapeDataString(consumerSecret) + "&amp;amp;" + (string.IsNullOrEmpty(tokenSecret) ? "" : Uri.EscapeDataString(tokenSecret));     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; byte[] keyBytes = encoding.GetBytes(key);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; byte[] messageBytes = encoding.GetBytes(signatureBase);&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; using (HMACSHA1 SHA1 = new HMACSHA1(keyBytes))    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var hashed = SHA1.ComputeHash(messageBytes);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return Convert.ToBase64String(hashed);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string OAuthHeader(Dictionary&amp;lt;string, string&amp;gt; oauthParams)    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return "OAuth " + string.Join(", ", oauthParams.Select(p =&amp;gt; $@"{p.Key}=""{p.Value}"""));     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string GenerateTimeStamp()    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TimeSpan ts = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0));     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return ((int)Math.Floor(ts.TotalSeconds)).ToString();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string Nonce(int length = 32)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var nonceString = new StringBuilder();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; for (int i = 0; i &amp;lt; length; i++)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; nonceString.Append(validChars[random.Next(0, validChars.Length - 1)]);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return nonceString.ToString();    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public async Task&amp;lt;string&amp;gt; Request(string url, IDictionary&amp;lt;string, string&amp;gt; otherParams, string oAuthHeader)    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; using (var httpClient = new HttpClient())     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; httpClient.DefaultRequestHeaders.Add("Authorization", oAuthHeader);&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string fullUrl = otherParams.Any() ? (url + "?" + string.Join("&amp;amp;", otherParams.Select(p =&amp;gt; $"{p.Key}={p.Value}"))) : url;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; HttpResponseMessage httpResp = await httpClient.GetAsync(fullUrl);&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return await httpResp.Content.ReadAsStringAsync();    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt; }&lt;/p&gt;
&lt;p&gt;With this in place we can do requests with the correct headers like this.&lt;/p&gt;
&lt;p&gt;string _consumerKey = "aaa";    &lt;br /&gt;string _consumerSecret = "bbb";     &lt;br /&gt;string _accessToken = "ccc";     &lt;br /&gt;string _accessTokenSecret = "ddd";&lt;/p&gt;
&lt;p&gt;var oAuth = new OAuth1();&lt;/p&gt;
&lt;p&gt;string url = "&lt;a href="http://myfancysite.com/api/cars/register&amp;quot;;"&gt;http://myfancysite.com/api/cars/register";&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;string timestamp = oAuth.GenerateTimeStamp();    &lt;br /&gt;&amp;nbsp; string nonce = oAuth.Nonce();&lt;/p&gt;
&lt;p&gt;Dictionary&amp;lt;string, string&amp;gt; oAuthParams = new Dictionary&amp;lt;string, string&amp;gt;()    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "oauth_consumer_key", _consumerKey },     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "oauth_nonce", nonce },     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "oauth_signature_method", "HMAC-SHA1" },     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "oauth_timestamp",&amp;nbsp; timestamp.ToString()},     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "oauth_token", _accessToken },     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "oauth_version",&amp;nbsp; "1.0"},     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; };&lt;/p&gt;
&lt;p&gt;Dictionary&amp;lt;string, string&amp;gt; otherParams = new Dictionary&amp;lt;string, string&amp;gt;()    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "email", "johnny@madsen.dk" },     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "make", "Skoda" },     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "model", "Fabia" },     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "firstname", "Johnny" },     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { "lastname", "Madsen" }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; };&lt;/p&gt;
&lt;p&gt;string signatureBase = oAuth.SignatureBase("GET", url, oAuthParams.Concat(otherParams.Select(p =&amp;gt; new KeyValuePair&amp;lt;string, string&amp;gt;(p.Key, Uri.EscapeDataString(p.Value)))).ToDictionary(p =&amp;gt; p.Key, p =&amp;gt; p.Value));    &lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp; string signature = oAuth.Signature(_consumerSecret, _accessTokenSecret, signatureBase);&lt;/p&gt;
&lt;p&gt;oAuthParams.Add("oauth_signature", signature);&lt;/p&gt;
&lt;p&gt;string oAuthHeader = oAuth.OAuthHeader(oAuthParams);&lt;/p&gt;
&lt;p&gt;var result = await oAuth.Request(url, otherParams, oAuthHeader);&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2020/03/24/Rolling-my-own-OAuth-10-client.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2020/03/24/Rolling-my-own-OAuth-10-client.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=16c5852c-6893-4b0d-a6ce-c4f44390558b</guid>
      <pubDate>Tue, 24 Mar 2020 08:18:00 +0000</pubDate>
      <category>Snippets</category>
      <dc:publisher>DotNetNerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=16c5852c-6893-4b0d-a6ce-c4f44390558b</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=16c5852c-6893-4b0d-a6ce-c4f44390558b</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2020/03/24/Rolling-my-own-OAuth-10-client.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=16c5852c-6893-4b0d-a6ce-c4f44390558b</wfw:commentRss>
    </item>
    <item>
      <title>This side up please</title>
      <description>&lt;p&gt;On my current project we started seeing issues with images, especially when taken using an IPhone, that were shown as being rotated. Reading up on it I found that this is due to IOS using EXIF orientation, which is not always handled the same way on eg. Windows.&lt;/p&gt;  &lt;p&gt;I found a couple of functions, that I modified to work with our TypeScript codebase, so that it utilizes async/await and has a minimum of type information. I suspect they might be useful for others, or myself later on, so here they are.&lt;/p&gt;    &lt;p&gt;First there is a function that takes an image as a blob and returnes the orientation.&lt;/p&gt;  &lt;p&gt;const getOrientataion = async (file: Blob) : Promise&amp;lt;number&amp;gt; =&amp;gt; {&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; return await new Promise&amp;lt;any&amp;gt;(resolve =&amp;gt; {    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; const reader = new FileReader();     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; reader.onload = (event: any) =&amp;gt; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (event.target === null || event.target.result === null) return;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; const view = new DataView(event.target.result as ArrayBuffer);&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (view.getUint16(0, false) != 0xFFD8) return resolve(-2);&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; let length = view.byteLength,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; offset = 2;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; while (offset &amp;lt; length) {    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; const marker = view.getUint16(offset, false);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; offset += 2;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (marker == 0xFFE1) {    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (view.getUint32(offset += 2, false) != 0x45786966) {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return resolve(-1);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; const little = view.getUint16(offset += 6, false) == 0x4949;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; offset += view.getUint32(offset + 4, little);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; const tags = view.getUint16(offset, little);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; offset += 2;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; for (let i = 0; i &amp;lt; tags; i++)    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (view.getUint16(offset + (i * 12), little) == 0x0112)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return resolve(view.getUint16(offset + (i * 12) + 8, little));     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else if ((marker &amp;amp; 0xFF00) != 0xFF00) break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else offset += view.getUint16(offset, false);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return resolve(-1);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; };     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; reader.readAsArrayBuffer(file.slice(0, 64 * 1024));     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; });     &lt;br /&gt; }&lt;/p&gt;  &lt;p&gt;Next is a small helper for converting the blob into a base64 encoded url.&lt;/p&gt;  &lt;p&gt;const blobToBase64Url = async (file: Blob) : Promise&amp;lt;string&amp;gt; =&amp;gt; {    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; const reader = new FileReader();     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return await new Promise&amp;lt;any&amp;gt;(resolve =&amp;gt; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; reader.onload = (e: any) =&amp;gt; resolve(e.target.result);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; reader.readAsDataURL(file);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; });     &lt;br /&gt; }&lt;/p&gt;  &lt;p&gt;Lastly a function that takes a base64 encoded image url and an orientation, and returnes a new base64 encoded image that is the right way up.&lt;/p&gt;  &lt;p&gt;const resetOrientation = async (srcBase64: string, srcOrientation: number) : Promise&amp;lt;string&amp;gt; =&amp;gt; {&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; return await new Promise&amp;lt;any&amp;gt;(resolve =&amp;gt; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; const img = new Image();     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; img.onload = () =&amp;gt; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; let width = img.width,     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; height = img.height,     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; canvas = document.createElement('canvas'),     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ctx = canvas.getContext(&amp;quot;2d&amp;quot;);&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (ctx == null) return;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (4 &amp;lt; srcOrientation &amp;amp;&amp;amp; srcOrientation &amp;lt; 9) {    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; canvas.width = height;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; canvas.height = width;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; } else {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; canvas.width = width;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; canvas.height = height;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; switch (srcOrientation) {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; case 3: ctx.transform(-1, 0, 0, -1, width, height); break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; case 4: ctx.transform(1, 0, 0, -1, 0, height); break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; case 6: ctx.transform(0, 1, -1, 0, height, 0); break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; case 7: ctx.transform(0, -1, -1, 0, height, width); break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; case 8: ctx.transform(0, -1, 1, 0, 0, width); break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; default: break;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ctx.drawImage(img, 0, 0);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; resolve(canvas.toDataURL());     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; };     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; img.src = srcBase64;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; });     &lt;br /&gt;}&lt;/p&gt;  &lt;p&gt;And that is it.&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2020/03/05/This-side-up-please.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2020/03/05/This-side-up-please.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=71b96ec7-875b-4f5c-8d0a-f54402c6248f</guid>
      <pubDate>Thu, 05 Mar 2020 11:29:00 +0000</pubDate>
      <category>Snippets</category>
      <dc:publisher>DotNetNerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=71b96ec7-875b-4f5c-8d0a-f54402c6248f</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=71b96ec7-875b-4f5c-8d0a-f54402c6248f</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2020/03/05/This-side-up-please.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=71b96ec7-875b-4f5c-8d0a-f54402c6248f</wfw:commentRss>
    </item>
    <item>
      <title>Copying data on Azure in code</title>
      <description>&lt;p&gt;I have recently been looking at copying entire collections of data on Azure, in a way that should run as either Azure Functions or Webjobs. This is useful for backups, and simply moving data between environments. I didn't come across too many good samples of how to do this, so I expect it can be a useful topic for others who need to do the same thing.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Copying blob storage containers&lt;/h2&gt;
&lt;p&gt;As always we first need the basics in place. So we spin up CloudBlobClients for the source and the target.&lt;/p&gt;
&lt;p&gt;CloudStorageAccount sourceStorageConnectionString = CloudStorageAccount.Parse(_connectionStringSettings.AzureStorageConnection);    &lt;br /&gt;CloudStorageAccount targetStorageConnectionString = CloudStorageAccount.Parse(_connectionStringSettings.AzureStorageBackupConnection);     &lt;br /&gt;CloudBlobClient sourceCloudBlobClient = sourceStorageConnectionString.CreateCloudBlobClient();     &lt;br /&gt;CloudBlobClient targetCloudBlobClient = targetStorageConnectionString.CreateCloudBlobClient();&lt;/p&gt;
&lt;p&gt;Then we get the CloudBlobContainers and ensure that the target exists.&lt;/p&gt;
&lt;p&gt;string container = "my-super-duper-dumpster";&lt;/p&gt;
&lt;p&gt;CloudBlobContainer sourceContainer = sourceCloudBlobClient.GetContainerReference(container);    &lt;br /&gt;CloudBlobContainer targetContainer = targetCloudBlobClient.GetContainerReference(container);&lt;/p&gt;
&lt;p&gt;await targetContainer.CreateIfNotExistsAsync();&lt;/p&gt;
&lt;p&gt;Now we need a way to iterate through all the blobs. It is quite useful to be able to do this for a variety of operations. Besides copying, we can use it to delete or modify certain blobs. So for this purpose I created this method, that runs through each item and takes some action.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt; private async Task ForeachBlob(CloudBlobContainer container, Func&amp;lt;CloudBlockBlob, Task&amp;gt; action)     &lt;br /&gt; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; BlobContinuationToken blobContinuationToken = null;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; do     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var resultSegment = await container.ListBlobsSegmentedAsync(     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; prefix: null,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; useFlatBlobListing: true,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; blobListingDetails: BlobListingDetails.None,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; maxResults: null,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; currentToken: blobContinuationToken,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; options: null,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; operationContext: null     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; );&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; blobContinuationToken = resultSegment.ContinuationToken;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; foreach (CloudBlockBlob item in resultSegment.Results)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; await action(item);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } while (blobContinuationToken != null);     &lt;br /&gt; }&lt;/p&gt;
&lt;p&gt;With this in place we can start copying. In my case the containers are private, so we need to get a SAS token, so it takes a bit of code like this.&lt;/p&gt;
&lt;p&gt;await ForeachBlob(sourceContainer, async item =&amp;gt; {    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CloudBlockBlob sourceBlob = sourceContainer.GetBlockBlobReference(item.Name);&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var policy = new SharedAccessBlobPolicy    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Permissions = SharedAccessBlobPermissions.Read,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-15),     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SharedAccessExpiryTime = DateTime.UtcNow.AddDays(1)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; };     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; string sourceBlobToken = sourceBlob.GetSharedAccessSignature(policy);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; string sourceBlobSAS = string.Format("{0}{1}", sourceBlob.Uri, sourceBlobToken);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CloudBlobContainer targetContainer = targetCloudBlobClient.GetContainerReference(container);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; await targetContainer.CreateIfNotExistsAsync();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CloudBlockBlob targetBlob = targetContainer.GetBlockBlobReference(item.Name);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; await targetBlob.DeleteIfExistsAsync();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; await targetBlob.StartCopyAsync(new Uri(sourceBlobSAS));     &lt;br /&gt; });&lt;/p&gt;
&lt;p&gt;That gives us all the pieces we need. One small addition that I made was to time and log each operation I did. Putting Stopwatch start, stop etc calls around everything becomes quite ugly and drowns out the more important part of the code, so I made a runner like this, that can handle the minutiae of timing and logging.&lt;/p&gt;
&lt;p&gt;public class Runner&amp;lt;T&amp;gt;    &lt;br /&gt; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; readonly ILogger&amp;lt;T&amp;gt; _logger;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public Runner(ILogger&amp;lt;T&amp;gt; logger)    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _logger = logger;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public async Task Run(Func&amp;lt;Task&amp;gt; func, Func&amp;lt;Stopwatch, string&amp;gt; logTextFactory)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var sw = Stopwatch.StartNew();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; await func();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sw.Stop();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var logText = logTextFactory(sw);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _logger.LogInformation(logText);&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt; }&lt;/p&gt;
&lt;p&gt;Then all it takes is to e.g. move the code that does the actual copying into its own method and wrap it like so.&lt;/p&gt;
&lt;p&gt;await _runner.Run(async () =&amp;gt; {    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; await CopyBlobs(targetCloudBlobClient, container, sourceContainer);     &lt;br /&gt; }, sw =&amp;gt; $"Copying from \"{sourceContainer.Uri}\" to \"{targetContainer.Uri}\" took {sw.Elapsed.ToString("c")}");&lt;/p&gt;
&lt;h2&gt;Copying CosmosDB collections&lt;/h2&gt;
&lt;p&gt;In the same way as I copied blob storage containers, I also needed to copy CosmosDB collections. It is simpler, especially because of the BulkExecutor which has been made for these kinds of scenarios, but I will include a sample here for completeness.&lt;/p&gt;
&lt;p&gt;Again we do the basics and spin up source and target clients.&lt;/p&gt;
&lt;p&gt;var jsonSerializerSettings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.DateTimeOffset };    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;DocumentClient sourceClient = new DocumentClient(     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; new Uri(_connectionStringSettings.DocumentDBConnection), _settings.DocumentStorageSettings.DocumentDbKey,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; jsonSerializerSettings, new ConnectionPolicy { ConnectionMode = ConnectionMode.Direct, ConnectionProtocol = Protocol.Tcp });&lt;/p&gt;
&lt;p&gt;DocumentClient targetClient = new DocumentClient(    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; new Uri(_connectionStringSettings.DocumentDBBackupConnection), _settings.DocumentStorageSettings.DocumentDbBackupKey,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; jsonSerializerSettings, new ConnectionPolicy { ConnectionMode = ConnectionMode.Direct, ConnectionProtocol = Protocol.Tcp });&lt;/p&gt;
&lt;p&gt;With those in place we set up the BulkExecutor.&lt;/p&gt;
&lt;p&gt;targetClient.ConnectionPolicy.RetryOptions.MaxRetryWaitTimeInSeconds = 30;    &lt;br /&gt;targetClient.ConnectionPolicy.RetryOptions.MaxRetryAttemptsOnThrottledRequests = 9;&lt;/p&gt;
&lt;p&gt;IBulkExecutor bulkExecutor = new BulkExecutor(targetClient, documentCollection);    &lt;br /&gt;await bulkExecutor.InitializeAsync();&lt;/p&gt;
&lt;p&gt;targetClient.ConnectionPolicy.RetryOptions.MaxRetryWaitTimeInSeconds = 0;    &lt;br /&gt;targetClient.ConnectionPolicy.RetryOptions.MaxRetryAttemptsOnThrottledRequests = 0;&lt;/p&gt;
&lt;p&gt;Then we are ready to read all the documents in batches and pass each to the bulk executor.&lt;/p&gt;
&lt;p&gt;var documentQuery = sourceClient.CreateDocumentQuery(    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; UriFactory.CreateDocumentCollectionUri(_settings.DocumentStorageSettings.DatabaseName, "MySuperDuperCollection"),     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; new FeedOptions() {/*MaxItemCount = 2000*/ }).AsDocumentQuery();&lt;/p&gt;
&lt;p&gt;while (documentQuery.HasMoreResults)    &lt;br /&gt; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var feed = await documentQuery.ExecuteNextAsync();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; BulkImportResponse bulkImportResponse = await bulkExecutor.BulkImportAsync(     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; documents: feed.ToArray(),     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; enableUpsert: false,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; disableAutomaticIdGeneration: true,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; maxConcurrencyPerPartitionKeyRange: null,     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; maxInMemorySortingBatchSize: null);     &lt;br /&gt; }&lt;/p&gt;
&lt;p&gt;Like in the first sample, we could wrap this part in a method and use the runner to do timing and logging.&lt;/p&gt;
&lt;p&gt;To give a complete overview these are the libraries and versions used in my case.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://blog.dotnetnerd.dk/image.axd?picture=image_8.png"&gt;&lt;img style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" title="image" src="http://blog.dotnetnerd.dk/image.axd?picture=image_thumb.png" border="0" alt="image" width="379" height="215" /&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2020/02/05/Copying-data-on-Azure-in-code.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2020/02/05/Copying-data-on-Azure-in-code.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=69715bb3-44e5-45b2-b769-62c02bced40b</guid>
      <pubDate>Wed, 05 Feb 2020 09:04:00 +0000</pubDate>
      <category>Snippets</category>
      <category>Technology</category>
      <dc:publisher>DotNetNerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=69715bb3-44e5-45b2-b769-62c02bced40b</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=69715bb3-44e5-45b2-b769-62c02bced40b</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2020/02/05/Copying-data-on-Azure-in-code.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=69715bb3-44e5-45b2-b769-62c02bced40b</wfw:commentRss>
    </item>
    <item>
      <title>Online tools and resources</title>
      <description>&lt;p&gt;Every once in a while you run into a great online tool or resource, and makes life as a developer easier. Moving between companies as I do, I often see that people are using services that provide lots of value, but are not necessarily well known by most. So in this post I will start by sharing a few of the services that I have come accross lately.&lt;/p&gt;  &lt;h4&gt;Docs and helpers&lt;/h4&gt;  &lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/api"&gt;https://docs.microsoft.com/en-us/dotnet/api&lt;/a&gt; – API browser for all Microsofts .NET API’s.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://regexr.com/"&gt;https://regexr.com&lt;/a&gt; – best quick regex testing site I know of.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://cronexpressiondescriptor.azurewebsites.net/"&gt;https://cronexpressiondescriptor.azurewebsites.net&lt;/a&gt; – simple way of testing cron expressions.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://rxwiki.wikidot.com/101samples"&gt;http://rxwiki.wikidot.com/101samples&lt;/a&gt; – 101 RX samples for guidance.&lt;/p&gt;  &lt;h4&gt;Image manipulation&lt;/h4&gt;  &lt;p&gt;&lt;a href="https://tinypng.com/"&gt;https://tinypng.com&lt;/a&gt; and &lt;a href="https://squoosh.app"&gt;https://squoosh.app&lt;/a&gt; – two fast and easy solutions for image compression.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://www.base64-image.de"&gt;https://www.base64-image.de&lt;/a&gt; – drag and drop to convert images to base64.&lt;/p&gt;  &lt;h4&gt;Slides and prototyping content&lt;/h4&gt;  &lt;p&gt;&lt;a href="https://unsplash.com"&gt;https://unsplash.com&lt;/a&gt; and &lt;a href="https://pixabay.com/images/search/presentation/"&gt;https://pixabay.com&lt;/a&gt; – quickly search and download tons of free beautiful photos.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://picsum.photos"&gt;https://picsum.photos&lt;/a&gt; – lorem ipsum with images.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://officeipsum.com"&gt;http://officeipsum.com&lt;/a&gt;, &lt;a href="https://hipsum.co"&gt;https://hipsum.co&lt;/a&gt;, &lt;a href="https://pirateipsum.me/"&gt;https://pirateipsum.me&lt;/a&gt; and &lt;a href="https://slipsum.com"&gt;https://slipsum.com&lt;/a&gt; – my favorite ipsum generators.&lt;/p&gt;  &lt;h4&gt;Email&lt;/h4&gt;  &lt;p&gt;&lt;a href="https://temp-mail.org"&gt;https://temp-mail.org&lt;/a&gt; – temporary email for testing and catching spam.&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2019/10/09/Online-tools-and-resources.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2019/10/09/Online-tools-and-resources.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=f8753a1f-4548-478b-9840-7d41d63d0b59</guid>
      <pubDate>Wed, 09 Oct 2019 08:25:54 +0000</pubDate>
      <category>Technology</category>
      <dc:publisher>dotnetnerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=f8753a1f-4548-478b-9840-7d41d63d0b59</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=f8753a1f-4548-478b-9840-7d41d63d0b59</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2019/10/09/Online-tools-and-resources.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=f8753a1f-4548-478b-9840-7d41d63d0b59</wfw:commentRss>
    </item>
    <item>
      <title>Azure AD B2C easy auth across frontend and backend</title>
      <description>&lt;p&gt;Recently I had the need to setup easy auth using Azure B2C to authenticate users across a frontend Azure Web App and an Azure Functions backend. Allthough it sounds like a regular scenario, the documentation I found could have been better. I don&amp;rsquo;t have the time to write the complete docs, but this blogpost will outline the steps, so I can remember it for next time, and hopefully to enable you to do the same kind of setup. Let&amp;rsquo;s get cracking. &lt;/p&gt;
&lt;h2&gt;Applications&lt;/h2&gt;
&lt;p&gt;I will assume the Azure AD B2C has been provisioned, and if it is not, there is pleanty of documentation on that part. From there you first need to set up the frontend and backend as seperate applications in the AD.&lt;/p&gt;
&lt;p&gt;In the Azure portal go to the Azure AD B2C and select applications. Here we have to setup the frontend and the backend respectively. Click add and enter a name for the application and choose &amp;ldquo;include web app / web API&amp;rdquo; for both applications.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img style="margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;" title="image" src="http://blog.dotnetnerd.dk/image.axd?picture=image_4.png" border="0" alt="image" width="215" height="361" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-size: small;"&gt;For each application select it in the application list and setup the reply URL to point to the call back. The url should point to https.//&amp;lt;mysitesurl&amp;gt;/.auth/login/aad/callback.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-size: small;"&gt;For the frontend select the keys section and generate a key. Write it down, as you cannot retrieve it later and it is needed at a later step.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-size: small;"&gt;For the backend provide an App Id URI&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img style="margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;" title="image" src="http://blog.dotnetnerd.dk/image.axd?picture=image_5.png" border="0" alt="image" width="515" height="179" /&gt;&lt;/p&gt;
&lt;h2&gt;Published scopes and API Access&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;Next the backend is setup to define access scopes. To do this &lt;span&gt;select the backend application and go to published scopes.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Enter a name for a new scope and write down the full scope value, which you will need later.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img style="margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;" title="image" src="http://blog.dotnetnerd.dk/image.axd?picture=image_6.png" border="0" alt="image" width="524" height="138" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;To require access to the backend the frontend needs to set up API Access. To do this s&lt;span&gt;elect the frontend application and go to API acces.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Click add and select the api you you just published. Check the scopes you wish to have access to.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;User flows custom design&lt;/h2&gt;
&lt;p&gt;As an optional step, but something you will most likely need we will now set up a custom design.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Upload the custom design to blob storage using the portal by going to the storage account and selecting blobs. Create one if it does not already exist. Upload the static design files and note down the url&amp;rsquo;s for the login and reset pages.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;From the Azure AD B2C overview choose user flows.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Create a Sign in v2 flow. Select the identity providers you need to support. Select the claims that you need. Most often it would be display name, email addresses and identity provider.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Under Page Layouts select &amp;ldquo;Use custom page content&amp;rdquo;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img style="margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;" title="clip_image007" src="http://blog.dotnetnerd.dk/image.axd?picture=clip_image007_1.jpg" border="0" alt="clip_image007" width="528" height="203" /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Enter the url for the customized layout for the login.&lt;/p&gt;
&lt;p&gt;&lt;img style="margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;" title="image" src="http://blog.dotnetnerd.dk/image.axd?picture=image_7.png" border="0" alt="image" width="523" height="203" /&gt;&lt;/p&gt;
&lt;h2&gt;Setup easy auth from the CLI&lt;/h2&gt;
&lt;p&gt;Now everything is setup and we are ready to turn on easy auth.&lt;/p&gt;
&lt;p&gt;Setting up easy auth is as simple as a oneliner using the Azure CLI. It needs to be run for both the frontend and backend application.&lt;/p&gt;
&lt;p&gt;az webapp auth update --name $appname --resource-group $ResourceName --enabled true --action LoginWithAzureActiveDirectory --aad-client-id $aadClientId --aad-allowed-token-audiences $url + "/.auth/login/aad/callback" --aad-client-secret $secret --aad-token-issuer-url $issuerurl&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;$aadClientId is the Azure AD ClientId that must be obtained from the AD&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;$url is the url for the app&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;$secret is the key generated in the AD B2C&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;$issuerurl is the url for the user flow in the AD (e.g. https://myad.b2clogin.com/myad.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_SignInFlow)&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Last kind of strange step&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;Lastly we need to set additionalLoginParams for the frontend app. Strangely this is not well supported via the CLI or Portal, so we need to use resource explorer to get the job done (&lt;/span&gt;&lt;a href="https://resources.azure.com"&gt;&lt;span&gt;https://resources.azure.com&lt;/span&gt;&lt;/a&gt;&lt;span&gt;). Pick your subscription and navigate to resourceGroups/&amp;lt;resourcegroup&amp;gt;/providers/Microsoft.Web/sites/&amp;lt;fontendapp&amp;gt;/config/authsettings &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Add an additionalLoginParams value with the required scopes. These are likely to be openid, offline_access and the full scope value defined in the AD B2C for the backend (e.g. "scope=openid%20offline_access%20https%3A%2F%2Fmyad.onmicrosoft.com%2Fapi%2FmyApi.ReadWrite"). Notice this needs to be url encoded.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Now you are all set. To make an http request to the backend you need to add a bearer token. This is retrieved from the /.auth/me route on the frontend. Here you also see refresh tokens and the claims that you requested.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;And done&amp;hellip;&lt;/span&gt;&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2019/04/02/Azure-AD-B2C-easy-auth-across-frontend-and-backend.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2019/04/02/Azure-AD-B2C-easy-auth-across-frontend-and-backend.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=cc71a6b3-8466-44e3-98bc-eb9f59e2cca1</guid>
      <pubDate>Tue, 02 Apr 2019 17:02:00 +0000</pubDate>
      <category>Technology</category>
      <dc:publisher>DotNetNerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=cc71a6b3-8466-44e3-98bc-eb9f59e2cca1</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=cc71a6b3-8466-44e3-98bc-eb9f59e2cca1</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2019/04/02/Azure-AD-B2C-easy-auth-across-frontend-and-backend.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=cc71a6b3-8466-44e3-98bc-eb9f59e2cca1</wfw:commentRss>
    </item>
    <item>
      <title>Why cloud native is a gamechanger</title>
      <description>&lt;p&gt;Cloud native is one of those words that make some people shake their heads and call BS. In some contexts I am one of those people. It does however also have its place, because building solutions that are cloud centric does come with a number of benefits and enables solutions that were very hardif not impossible pre-cloud.&lt;/p&gt;  &lt;p&gt;Sure, you can script the setup of a server from scratch, but it requires quite a bit of work, it takes time to execute and you still end up with an environment that requires updates and patching as soon as the script is a week old. In a cloud setup good practices, in the form of DevOps mainly using the CLI makes this very obtainable. Actually the current environment I am working with combines an ARM template and a few lines of script so we can spin up an entire environment in about 15 minutes. The only manual step is setting up the domain and SSL cert, but even that could be scripted if I wanted to.&lt;/p&gt;    &lt;h2&gt;Simplicity through serverless&lt;/h2&gt;  &lt;p&gt;Serverless is of course also a feature of cloud technology. It has quickly become one I love because it is, in a sence, the single responsibility principle taken to its conclusion. Building a regular WebAPI does not make you fall into the pit of success, simply because structuring functions well is difficult, and pretty much always leads to breaking the SRP in some degree. Creating one function at a time, with only the required dependencies and zero ceremony is likely to give you a much better foundation. A main focus of good software design really is dependency management, so being nudged in the right direction is hugely important.&lt;/p&gt;  &lt;p&gt;In our case we went all in on JavaScript, and for our use case just having one language and especially one way if modeling has made life easy. We don't have too many complex rules or models, so here simplicity wins over having a more expressive language. The awesome thing is that in any case, we can adapt, because we are free to write some functions in eg C# if the need arises.&lt;/p&gt;  &lt;h2&gt;No more “SQL Server for everything”&lt;/h2&gt;  &lt;p&gt;Polyglot persistence is another paradigme that wins out by a cloud setup. If you have to install and maintain a bunch if databases yourself or wait for your it-department to do it, you will with good reason be reluctant to do it. When you can provision different databases as PaaS services it is trivial to do, and maintenance is no longer an issue. Simply put it allows you to pick the right tool for the job, without making you suffer for it. &lt;/p&gt;  &lt;p&gt;I used to do what most developers did, and based everything on SQL server, because it allowed for rich querying, transactions and a fair amount of flexibility. It clearly came at the cost if spending a lot of time managing schemas, mapping data and solving performance issues, but there was no better way available at the time. &lt;/p&gt;  &lt;p&gt;On Azure I get CosmosDB, which with the SQL API lets us save json, which means deeply nested models in the same shape as objects. So we spend less time bridging technologies, which makes us perform better, while the queries also perform better. We still have rich querying capabilities, transactions and we can even choose different API's if we need table or graph models. This in itself is important, but for the overall architecture the ability to add Redis for caching, table storage for huge amounts of tabular data or even a good old SQL database for doing BI is the real gamechanger.&lt;/p&gt;  &lt;p&gt;Lastly there are all the other services that are available and can quickly be added as needed. An example I often think of is all the times I have seen solutions with custom &amp;quot;queues&amp;quot; implemented on SQL server. Again this was done to avoid adding a server that should be maintained, but at the cost of performance and not least bugs, because making a robust queue is not trivia. I am so glad those days are over! On top of all this comes Azure Vault, SignalR, IoT, ML, mobile, logic apps and I could go on and on listing specialized services, but suffice to say I love how architecture is no longer about limiting yourself, but about choosing the right tools.&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2018/11/09/Why-cloud-native-is-a-gamechanger.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2018/11/09/Why-cloud-native-is-a-gamechanger.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=dd67f43a-81e7-4f79-ba35-ff353ece251b</guid>
      <pubDate>Fri, 09 Nov 2018 11:25:09 +0000</pubDate>
      <category>Architecture</category>
      <category>Technology</category>
      <dc:publisher>dotnetnerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=dd67f43a-81e7-4f79-ba35-ff353ece251b</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=dd67f43a-81e7-4f79-ba35-ff353ece251b</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2018/11/09/Why-cloud-native-is-a-gamechanger.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=dd67f43a-81e7-4f79-ba35-ff353ece251b</wfw:commentRss>
    </item>
    <item>
      <title>UI testing done right with Cypress.IO</title>
      <description>&lt;p&gt;Finally, someone has written a UI testing tool for the web and done it right! For at least 5 years I have been envious of the UI testing tools that were written for native application development. I have tried various tools for UI testing websites, but they all relied on selenium, which sucks harder than my vacuum cleaner. No matter how much lipstick you put on a pig, it is still a pig, so the brittle nature of selenium would bleed through, and require you to do updates to drivers as well as handle very low level things like timing between a click and the actual page being re-rendered. So working with those tools has been slow and painful.&lt;/p&gt;    &lt;p&gt;By coincidence I stumbled on &lt;a href="https://www.cypress.io/"&gt;cypress.io&lt;/a&gt; a few days ago, and right how I think it is the best thing since sliced bread. &lt;/p&gt;  &lt;p&gt;Getting set up is litterally one single npm install, and they you are ready to go. The tests are written using mocha, chai and sinon, like we are used to. To there is no learning curve there. &lt;/p&gt;  &lt;p&gt;Controlling the browser actions is done through an API that is so simple that my first test worked right off the bat, and it is very well documented. On top of that it includes the jquery, lowdash, moment, minimatch, blob and promise libraries that are all well known for most of us working with javascript.&lt;/p&gt;  &lt;p&gt;Running the tests is done using the equally well documented and easy to use cypress cli, which can either run the tests directly using a headless browser, or it can open the cypress application, where you get a well designed UI. In the UI you can pick exactly which tests to run, you see them running I&amp;#160; a browser window and the steps are listed. Not only that but you get time travel by hovering each step, so you will see exactly what the pages looked like at that point in time. On top of this it records video and takes screenshots when an error occurs, so you can add that to your bug report.&lt;/p&gt;  &lt;p&gt;The ability to do this using a headless browser means that it is really easy to integrate into your CI build. Actually they have comprehensive guides for all the big CI solutions. It also includes reporters, so you get a nice report as part of your overall CI report.&lt;/p&gt;  &lt;p&gt;So it is safe to say that I am very impressed and I can't wait to use this for my current project. Oh and I mention it is free, as in beer. &lt;/p&gt;  &lt;p&gt;There is one downside currently that I ha e to call out. It is the fact that currently it is limited to the different versions of chrome browsers. No IE, no Edge and no Firefox. I believe it is being worked on, from what I read on the website, so we will get there, but for now that means it does not solve cross browser testing.&lt;/p&gt;</description>
      <link>https://blog.dotnetnerd.dk/post/2018/10/11/UI-testing-done-right-with-CypressIO.aspx</link>
      <author>christian@dotnetnerd.dk</author>
      <comments>https://blog.dotnetnerd.dk/post/2018/10/11/UI-testing-done-right-with-CypressIO.aspx#comment</comments>
      <guid>https://blog.dotnetnerd.dk/post.aspx?id=61dbfa92-59e1-4479-9560-107b707b3c96</guid>
      <pubDate>Thu, 11 Oct 2018 12:07:40 +0000</pubDate>
      <category>Technology</category>
      <dc:publisher>dotnetnerd</dc:publisher>
      <pingback:server>https://blog.dotnetnerd.dk/pingback.axd</pingback:server>
      <pingback:target>https://blog.dotnetnerd.dk/post.aspx?id=61dbfa92-59e1-4479-9560-107b707b3c96</pingback:target>
      <slash:comments>0</slash:comments>
      <trackback:ping>https://blog.dotnetnerd.dk/trackback.axd?id=61dbfa92-59e1-4479-9560-107b707b3c96</trackback:ping>
      <wfw:comment>https://blog.dotnetnerd.dk/post/2018/10/11/UI-testing-done-right-with-CypressIO.aspx#comment</wfw:comment>
      <wfw:commentRss>https://blog.dotnetnerd.dk/syndication.axd?post=61dbfa92-59e1-4479-9560-107b707b3c96</wfw:commentRss>
    </item>
  </channel>
</rss>