<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Manuel Guilbault's Blog</title>
    <description></description>
    <link>http://manuelguilbault.com/</link>
    <atom:link href="http://manuelguilbault.com/rss.xml" rel="self" type="application/rss+xml" />
    <pubDate>Thu, 08 Mar 2018 21:39:33 +0000</pubDate>
    <lastBuildDate>Thu, 08 Mar 2018 21:39:33 +0000</lastBuildDate>
    <language>en-us</language>
    <image>
      <url>http://manuelguilbault.com/images/favicons/favicon-96x96.png</url>
      <title>Manuel Guilbault's Blog</title>
      <link>http://manuelguilbault.com/</link>
      <width>96</width>
      <height>96</height>
    </image>
    <copyright>Copyright 2018 - Manuel Guilbault. All rights reserved</copyright>
    <generator>Jekyll v3.6.2</generator>
    
      <item>
        <title>Adding deep linking support to an Azure Functions-based Aurelia app</title>
        <description>&lt;p&gt;In this serie:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/08/22/Hosting-an-Aurelia-app-on-Azure/&quot;&gt;Hosting an Aurelia app on Azure&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/12/04/Deploying-an-Aurelia-app-on-Azure-using-VSTS/&quot;&gt;Deploying an Aurelia app on Azure using VSTS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/12/07/Adding-deep-linking-support-to-Azure-Functions-based-Aurelia-app/&quot;&gt;Adding deep linking support to an Azure Functions-based Aurelia app (this post)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Adding Let’s Encrypt to an Azure Functions-based Aurelia app (coming soon)&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;In the previous posts of this serie, we saw a very unexpensive solution to host an Aurelia app on Azure.
We also saw how to automate the deployment process of an Aurelia app on Azure using Visual Studio Team Services.&lt;/p&gt;

&lt;p&gt;However, the current state of the solution doesn’t support deep linking if the app uses the router in push state mode.
In this post, we’ll see how to fix this.&lt;/p&gt;

&lt;p&gt;You can get the sample Aurelia app I used for this post 
&lt;a href=&quot;https://github.com/manuel-guilbault/blog-post-aurelia-azure/releases/tag/2017-12-08-Adding-deep-linking-support-to-Azure-Functions-based-Aurelia-app&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;understanding-how-deep-linking-works-in-push-state-mode&quot;&gt;Understanding how deep linking works in push state mode&lt;/h2&gt;

&lt;p&gt;Imagine an Aurelia app that uses the router in push state mode. Imagine this app is deployed on 
&lt;code class=&quot;highlighter-rouge&quot;&gt;http://www.my-awesome-app.com/&lt;/code&gt;. Imagine that a user accesses the app and, through the navigation menu, 
navigates to &lt;code class=&quot;highlighter-rouge&quot;&gt;http://www.my-awesome-app.com/some-feature&lt;/code&gt; and bookmarks this URL.
What normally happens when he comes back to this bookmarked URL later?&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The web server receives a GET request for &lt;code class=&quot;highlighter-rouge&quot;&gt;/some-feature&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;It searches its file system (or its storage mechanism, whatever it is) for a file matching this path,
but can’t find any matching file.&lt;/li&gt;
  &lt;li&gt;It returns a &lt;code class=&quot;highlighter-rouge&quot;&gt;200 OK&lt;/code&gt; response with the content of &lt;code class=&quot;highlighter-rouge&quot;&gt;/index.html&lt;/code&gt; instead of a &lt;code class=&quot;highlighter-rouge&quot;&gt;404 Not Found&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Since the user’s browser received &lt;code class=&quot;highlighter-rouge&quot;&gt;/index.html&lt;/code&gt;, the Aurelia app starts.&lt;/li&gt;
  &lt;li&gt;Since the path of the current URL is &lt;code class=&quot;highlighter-rouge&quot;&gt;/some-feature&lt;/code&gt; in the user’s browser, the Aurelia router 
loads the component linked to this path. Everything works as expected.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the moment, our solution doesn’t perform step 4 properly. If the proxy app receives a GET request
for a path not matching a file in the Storage container, it just returns a &lt;code class=&quot;highlighter-rouge&quot;&gt;404 Not Found&lt;/code&gt; response.
How can we fix that?&lt;/p&gt;

&lt;h2 id=&quot;replacing-the-proxy-functions&quot;&gt;Replacing the proxy functions&lt;/h2&gt;

&lt;p&gt;At the moment of writing, the Azure Functions proxies don’t support this type of fallback mechanism.
The only solution is to remove the &lt;code class=&quot;highlighter-rouge&quot;&gt;proxies.json&lt;/code&gt; file and to create a full-fledged Azure Function
that will act as a proxy and that will implement the fallback mechanism.&lt;/p&gt;

&lt;p&gt;First, delete the &lt;code class=&quot;highlighter-rouge&quot;&gt;azure/functions-app/proxies.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Then, in your Aurelia app root directory, adjust the file structure to match the following:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;azure
└── functions-app
    ├── proxy
    │   ├── function.json
    │   └── index.js
    └── host.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, put the following snippet in &lt;code class=&quot;highlighter-rouge&quot;&gt;azure/functions-app/proxy/function.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bindings&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;httpTrigger&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;in&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;authLevel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;methods&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;get&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;route&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{*path}&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;$return&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;out&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This file states that the function will be triggered when the Functions app receives a GET request to
any path. The function will receive the HTTP request as its &lt;code class=&quot;highlighter-rouge&quot;&gt;request&lt;/code&gt; parameter, and will have the
request path available as the &lt;code class=&quot;highlighter-rouge&quot;&gt;path&lt;/code&gt; context variable.&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;highlighter-rouge&quot;&gt;azure/functions-app/proxy/index.js&lt;/code&gt;, put the following code:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'http'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'path'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parseUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formatUrl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'url'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bindingData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;backendUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getBackendUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;backendUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Request to &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;backendUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; failed: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'500 Internal Server Error'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;indexUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getBackendUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'index.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  
      &lt;span class=&quot;nx&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Request to &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; failed: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'500 Internal Server Error'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Request to &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; returned &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getBackendUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;storageHostAndPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Storage.HostAndContainer'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sasToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Storage.SasToken'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'index.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hostAndPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;posix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storageHostAndPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rawUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`http://&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hostAndPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sasToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parseUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rawUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toAzureFunctionsResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toAzureFunctionsResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;azureFunctionsResponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'data'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;azureFunctionsResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;azureFunctionsResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This file exports a function which will be executed by the Azure Functions runtime based on the triggers
it is bound to in the &lt;code class=&quot;highlighter-rouge&quot;&gt;function.json&lt;/code&gt; file. It first retrieves the request &lt;code class=&quot;highlighter-rouge&quot;&gt;path&lt;/code&gt; from its execution context,
and computes the URL to the Storage container for this path using the &lt;code class=&quot;highlighter-rouge&quot;&gt;Storage.HostAndContainer&lt;/code&gt; and
&lt;code class=&quot;highlighter-rouge&quot;&gt;Storage.SasToken&lt;/code&gt; app settings. It then forwards the request to this URL. If the path is found on the
Storage container, the response is piped back to the client. If the Storage container returns a &lt;code class=&quot;highlighter-rouge&quot;&gt;404 Not Found&lt;/code&gt;,
the function then falls back to &lt;code class=&quot;highlighter-rouge&quot;&gt;/index.html&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Lastly, we need to change the &lt;code class=&quot;highlighter-rouge&quot;&gt;azure/functions-app/host.json&lt;/code&gt; file. By default, Azure Functions HTTP triggers
will match only routes with the &lt;code class=&quot;highlighter-rouge&quot;&gt;/api&lt;/code&gt; prefix. We just need to remove this default prefix:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;routePrefix&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can now redeploy this app (this should be easy if you followed my 
&lt;a href=&quot;/blog/2017/12/04/Deploying-an-Aurelia-app-on-Azure-using-VSTS/&quot;&gt;previous post&lt;/a&gt;).
If you give it a try, deep linking should now work properly.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Solving the deep linking problem was not that complicated. However, it would be pretty neat if 
the Azure Functions proxies supported this kind of feature (there’s already a 
&lt;a href=&quot;https://github.com/Azure/Azure-Functions/issues/606&quot; target=&quot;_blank&quot;&gt;feature request&lt;/a&gt; for this).
In the meantime, this work around is an okay enough solution.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;

&lt;p&gt;In my next post, we’ll see how to add a custom domain to our app. We’ll also see how to integrate the
&lt;a href=&quot;https://github.com/sjkp/letsencrypt-siteextension&quot; target=&quot;_blank&quot;&gt;Let’s Encrypt Azure site extension&lt;/a&gt; with our
Azure Functions app, so we can enable HTTPS by generating an SSL certificate for free using
&lt;a href=&quot;https://letsencrypt.org/&quot; target=&quot;_blank&quot;&gt;Let’s Encrypt&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Thu, 07 Dec 2017 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2017/12/07/Adding-deep-linking-support-to-Azure-Functions-based-Aurelia-app/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2017/12/07/Adding-deep-linking-support-to-Azure-Functions-based-Aurelia-app/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>Azure</category>
        
        <category>VSTS</category>
        
        <category>DevOps</category>
        
        
      </item>
    
      <item>
        <title>Deploying an Aurelia app on Azure using VSTS</title>
        <description>&lt;p&gt;In this serie:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/08/22/Hosting-an-Aurelia-app-on-Azure/&quot;&gt;Hosting an Aurelia app on Azure&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/12/04/Deploying-an-Aurelia-app-on-Azure-using-VSTS/&quot;&gt;Deploying an Aurelia app on Azure using VSTS (this post)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/12/07/Adding-deep-linking-support-to-Azure-Functions-based-Aurelia-app/&quot;&gt;Adding deep linking support to an Azure Functions-based Aurelia app&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Adding Let’s Encrypt to an Azure Functions-based Aurelia app (coming soon)&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;In my &lt;a href=&quot;/blog/2017/08/22/Hosting-an-Aurelia-app-on-Azure/&quot;&gt;previous post&lt;/a&gt;,
I explained how to host an Aurelia application on Azure for very cheap.&lt;/p&gt;

&lt;p&gt;In this post, I’ll explain how to use 
&lt;a href=&quot;https://www.visualstudio.com/&quot; target=&quot;_blank&quot;&gt;Visual Studio Team Services&lt;/a&gt;
(VSTS) to easily deploy a static website or SPA application to Azure.&lt;/p&gt;

&lt;p&gt;We’ll start by automating the creation of our Azure infrastructure using 
an Azure Resource Manager template, then we’ll script our Azure Functions 
proxy app. Lastly, we’ll create a build definition and a release definition in 
VSTS to automate the whole deployment process.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You’ll need a &lt;a href=&quot;https://www.visualstudio.com/fr/team-services/&quot; target=&quot;_blank&quot;&gt;VSTS account&lt;/a&gt; to
follow the third part of this post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;You can get the sample Aurelia app I used for this post 
&lt;a href=&quot;https://github.com/manuel-guilbault/blog-post-aurelia-azure/releases/tag/2017-12-01-Deploying-an-Aurelia-app-on-Azure-using-VSTS&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
Additionally, this repository contains some files used for deployment on Azure
in the &lt;code class=&quot;highlighter-rouge&quot;&gt;azure&lt;/code&gt; directory. More on this later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-azure-resource-manager-template&quot;&gt;The Azure Resource Manager template&lt;/h2&gt;

&lt;p&gt;We can imagine that this application is part of a larger system. In such a
context, we probably would want to deploy this system on
additional environments such as development, staging, integration, and so on.
However, in the previous post, I created the Azure infrastructure manually
using the Azure portal. It makes our infrastructure unreplicable, and makes
creating those other environments tedious and error-prone.&lt;/p&gt;

&lt;p&gt;Azure offers a piece of technology called Azure Resource Manager (ARM) templates.
An ARM template is a JSON file that describes a set of Azure resources to 
deploy, along with how they depend on each other, so they can be deployed
in the right order. Additionally, if a given Azure resource already exists,
the ARM API is intelligent enough to simply update it so it matches the new
definition. Thanks to this, a stateful resource such as a Blob storage or a
SQL database won’t lose its state.&lt;/p&gt;

&lt;p&gt;We’ll start by creating an ARM template to define the whole Azure
infrastructure we manually created in the previous post.&lt;/p&gt;

&lt;p&gt;First, we’ll create an &lt;code class=&quot;highlighter-rouge&quot;&gt;azure&lt;/code&gt; directory in our Aurelia application, in
which we’ll create the following &lt;code class=&quot;highlighter-rouge&quot;&gt;azuredeploy.json&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;contentVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;outputs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is the standard ARM template skeleton. A template can have input &lt;code class=&quot;highlighter-rouge&quot;&gt;parameters&lt;/code&gt;,
and can define &lt;code class=&quot;highlighter-rouge&quot;&gt;variables&lt;/code&gt;. Of course, it is pretty useless if it doesn’t define
some &lt;code class=&quot;highlighter-rouge&quot;&gt;resources&lt;/code&gt;. Finally, it can export some &lt;code class=&quot;highlighter-rouge&quot;&gt;outputs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In addition to this structure, a template can use a rich expression syntax to
access resources’ properties, compute values from others, or access variables or
parameters. You can get more familiar with ARM templates by checking the
&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates&quot; target=&quot;_blank&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-parameters&quot;&gt;The parameters&lt;/h3&gt;

&lt;p&gt;Let’s add the following &lt;code class=&quot;highlighter-rouge&quot;&gt;parameters&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;appName&lt;/code&gt;: the name of the Azure Function app service. It must be globally unique,
as it will be used in the web app’s domain name. This parameter will be optional,
as its default value will be generated by the template.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;storageAccountName&lt;/code&gt;: the name of the Storage account that will store the application’s files.
It must be globally unique. This parameter will be optional, as its default value will be 
generated by the template.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;storageAccountType&lt;/code&gt;: the replication type of Blob Storage account. This parameter
will be optional, as its default value will be &lt;code class=&quot;highlighter-rouge&quot;&gt;Standard_LRS&lt;/code&gt; (see
&lt;em&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/storage/common/storage-introduction&quot; target=&quot;_blank&quot;&gt;locally redundant storage&lt;/a&gt;&lt;/em&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;blobContainerName&lt;/code&gt;: the name of the Blob Storage container in which our Aurelia
application files will be stored. This parameter will be optional, as its default value
will be &lt;code class=&quot;highlighter-rouge&quot;&gt;web-app-content&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To add those parameters, simply replace the &lt;code class=&quot;highlighter-rouge&quot;&gt;parameters&lt;/code&gt; object with the following:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;appName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;defaultValue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[uniquestring('manuelguilbault.com', 'aurelia-azure', resourceGroup().id, 'webapp')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The name of your proxy webapp.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;storageAccountName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;defaultValue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[uniquestring('manuelguilbault.com', 'aurelia-azure', resourceGroup().id, 'storage')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The name of the Storage account.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;storageAccountType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;defaultValue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Standard_LRS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;allowedValues&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Standard_LRS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Standard_GRS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Standard_RAGRS&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The type of Storage account&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;blobContainerName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;defaultValue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;web-app-content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The name of the blob container where the web app is stored.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;It is important to note that the &lt;code class=&quot;highlighter-rouge&quot;&gt;uniquestring&lt;/code&gt; function used to generate the default 
value of the &lt;code class=&quot;highlighter-rouge&quot;&gt;appName&lt;/code&gt; and the &lt;code class=&quot;highlighter-rouge&quot;&gt;storageAccountName&lt;/code&gt; parameters computes a deterministic 
value based on its arguments. Since App Service and Storage account names must be globally 
unique on Azure, you’ll most certainly need to change the set of arguments passed to 
those &lt;code class=&quot;highlighter-rouge&quot;&gt;uniquestring&lt;/code&gt; calls if you want to deploy this template.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;the-variables&quot;&gt;The variables&lt;/h3&gt;

&lt;p&gt;Next, let’s use those parameters and define some variables that we’ll use
in the description of the resources we wish to deploy:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;functionAppName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[parameters('appName')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hostingPlanName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[parameters('appName')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;storageAccountName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[parameters('storageAccountName')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;storageAccountType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[parameters('storageAccountType')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;blobContainerName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[parameters('blobContainerName')]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Simply replace the &lt;code class=&quot;highlighter-rouge&quot;&gt;variables&lt;/code&gt; object with the previous snippet in the template.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;As a rule of thumb, I always assign each parameter to a variable, so the &lt;code class=&quot;highlighter-rouge&quot;&gt;resources&lt;/code&gt;
declarations never directly depend on parameters but only on variables. This makes the
template easier to change, as you can easily add or remove parameters and change how a 
variable’s value is computed without having to change multiple resource declarations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;the-resources&quot;&gt;The resources&lt;/h3&gt;

&lt;p&gt;Our template will declare 3 different resources: a Storage account, a hosting plan, and a functions app.
Let’s see how this looks like:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Microsoft.Storage/storageAccounts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[variables('storageAccountName')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2017-06-01&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[resourceGroup().location]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Storage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[variables('storageAccountType')]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Microsoft.Web/serverfarms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2015-04-01&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[variables('hostingPlanName')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[resourceGroup().location]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[variables('hostingPlanName')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;computeMode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Dynamic&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sku&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Dynamic&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Microsoft.Web/sites&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2015-08-01&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[variables('functionAppName')]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[resourceGroup().location]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;functionapp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;dependsOn&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;serverFarmId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;siteConfig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;appSettings&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AzureWebJobsDashboard&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AzureWebJobsStorage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;WEBSITE_CONTENTAZUREFILECONNECTIONSTRING&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;WEBSITE_CONTENTSHARE&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[toLower(variables('functionAppName'))]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;FUNCTIONS_EXTENSION_VERSION&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;comment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;This will enable proxy functions. See https://github.com/Azure/Azure-Functions/issues/356&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ROUTING_EXTENSION_VERSION&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~0.2&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;WEBSITE_NODE_DEFAULT_VERSION&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;6.5.0&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Storage.HostAndContainer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[concat(variables('storageAccountName'), '.blob.core.windows.net/', variables('blobContainerName'))]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first object in the array describes the Storage account, and the second the hosting plan. Here, the
hosting plan uses the &lt;code class=&quot;highlighter-rouge&quot;&gt;Dynamic&lt;/code&gt; SKU, so the pricing is calculated based on the consumption.&lt;/p&gt;

&lt;p&gt;The third object describes the Functions app acting as a proxy in front of the Storage container. It contains
a couple of interesting details:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;dependsOn&lt;/code&gt; array contains the ID of the first and second resources. This is to make sure that the
web app is provisioned only once the Storage account and the hosting plan have been deployed.&lt;/li&gt;
  &lt;li&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;appSettings&lt;/code&gt; array contains all the application settings that are either static values or that
are known in the scope of the ARM template.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can simply replace the value of the &lt;code class=&quot;highlighter-rouge&quot;&gt;resources&lt;/code&gt; property in the template with the previous snippet.&lt;/p&gt;

&lt;h3 id=&quot;the-outputs&quot;&gt;The outputs&lt;/h3&gt;

&lt;p&gt;Depending on which parameters are specified when the template is executed, some resource names may be
generated by the template. However, some of those names may be required by the release pipeline. As such,
we’ll output those names, so the release pipeline can gather them up after the template is deployed and
use them in further release steps:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AppService.Name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[variables('functionAppName')]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Storage.Account.Name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[variables('storageAccountName')]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Storage.Container.Name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[variables('blobContainerName')]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Just replace the value of the &lt;code class=&quot;highlighter-rouge&quot;&gt;outputs&lt;/code&gt; property in the template with the previous snippet.&lt;/p&gt;

&lt;h2 id=&quot;the-azure-functions-app&quot;&gt;The Azure Functions app&lt;/h2&gt;

&lt;p&gt;In the previous post, we created the Azure Functions proxy app manually through the Azure portal.
In order to make our deployment automatic and replicable, we need to script it.&lt;/p&gt;

&lt;p&gt;In your Aurelia app root directory, create the following file structure:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;azure
└── functions-app
    ├── host.json
    └── proxies.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;highlighter-rouge&quot;&gt;functions-app/host.json&lt;/code&gt;, just put an empty JSON object:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;highlighter-rouge&quot;&gt;functions-app/proxies.json&lt;/code&gt;, put the following:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://json.schemastore.org/proxies&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;proxies&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;matchCondition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;route&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/{*path}&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;backendUri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://%Storage.HostAndContainer%/{path}%Storage.SasToken%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;root&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;matchCondition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;route&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;backendUri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://%Storage.HostAndContainer%/index.html%Storage.SasToken%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will have the exact same result as what we did using the Azure portal in the previous post.&lt;/p&gt;

&lt;h2 id=&quot;visual-studio-team-services&quot;&gt;Visual Studio Team Services&lt;/h2&gt;

&lt;p&gt;Now that our ARM template and our Functions app are ready, we can start creating our build and 
release pipelines in VSTS.&lt;/p&gt;

&lt;p&gt;If you’re not familiar with VSTS, let’s just say that it supports two different types of pipelines:
builds and releases. A build pipeline’s job is to process some source code and to create one or more
build artifacts out of it. A release pipeline’s job is to take one or more build artifacts and to
deploy them to a hosting environment. In our case, this hosting environment will be Azure.&lt;/p&gt;

&lt;p&gt;Build and release pipelines are made of tasks. A task has a set of settings, based on its type, and
variables can be used in those settings. A variable is accessed using the &lt;code class=&quot;highlighter-rouge&quot;&gt;$(variable_name)&lt;/code&gt; notation.
You’ll see examples of this later.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;For the following part, you’ll need to host your Aurelia app along with the &lt;code class=&quot;highlighter-rouge&quot;&gt;azure&lt;/code&gt; directory
containing the ARM template and the Functions proxy app on a Git repository. VSTS supports a couple
of different providers, such as GitHub or Bitbucket. However, unless you already created a Git
repository somewhere else, the simplest solution is to create a Git repository on VSTS and to push
your code there.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you never did it before, you’ll need to authorize VSTS to deploy on your Azure 
subscription. You can follow &lt;a href=&quot;https://blogs.msdn.microsoft.com/devops/2015/10/04/automating-azure-resource-group-deployment-using-a-service-principal-in-visual-studio-online-buildrelease-management/&quot; target=&quot;_blank&quot;&gt;this blog post&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;the-build-pipeline&quot;&gt;The build pipeline&lt;/h3&gt;

&lt;p&gt;A build pipeline is a sequence of tasks, which should publish one or more build artifacts.&lt;/p&gt;

&lt;h4 id=&quot;creating-the-build-definition&quot;&gt;Creating the build definition&lt;/h4&gt;

&lt;p&gt;In VSTS, go to &lt;code class=&quot;highlighter-rouge&quot;&gt;Build and Release&lt;/code&gt;, then &lt;code class=&quot;highlighter-rouge&quot;&gt;Builds&lt;/code&gt;, then &lt;code class=&quot;highlighter-rouge&quot;&gt;New&lt;/code&gt;. You’ll be asked to choose a template.
Just select &lt;code class=&quot;highlighter-rouge&quot;&gt;Empty process&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;VSTS will show the following screen, where the &lt;code class=&quot;highlighter-rouge&quot;&gt;Process&lt;/code&gt; tab is selected in the left panel:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Build-Process-Step.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Build-Process-Step.png&quot; alt=&quot;Create Build pipeline, Process step&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, give a name to your build pipeline and select the &lt;code class=&quot;highlighter-rouge&quot;&gt;Hosted&lt;/code&gt; agent queue.
Then, click on the &lt;code class=&quot;highlighter-rouge&quot;&gt;Get sources&lt;/code&gt; tab in the left panel. You’ll see the following screen:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Build-Get-Sources-Step.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Build-Get-Sources-Step.png&quot; alt=&quot;Create Build pipeline, Get Sources step&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, select the repository from which your code will be fetched. Selecting &lt;code class=&quot;highlighter-rouge&quot;&gt;This project&lt;/code&gt; will
allow you to pick a Git repository from your VSTS project (that’s what I did).&lt;/p&gt;

&lt;h4 id=&quot;adding-the-tasks&quot;&gt;Adding the tasks&lt;/h4&gt;

&lt;p&gt;Next, we’ll add tasks to our build pipeline. Clicking the &lt;code class=&quot;highlighter-rouge&quot;&gt;+&lt;/code&gt; button sitting beside the &lt;code class=&quot;highlighter-rouge&quot;&gt;Phase 1&lt;/code&gt;
tab in the left panel will display the task selection screen. You can browse the available tasks,
or use the search bar. After you find the task you want to add, you just have to click its &lt;code class=&quot;highlighter-rouge&quot;&gt;Add&lt;/code&gt; 
button, and the task will appear in the left panel. You can then select the task in the left 
panel and its configuration form will show up in the right panel.&lt;/p&gt;

&lt;p&gt;Here’s the list of tasks, along with their respective properties, to add to the build pipeline.
The order must be respected. Any property not listed here should be left with its default value.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Task type&lt;/th&gt;
      &lt;th&gt;Property&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;npm&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Command&lt;/td&gt;
      &lt;td&gt;custom&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Command and arguments&lt;/td&gt;
      &lt;td&gt;install -g aurelia-cli&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;npm&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Command&lt;/td&gt;
      &lt;td&gt;install&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Command Line&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Tool&lt;/td&gt;
      &lt;td&gt;au&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Arguments&lt;/td&gt;
      &lt;td&gt;build --env prod&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Copy Files&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Source Folder&lt;/td&gt;
      &lt;td&gt;$(System.DefaultWorkingDirectory)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Contents&lt;/td&gt;
      &lt;td&gt;scripts/**/*&lt;br /&gt;favicon.ico&lt;br /&gt;index.html&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Target Folder&lt;/td&gt;
      &lt;td&gt;$(Build.ArtifactStagingDirectory)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Publish Build Artifacts&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Path to publish&lt;/td&gt;
      &lt;td&gt;$(Build.ArtifactStagingDirectory)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Artifact name&lt;/td&gt;
      &lt;td&gt;drop&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Delete Files&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Source Folder&lt;/td&gt;
      &lt;td&gt;$(Build.ArtifactStagingDirectory)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Contents&lt;/td&gt;
      &lt;td&gt;**&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Copy Files&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Source Folder&lt;/td&gt;
      &lt;td&gt;$(System.DefaultWorkingDirectory)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Contents&lt;/td&gt;
      &lt;td&gt;azure/**/*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Target Folder&lt;/td&gt;
      &lt;td&gt;$(Build.ArtifactStagingDirectory)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Publish Build Artifacts&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Path to publish&lt;/td&gt;
      &lt;td&gt;$(Build.ArtifactStagingDirectory)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Artifact name&lt;/td&gt;
      &lt;td&gt;azure&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Once you are done adding the list of tasks, click the &lt;code class=&quot;highlighter-rouge&quot;&gt;Save&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;To summarize, this pipeline will first install the Aurelia CLI, then will restore the application’s
dependencies and transpile and bundle the app. Next, it will create two artifacts: a first one named
&lt;code class=&quot;highlighter-rouge&quot;&gt;drop&lt;/code&gt;, which will contain the application’s files, and a second named &lt;code class=&quot;highlighter-rouge&quot;&gt;deploy&lt;/code&gt;, which will contain
the ARM template, the Azure Functions app, and other scripts that our release pipeline will use.&lt;/p&gt;

&lt;h3 id=&quot;the-release-pipeline&quot;&gt;The release pipeline&lt;/h3&gt;

&lt;p&gt;The release pipeline is linked to a source, which in our case will be build artifacts, and is made 
of one or more environments, which can be connected together in sequence or in parallel. Each 
environment is a sequence of tasks, just like a build pipeline.&lt;/p&gt;

&lt;h4 id=&quot;installing-vsts-extensions&quot;&gt;Installing VSTS extensions&lt;/h4&gt;

&lt;p&gt;To make our release pipeline as simple as possible, we’ll install two extensions from the 
&lt;a href=&quot;https://marketplace.visualstudio.com/search?term=app%20settings%20arm%20outputs&amp;amp;target=vsts&amp;amp;category=Build%20and%20release&amp;amp;sortBy=Relevance&quot; target=&quot;_blank&quot;&gt;VSTS marketplace&lt;/a&gt;: 
&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=keesschollaart.arm-outputs&quot; target=&quot;_blank&quot;&gt;ARM Outputs&lt;/a&gt; and 
&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=hboelman.AzureAppServiceSetAppSettings&quot; target=&quot;_blank&quot;&gt;Azure App Service: Set App settings&lt;/a&gt;. The first extension will add a release task that
retrieves the output of an ARM template deployment and exposes them as VSTS variables, making them available
to the next tasks in the sequence. The second extension will add a release task allowing to update
the App settings of an App Service in Azure. Those two extensions are free and open source.&lt;/p&gt;

&lt;p&gt;To install the extensions, just follow the two links and, for each, click &lt;code class=&quot;highlighter-rouge&quot;&gt;Install&lt;/code&gt; and follow the process.&lt;/p&gt;

&lt;h4 id=&quot;creating-the-release-definition&quot;&gt;Creating the release definition&lt;/h4&gt;

&lt;p&gt;Once this is done, go to &lt;code class=&quot;highlighter-rouge&quot;&gt;Build and Release&lt;/code&gt;, then &lt;code class=&quot;highlighter-rouge&quot;&gt;Releases&lt;/code&gt;, click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Create release definition&lt;/code&gt; and select
&lt;code class=&quot;highlighter-rouge&quot;&gt;Empty process&lt;/code&gt;. VSTS will ask you to create a first environment:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Release-Create-Environment.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Release-Create-Environment.png&quot; alt=&quot;Create Release pipeline, create environment step&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the sake of this post, let’s just create a single &lt;code class=&quot;highlighter-rouge&quot;&gt;production&lt;/code&gt; environment, so go ahead and change the
name to &lt;code class=&quot;highlighter-rouge&quot;&gt;production&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;linking-the-build-artifact&quot;&gt;Linking the build artifact&lt;/h4&gt;

&lt;p&gt;Next, let’s link our release pipeline to our build pipeline. Click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Add artifact&lt;/code&gt; and fill the fields as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Release-Add-Artifact.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Release-Add-Artifact.png&quot; alt=&quot;Create Release pipeline, add artifact step&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, you should select the proper VSTS project in the Project field, and your new build definition in the
Source field. You can leave the other fields to their default values.&lt;/p&gt;

&lt;p&gt;Next, let’s enable continuous deployment by clicking on the lightning icon on the top right corner of the new 
linked artifact and by enabling continuous deployment:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Release-Enable-Continuous-Deployment.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Deploying-an-Aurelia-app-on-Azure-using-VSTS/Create-Release-Enable-Continuous-Deployment.png&quot; alt=&quot;Create Release pipeline, enable continuous deployment&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks to this, every time a new build is completed successfully, the release pipeline will be triggered automatically.&lt;/p&gt;

&lt;h4 id=&quot;adding-variables&quot;&gt;Adding variables&lt;/h4&gt;

&lt;p&gt;Next, let’s create some variables to make our release pipeline easier to maintain. Select the &lt;code class=&quot;highlighter-rouge&quot;&gt;Variables&lt;/code&gt; tab,
then &lt;code class=&quot;highlighter-rouge&quot;&gt;Process variables&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;Add&lt;/code&gt; the following rows:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Name&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;AppService.Name&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;The unique name for your application. This will be the subdomaine of &lt;code class=&quot;highlighter-rouge&quot;&gt;azurewebsites.net&lt;/code&gt; used to access to your app. For example, I put &lt;code class=&quot;highlighter-rouge&quot;&gt;manuelguilbault-blog-post-aurelia-azure&lt;/code&gt; here, so the URL to my app is &lt;code class=&quot;highlighter-rouge&quot;&gt;http://manuelguilbault-blog-post-aurelia-azure.azurewebsites.net&lt;/code&gt;&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ResourceGroup.Name&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;The name of the resource group on which the application’s resources will be deployed. I used &lt;code class=&quot;highlighter-rouge&quot;&gt;blog-post-aurelia-azure-production&lt;/code&gt; here.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;BuildArtifact.Name&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;The value of the Source alias field when you linked the build artifact. By default, this is the name of the build pipeline to which our release pipeline is linked. Here I used &lt;code class=&quot;highlighter-rouge&quot;&gt;post-azure-aurelia&lt;/code&gt;.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Then, click &lt;code class=&quot;highlighter-rouge&quot;&gt;Save&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;creating-an-environment&quot;&gt;Creating an environment&lt;/h4&gt;

&lt;p&gt;Lastly, let’s create the task sequence for our &lt;code class=&quot;highlighter-rouge&quot;&gt;production&lt;/code&gt; environment. Go back to &lt;code class=&quot;highlighter-rouge&quot;&gt;Pipeline&lt;/code&gt; and
click on the &lt;code class=&quot;highlighter-rouge&quot;&gt;1 phase, 0 task&lt;/code&gt; link in the &lt;code class=&quot;highlighter-rouge&quot;&gt;production&lt;/code&gt; environment box. VSTS will show a screen 
that is very similar to the one we used to add tasks to our build pipeline. It works the same way.
Here’s the sequence of tasks, along with their properties, to add to the release pipeline:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Task type&lt;/th&gt;
      &lt;th&gt;Property&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Azure Resource Group Deployment&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Azure subscription&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Select your Azure subscription.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Resource group&lt;/td&gt;
      &lt;td&gt;$(ResourceGroup.Name)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Location&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Select the location where you want to deploy your app. Here I used &lt;code class=&quot;highlighter-rouge&quot;&gt;West Europe&lt;/code&gt;.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Template&lt;/td&gt;
      &lt;td&gt;$(System.DefaultWorkingDirectory)/$(BuildArtifact.Name)/deploy/azure/azuredeploy.json&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Override template parameters&lt;/td&gt;
      &lt;td&gt;-appName $(AppService.Name)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;ARM Outputs&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Azure Connection Type&lt;/td&gt;
      &lt;td&gt;Azure Resource Manager&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;AzureRM Subscription&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Select your Azure subscription.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Resource Group&lt;/td&gt;
      &lt;td&gt;$(ResourceGroup.Name)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Outputs to process&lt;/td&gt;
      &lt;td&gt;Storage.Account.Name, Storage.Container.Name&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Azure File Copy&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Source&lt;/td&gt;
      &lt;td&gt;$(System.DefaultWorkingDirectory)/$(BuildArtifact.Name)/drop/&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Azure Connection Type&lt;/td&gt;
      &lt;td&gt;Azure Resource Manager&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Azure Subscription&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Select your Azure subscription.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Destination Type&lt;/td&gt;
      &lt;td&gt;Azure Blob&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;RM Storage Account&lt;/td&gt;
      &lt;td&gt;$(Storage.Account.Name)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Container Name&lt;/td&gt;
      &lt;td&gt;$(Storage.Container.Name)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Additional Arguments&lt;/td&gt;
      &lt;td&gt;/SetContentType&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Azure PowerShell&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Azure Connection Type&lt;/td&gt;
      &lt;td&gt;Azure Resource Manager&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Azure Subscription&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Select your Azure subscription.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Script Type&lt;/td&gt;
      &lt;td&gt;Script File Path&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Script Path&lt;/td&gt;
      &lt;td&gt;$(System.DefaultWorkingDirectory)/$(BuildArtifact.Name)/deploy/azure/tools/generate-sas-token.ps1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Script Arguments&lt;/td&gt;
      &lt;td&gt;-ResourceGroup $(ResourceGroup.Name) -StorageAccount $(Storage.Account.Name) -StorageContainer $(Storage.Container.Name) -Permission r -ExportTo Storage.SasToken&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Azure App Service Deploy&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Azure Subscription&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Select your Azure subscription.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;App Service Name&lt;/td&gt;
      &lt;td&gt;$(AppService.Name)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Package or folder&lt;/td&gt;
      &lt;td&gt;$(System.DefaultWorkingDirectory)/$(BuildArtifact.Name)/deploy/azure/functions-app/&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Publish using Web Deploy&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Check it.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Remove additional files at destination&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Check it.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Azure App Service: Set App settings&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;AzureRM Subscription&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Select your Azure subscription.&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Azure App Service&lt;/td&gt;
      &lt;td&gt;$(AppService.Name)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Resource Group&lt;/td&gt;
      &lt;td&gt;$(ResourceGroup.Name)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;App Settings&lt;/td&gt;
      &lt;td&gt;Storage.SasToken=’$(Storage.SasToken)’&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Once you are done adding the list of tasks, click the &lt;code class=&quot;highlighter-rouge&quot;&gt;Save&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;To summarize, this pipeline will first provision the Azure resources using our ARM template. It will then
retrieve the template’s outputs and add them as VSTS variables, so they are available to the next tasks.
After this, it copies the content of the &lt;code class=&quot;highlighter-rouge&quot;&gt;drop&lt;/code&gt; artifact to the Blob Storage container, then generates
a long-lived SAS token so the proxy app has permission to read the Blob Storage container’s content. This
generated SAS token is assigned to yet another VSTS variable. Lastly, the proxy app is deployed on the
Azure Functions App Service and its app settings are updated with the Storage container SAS token. At this
point, the application is fully deployed and ready to be used.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You may have noticed that the ARM template deployment task overrides the &lt;code class=&quot;highlighter-rouge&quot;&gt;appName&lt;/code&gt; parameter. This
is so the app’s domain name is predetermined. However in some scenarios, such as if we add a custom
domain to the app, we could leave this parameter out and let the ARM template generate the app name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;generating-a-storage-sas-token&quot;&gt;Generating a Storage SAS token&lt;/h4&gt;

&lt;p&gt;You may have noticed that the fourth task expects a PowerShell script at &lt;code class=&quot;highlighter-rouge&quot;&gt;azure/tools/generate-sas-token.ps1&lt;/code&gt;.
This script is called with the resource group, the Storage account name, and the Storage container
name as parameters. It is expected to generate a SAS token with read permissions on the Storage
container and to assign this token to the &lt;code class=&quot;highlighter-rouge&quot;&gt;Storage.SasToken&lt;/code&gt; VSTS variable.&lt;/p&gt;

&lt;p&gt;The content of this script is pretty straightforward if you’re familiar with Azure Powershell:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;,
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$StorageAccount&lt;/span&gt;,
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$StorageContainer&lt;/span&gt;,
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$Permission&lt;/span&gt;,
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ExportTo&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Get-AzureRmStorageAccountKey -ResourceGroupName &lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt; -Name &lt;span class=&quot;nv&quot;&gt;$StorageAccount&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; New-AzureStorageContext -StorageAccountName &lt;span class=&quot;nv&quot;&gt;$StorageAccount&lt;/span&gt; -StorageAccountKey &lt;span class=&quot;nv&quot;&gt;$key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;0].Value
&lt;span class=&quot;nv&quot;&gt;$sasToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; New-AzureStorageContainerSASToken -Name &lt;span class=&quot;nv&quot;&gt;$StorageContainer&lt;/span&gt; -Permission &lt;span class=&quot;nv&quot;&gt;$Permission&lt;/span&gt; -Context &lt;span class=&quot;nv&quot;&gt;$context&lt;/span&gt; -ExpiryTime &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Get-Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;.AddYears&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;100&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;Write-Host&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;##vso[task.setvariable variable=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ExportTo&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$sasToken&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The script starts by fetching the access keys for the Storage account, then it creates a context object for the
Storage account using the primary access key and creates a new SAS token, setting the expiration 100 years in
the future. Lastly, it writes a VSTS command to the standard output so the SAS token is assigned to a VSTS
variable whose name was passed as the &lt;code class=&quot;highlighter-rouge&quot;&gt;ExportTo&lt;/code&gt; parameter.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Don’t forget to push this file to your Git repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;building--releasing&quot;&gt;Building &amp;amp; releasing&lt;/h3&gt;

&lt;p&gt;Once your code is pushed, and the build and release pipelines are created, you can go back to
&lt;code class=&quot;highlighter-rouge&quot;&gt;Build and Release&lt;/code&gt;, then &lt;code class=&quot;highlighter-rouge&quot;&gt;Builds&lt;/code&gt;. If you move your mouse over the row of the build definition we
created earlier, you’ll see a &lt;code class=&quot;highlighter-rouge&quot;&gt;...&lt;/code&gt; button appear. Click on it, then click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Queue new build&lt;/code&gt; in the
contextual menu, and confirm the dialog that will show up by clicking on &lt;code class=&quot;highlighter-rouge&quot;&gt;Queue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once the build is completed, you can go to &lt;code class=&quot;highlighter-rouge&quot;&gt;Releases&lt;/code&gt;. A new release should start automatically if
you properly enabled continuous deployment earlier. Once the release completes, you can then go to
your &lt;a href=&quot;https://portal.azure.com/&quot; target=&quot;_blank&quot;&gt;Azure portal&lt;/a&gt; to make sure everything was deployed properly.&lt;/p&gt;

&lt;p&gt;You can also fire up your favorite browser and navigate to &lt;code class=&quot;highlighter-rouge&quot;&gt;http://your_app_name.azurewebsites.net/&lt;/code&gt;
to test your app running on Azure.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;VSTS, Azure and ARM templates are amazing pieces of technology. When used together, not only is hosting
an Aurelia application on Azure pretty cheap, but building and deploying it is fairly easy. 
Additionally, the build and release pipelines we created here can be enhanced to do much more.
For example, you can add a NPM task to run unit tests in your build pipeline.
Or you can
&lt;a href=&quot;https://azure.microsoft.com/en-in/resources/templates/201-web-app-custom-domain/&quot; target=&quot;_blank&quot;&gt;add a custom domain&lt;/a&gt;
to the proxy app during release. If your app relies on the 
&lt;a href=&quot;https://github.com/Vheissu/aurelia-configuration&quot; target=&quot;_blank&quot;&gt;aurelia-configuration plugin&lt;/a&gt;,
you can &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=geeklearningio.gl-vsts-tasks-file-patch&quot; target=&quot;_blank&quot;&gt;update your config.json file&lt;/a&gt; using VSTS variables just like we do with the
App Service’s app settings. There are lots of extensions to perform various tasks and, when you really 
can’t find what you need, the Azure PowerShell API is &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/azure/overview&quot; target=&quot;_blank&quot;&gt;pretty well documented&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;

&lt;p&gt;If your Aurelia application uses the router in push state mode, you may have noticed that the current
solution doesn’t support deep linking. In my 
&lt;a href=&quot;/blog/2017/12/07/Adding-deep-linking-support-to-Azure-Functions-based-Aurelia-app/&quot;&gt;next post&lt;/a&gt;,
I address this problem.&lt;/p&gt;

&lt;p&gt;In a further post, we’ll see how to add a custom domain to our app. We’ll also see how to integrate the
&lt;a href=&quot;https://github.com/sjkp/letsencrypt-siteextension&quot; target=&quot;_blank&quot;&gt;Let’s Encrypt Azure site extension&lt;/a&gt; with our
Azure Functions app, so we can enable HTTPS by generating an SSL certificate for free using
&lt;a href=&quot;https://letsencrypt.org/&quot; target=&quot;_blank&quot;&gt;Let’s Encrypt&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 04 Dec 2017 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2017/12/04/Deploying-an-Aurelia-app-on-Azure-using-VSTS/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2017/12/04/Deploying-an-Aurelia-app-on-Azure-using-VSTS/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>Azure</category>
        
        <category>VSTS</category>
        
        <category>DevOps</category>
        
        
      </item>
    
      <item>
        <title>Hosting an Aurelia app on Azure</title>
        <description>&lt;p&gt;In this serie:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/08/22/Hosting-an-Aurelia-app-on-Azure/&quot;&gt;Hosting an Aurelia app on Azure (this post)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/12/04/Deploying-an-Aurelia-app-on-Azure-using-VSTS/&quot;&gt;Deploying an Aurelia app on Azure using VSTS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/12/07/Adding-deep-linking-support-to-Azure-Functions-based-Aurelia-app/&quot;&gt;Adding deep linking support to an Azure Functions-based Aurelia app&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Adding Let’s Encrypt to an Azure Functions-based Aurelia app (coming soon)&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;When it comes to hosting a static website on Azure, there are multiple possibilities, each with
their own advantages and limitations.&lt;/p&gt;

&lt;p&gt;One of those possibilities is to host the static files as a Web Application. However, this can be a pretty
expensive solution, as the cheapest pricing tier supporting custom domains is a little more than 8 euros per
month. Even more, if you want to support SSL, it bumps up to nearly 50 euros.&lt;/p&gt;

&lt;p&gt;In this blog post, I’m going to show how to host an Aurelia application (or any static site, actually)
on Azure for *&lt;em&gt;almost&lt;/em&gt;* nothing. We’ll host the application’s files on a Blob storage container, then use
an Azure Proxy Function, which will act as an entry point and work around some limitations of Blob storage.&lt;/p&gt;

&lt;p&gt;For sites than have less than a million visits per month, this setup will likely cost less than
a couple of euros per month. This is because Blob storage is really cheap, and Azure Functions, when using
the consumption plan, come with a million of free executions per month.&lt;/p&gt;

&lt;p&gt;You can use this &lt;a href=&quot;https://github.com/manuel-guilbault/blog-post-aurelia-azure/releases/tag/2017-08-22-Hosting-an-Aurelia-app-on-Azure&quot; target=&quot;_blank&quot;&gt;sample Aurelia application&lt;/a&gt;
if you want to follow along.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;To follow this post, you’ll need an &lt;a href=&quot;https://azure.microsoft.com/&quot; target=&quot;_blank&quot;&gt;Azure&lt;/a&gt; subscription.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;storing-files-on-a-blob-storage-container&quot;&gt;Storing files on a Blob storage container&lt;/h2&gt;

&lt;p&gt;We’ll start by creating a Blob storage container and upload a simple Aurelia application to it.&lt;/p&gt;

&lt;h3 id=&quot;creating-the-storage-account&quot;&gt;Creating the storage account&lt;/h3&gt;

&lt;p&gt;Start by navigating to your Azure portal.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Create-blob-storage-account.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Create-blob-storage-account.png&quot; alt=&quot;Create Blob storage account&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to &lt;code class=&quot;highlighter-rouge&quot;&gt;New&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Storage&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Storage account&lt;/code&gt;. A storage account creation form will show. Fill the following properties:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Name:&lt;/em&gt; the name of your Blob storage account (I used &lt;code class=&quot;highlighter-rouge&quot;&gt;manuelguilbault&lt;/code&gt; here). Must be globaly unique, as 
it will be used in the Blob storage’s domain name.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Subscription:&lt;/em&gt; select your Azure subscription.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Resource group:&lt;/em&gt; every Azure artefact must belong to a given resource group. Either select an existing resource group or create one.&lt;/li&gt;
  &lt;li&gt;All other properties can keep their default values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can next click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Azure is going to work for a little while. Once your Blob storage account has been created, navigate to it
(you can either search for it in the top search bar, or simply click on the success notification). You’ll see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Blob-storage-account-overview.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Blob-storage-account-overview.png&quot; alt=&quot;Blob storage account overview&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under &lt;code class=&quot;highlighter-rouge&quot;&gt;Services&lt;/code&gt;, click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Blobs&lt;/code&gt;, then on the button to add a &lt;code class=&quot;highlighter-rouge&quot;&gt;Container&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Add-blob-storage-container.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Add-blob-storage-container.png&quot; alt=&quot;Add blob storage container&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give the container the name you want (I named mine &lt;code class=&quot;highlighter-rouge&quot;&gt;blog-post-aurelia-azure&lt;/code&gt;) and select &lt;code class=&quot;highlighter-rouge&quot;&gt;Blob&lt;/code&gt; as the access type, 
so the data stored in the container is publicly visible on the web, then click &lt;code class=&quot;highlighter-rouge&quot;&gt;OK&lt;/code&gt;. Your new Blob container
will be created.&lt;/p&gt;

&lt;h3 id=&quot;uploading-the-app&quot;&gt;Uploading the app&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;For the next steps, you’ll need the &lt;a href=&quot;http://storageexplorer.com/&quot; target=&quot;_blank&quot;&gt;Azure Storage Explorer&lt;/a&gt; installed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Scroll left so you see your storage account, and click on the &lt;code class=&quot;highlighter-rouge&quot;&gt;Open in Explorer&lt;/code&gt; button. This will launch the Azure
Storage Explorer. Follow the instructions and sign in, so you see your subscription(s) in the explorer’s left panel.&lt;/p&gt;

&lt;p&gt;In this left panel, expand your subscription, then your storage account and its &lt;code class=&quot;highlighter-rouge&quot;&gt;Blob Containers&lt;/code&gt; and select your new container. Then, in the container’s tab in the center panel, use the &lt;code class=&quot;highlighter-rouge&quot;&gt;Upload&lt;/code&gt; button to upload the files and folders of your Aurelia application.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Open-storage-in-Azure-explorer.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Open-storage-in-Azure-explorer.png&quot; alt=&quot;Open the blob container in Azure explorer&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you upload the sample app provided at the beginning of this post, first make sure you &lt;code class=&quot;highlighter-rouge&quot;&gt;au build&lt;/code&gt; it before hand
(see its &lt;code class=&quot;highlighter-rouge&quot;&gt;README.md&lt;/code&gt; file), then upload the &lt;code class=&quot;highlighter-rouge&quot;&gt;scripts&lt;/code&gt; folder, and the &lt;code class=&quot;highlighter-rouge&quot;&gt;favicon.ico&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; files.&lt;/p&gt;

&lt;h3 id=&quot;accessing-the-files&quot;&gt;Accessing the files&lt;/h3&gt;

&lt;p&gt;Once the app is uploaded to Azure, let’s try to access the files from a browser. Go back to your storage 
account on the Azure portal and copy it’s &lt;code class=&quot;highlighter-rouge&quot;&gt;Primary blob service endpoint&lt;/code&gt;, which is a URL following this pattern:
&lt;code class=&quot;highlighter-rouge&quot;&gt;https://&amp;lt;your_storage_account_name&amp;gt;.blob.core.windows.net/&lt;/code&gt;. In my case, the URL is
&lt;code class=&quot;highlighter-rouge&quot;&gt;https://manuelguilbault.blob.core.windows.net/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To this URL, append the name of the Blob container, followed by &lt;code class=&quot;highlighter-rouge&quot;&gt;/index.html&lt;/code&gt; (or the name of your application’s index file). In my case, the final URL looks like this: &lt;code class=&quot;highlighter-rouge&quot;&gt;https://manuelguilbault.blob.core.windows.net/blog-post-aurelia-azure/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you access this URL in a browser, the &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; page should load (check your browser’s developer tools to make sure).
Now, if you used the sample Aurelia application provided at the beginning of this post, or if your own Aurelia app uses the 
router with push state, your app shouldn’t work when you navigate to this URL. This is because Aurelia expects that the
application is loaded using a default document (without the &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; part), so the router can correctly match the
&lt;code class=&quot;highlighter-rouge&quot;&gt;/&lt;/code&gt; path to its &lt;code class=&quot;highlighter-rouge&quot;&gt;home&lt;/code&gt; route.&lt;/p&gt;

&lt;h2 id=&quot;using-azure-proxy-functions&quot;&gt;Using Azure Proxy Functions&lt;/h2&gt;

&lt;p&gt;Accessing an Aurelia application directly from the Blob storage URL has some severe limitations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Blob storage doesn’t support default documents. This means that accessing a directory path (in my case, 
&lt;code class=&quot;highlighter-rouge&quot;&gt;https://manuelguilbault.blob.core.windows.net/blog-post-aurelia-azure/&lt;/code&gt;) doesn’t serve the &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; file in
the directory, but will return a 404 Not Found response.&lt;/li&gt;
  &lt;li&gt;If your Aurelia application uses the router in push state mode, your app won’t work because the router expects
the app to load using the default document (&lt;code class=&quot;highlighter-rouge&quot;&gt;/&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To solve these issues, we’ll create an Azure Function app on top of the Blob storage container, that will act
as a proxy for our application.&lt;/p&gt;

&lt;h3 id=&quot;creating-an-azure-function-application&quot;&gt;Creating an Azure Function Application&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Go-to-new-proxy-function.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Go-to-new-proxy-function.png&quot; alt=&quot;Go to New Proxy Function&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Azure dashboard, click on &lt;code class=&quot;highlighter-rouge&quot;&gt;New&lt;/code&gt;, search for &lt;code class=&quot;highlighter-rouge&quot;&gt;function&lt;/code&gt;, click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Function App&lt;/code&gt;, then on &lt;code class=&quot;highlighter-rouge&quot;&gt;Create&lt;/code&gt;. Next, 
fill the form:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;App name:&lt;/em&gt; enter the name of your Function app (I named mine &lt;code class=&quot;highlighter-rouge&quot;&gt;manuelguilbault-blog-post-aurelia-azure&lt;/code&gt;). Must be 
globaly unique, as it will be used in the app’s domain name.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Subscription:&lt;/em&gt; select your Azure subscription.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Resource Group:&lt;/em&gt; every Azure artefact must belong to a given resource group. Either select an existing resource group 
or create one.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Hosting Plan:&lt;/em&gt; select &lt;code class=&quot;highlighter-rouge&quot;&gt;Consumption Plan&lt;/code&gt; to get 1 million free executions per month (at the moment of writing).&lt;/li&gt;
  &lt;li&gt;All other properties can keep their default values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Create-function-app.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Create-function-app.png&quot; alt=&quot;Fill the form&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can next click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;enabling-proxy-functions&quot;&gt;Enabling Proxy Functions&lt;/h3&gt;

&lt;p&gt;At the moment of writing, the Azure Proxy Functions feature is in preview. As such, it must be enabled before it can
be used.&lt;/p&gt;

&lt;p&gt;To do so, navigate to your new Function App in the Azure portal. In the &lt;code class=&quot;highlighter-rouge&quot;&gt;Overview&lt;/code&gt; tab, under &lt;code class=&quot;highlighter-rouge&quot;&gt;Configured features&lt;/code&gt;,
click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Function app settings&lt;/code&gt;. This will open a new tab titled &lt;code class=&quot;highlighter-rouge&quot;&gt;Function app settings&lt;/code&gt;, under which you can enable
&lt;code class=&quot;highlighter-rouge&quot;&gt;Proxies&lt;/code&gt; by toggling the feature to &lt;code class=&quot;highlighter-rouge&quot;&gt;On&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Enable-proxy-functions.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Enable-proxy-functions.png&quot; alt=&quot;Function app settings&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;creating-an-azure-proxy-function&quot;&gt;Creating an Azure Proxy Function&lt;/h3&gt;

&lt;p&gt;Navigate to your Function App in the Azure portal, and click on the plus button beside the &lt;code class=&quot;highlighter-rouge&quot;&gt;Proxies&lt;/code&gt; list item.
This will display a creation form, which you can fill:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Name:&lt;/em&gt; the name of the proxy function. I named mine &lt;code class=&quot;highlighter-rouge&quot;&gt;default&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Route template:&lt;/em&gt; the template of the URL path that must be matched for this proxy function to be triggered. 
Here, enter &lt;code class=&quot;highlighter-rouge&quot;&gt;/{*path}&lt;/code&gt;. This pattern will match any path and make it available as a &lt;code class=&quot;highlighter-rouge&quot;&gt;path&lt;/code&gt; variable to the backend URL.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Backend URL:&lt;/em&gt; the URL to which matching requests will be dispatched. Here, enter the URL of your Blob storage 
container, followed by the &lt;code class=&quot;highlighter-rouge&quot;&gt;{path}&lt;/code&gt; variable output. For me, this was &lt;code class=&quot;highlighter-rouge&quot;&gt;https://manuelguilbault.blob.core.windows.net/blog-post-aurelia-azure/{path}&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;All other properties can keep their default values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Create-default-proxy-function.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Create-default-proxy-function.png&quot; alt=&quot;Proxy Function creation form&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can next click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This function will dispatch all requests sent to &lt;code class=&quot;highlighter-rouge&quot;&gt;https://manuelguilbault-blog-post-aurelia-azure.azurewebsites.net/&lt;/code&gt;
to my Blob storage container. However, in order to support default documents, so the &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; file is loaded when
accessing the &lt;code class=&quot;highlighter-rouge&quot;&gt;/&lt;/code&gt; path, we need to create a second proxy function with the following properties:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Name:&lt;/em&gt; the name of the proxy function. I named this one &lt;code class=&quot;highlighter-rouge&quot;&gt;root&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Route template:&lt;/em&gt; this proxy function should match only requests sent to the root, so enter &lt;code class=&quot;highlighter-rouge&quot;&gt;/&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Backend URL:&lt;/em&gt; requests sent to the root should return the &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; file, so enter URL of your Blob storage 
container, followed by &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt;. For me, this was
&lt;code class=&quot;highlighter-rouge&quot;&gt;https://manuelguilbault.blob.core.windows.net/blog-post-aurelia-azure/index.html&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Create-root-proxy-function.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Create-root-proxy-function.png&quot; alt=&quot;Proxy Function creation form&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;testing-the-application&quot;&gt;Testing the application&lt;/h3&gt;

&lt;p&gt;We now have two proxy functions. The first one, named &lt;code class=&quot;highlighter-rouge&quot;&gt;root&lt;/code&gt;, will be used to load the &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; when accessing
the root of our function app’s domain, and a second one named &lt;code class=&quot;highlighter-rouge&quot;&gt;default&lt;/code&gt; which will
simply forward any other requests to the Blob storage container. The first one will be used to load our Aurelia 
application itself, and the second one will kick in when the &lt;code class=&quot;highlighter-rouge&quot;&gt;index.html&lt;/code&gt; file will load our application’s bundles, 
CSS files or images.&lt;/p&gt;

&lt;p&gt;To test the application, simply launch a browser tab and navigate to your function app’s URL
(in my case, &lt;code class=&quot;highlighter-rouge&quot;&gt;https://manuelguilbault-blog-post-aurelia-azure.azurewebsites.net/&lt;/code&gt;). The Aurelia app (or your
static site) should load properly now.&lt;/p&gt;

&lt;h3 id=&quot;making-the-blob-storage-container-private&quot;&gt;Making the Blob storage container private&lt;/h3&gt;

&lt;p&gt;Since the Blob storage container is no more accessed directly, but only through a Proxy Function, you can
make it private to make sure no one can access it directly from the web.&lt;/p&gt;

&lt;p&gt;To do this, go back to your Blob storage container in your Azure portal, then click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Access policy&lt;/code&gt;. You should see
this form come up:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Change-blob-storage-container-access-type.png&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/posts/Hosting-an-Aurelia-app-on-Azure/Change-blob-storage-container-access-type.png&quot; alt=&quot;Changing Blob storage container access type&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, simply change the &lt;code class=&quot;highlighter-rouge&quot;&gt;Public access level&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;Private&lt;/code&gt; and click &lt;code class=&quot;highlighter-rouge&quot;&gt;Save&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;wrapping-things-up&quot;&gt;Wrapping things up&lt;/h2&gt;

&lt;p&gt;With two very simple proxy functions and a Blob storage container, we now have an Aurelia app (or a static site)
hosted on Azure, for a very cheap price. Additionally, since our application is accessed through a function app, 
we can easily add a custom domain and an SSL certificate to our app.&lt;/p&gt;

&lt;p&gt;In my &lt;a href=&quot;/blog/2017/12/04/Deploying-an-Aurelia-app-on-Azure-using-VSTS/&quot;&gt;next post&lt;/a&gt;, I show how to use 
&lt;a href=&quot;https://www.visualstudio.com/&quot; target=&quot;_blank&quot;&gt;Visual Studio Team Services&lt;/a&gt;
to automate the build and deployment of an Aurelia application to a Blob storage container. Stay tuned!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Thanks to &lt;a href=&quot;https://twitter.com/natmarchand&quot; target=&quot;_blank&quot;&gt;@NatMarchand&lt;/a&gt; for the idea!&lt;/p&gt;
&lt;/blockquote&gt;
</description>
        <pubDate>Tue, 22 Aug 2017 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2017/08/22/Hosting-an-Aurelia-app-on-Azure/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2017/08/22/Hosting-an-Aurelia-app-on-Azure/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>Azure</category>
        
        <category>Aurelia</category>
        
        <category>Web</category>
        
        
      </item>
    
      <item>
        <title>Multiple Aurelia apps in a single ASP.NET Core application</title>
        <description>&lt;p&gt;I’ve been recently working on an ASP.NET Core MVC application for a tour operator company. The application follows the 
standard MVC navigation principles:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You search for flights between two cities, for a specific time period;&lt;/li&gt;
  &lt;li&gt;You see a grid with the flights matching the criteria, displaying all sorts of yielding information (sold seats, capacity, pricing parameters, etc.);&lt;/li&gt;
  &lt;li&gt;You click on a single flight to go to its edition page;&lt;/li&gt;
  &lt;li&gt;You edit the flight’s capacity and pricing parameters, save, and go back to the result grid.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The customers asked if we could redesign this workflow so they could edit the pricing parameters directly in the grid. To support this demand, the grid 
being a simple &lt;code class=&quot;highlighter-rouge&quot;&gt;table&lt;/code&gt; element rendered using a Razor template, we decided to transform it into a small Aurelia application which would render the grid 
and handle inline edition.&lt;/p&gt;

&lt;p&gt;As the customer also mentionned they would like to have some other workflows redesigned as such in the future, we figured
we would end up with multiple Aurelia apps in our .NET project. As such, we wanted modified the default setup of an ASP .NET Core Aurelia app
to support multiple client apps.&lt;/p&gt;

&lt;p&gt;We needed to fiddle a bit here and there, but we ended up being able to do it pretty easily. Here’s a recap of how we did it.&lt;/p&gt;

&lt;h2 id=&quot;setting-up-the-project--the-environment&quot;&gt;Setting up the project &amp;amp; the environment&lt;/h2&gt;

&lt;p&gt;To follow this example, you’ll need to create a .NET Core Aurelia web app:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Install the &lt;a href=&quot;https://www.microsoft.com/net/core&quot;&gt;.NET Core CLI&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Install the ASP.NET Spa Templates: &lt;code class=&quot;highlighter-rouge&quot;&gt;dotnet new --install &quot;Microsoft.AspNetCore.SpaTemplates::*&quot;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Create a new folder for your project: &lt;code class=&quot;highlighter-rouge&quot;&gt;mkdir dotnet-core-multiple-aurelia-apps&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Move into it: &lt;code class=&quot;highlighter-rouge&quot;&gt;cd dotnet-core-multiple-aurelia-apps&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Create your ASP.NET Core Aurelia project: &lt;code class=&quot;highlighter-rouge&quot;&gt;dotnet new aurelia&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;Of course, since Aurelia is involved, you’ll also need to have &lt;a href=&quot;https://nodejs.org/&quot;&gt;NodeJS&lt;/a&gt; installed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;transforming-a-single-aurelia-app-project-into-a-multiple-one&quot;&gt;Transforming a single Aurelia app project into a multiple one&lt;/h2&gt;

&lt;p&gt;Once you got your ASP.NET Core project ready, you can start transforming it into a multiple Aurelia apps project.
Quickly:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;rename the &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApp&lt;/code&gt; directory to &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApps&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;create &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApps/app1&lt;/code&gt;, where &lt;code class=&quot;highlighter-rouge&quot;&gt;app1&lt;/code&gt; is the name of your first Aurelia app;&lt;/li&gt;
  &lt;li&gt;move everything from &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApps&lt;/code&gt; inside &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApps/app1&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, in &lt;code class=&quot;highlighter-rouge&quot;&gt;webpack.config.js&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;replace every occurences of &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApp&lt;/code&gt; with &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApps&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;change the file so it returns one configuration object per Aurelia app instead of a single one;&lt;/li&gt;
  &lt;li&gt;for each app’s configuration object:
    &lt;ul&gt;
      &lt;li&gt;change the &lt;code class=&quot;highlighter-rouge&quot;&gt;entry&lt;/code&gt; point’s name from &lt;code class=&quot;highlighter-rouge&quot;&gt;'app'&lt;/code&gt; to the app’s name;&lt;/li&gt;
      &lt;li&gt;change how it &lt;code class=&quot;highlighter-rouge&quot;&gt;resolve&lt;/code&gt;s its modules, not from &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApps&lt;/code&gt; anymore, but from the app directory inside &lt;code class=&quot;highlighter-rouge&quot;&gt;ClientApps&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The updated &lt;code class=&quot;highlighter-rouge&quot;&gt;webpack.config.js&lt;/code&gt; should look like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'path'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webpack&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'webpack'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AureliaPlugin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'aurelia-webpack-plugin'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bundleOutputDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'./wwwroot/dist'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;isDevBuild&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;'app1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Add any other Aurelia app here&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'aurelia-bootstrapper'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'.ts'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'.js'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`ClientApps/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'node_modules'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bundleOutputDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;publicPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/dist/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'[name].js'&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;ts$/i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/ClientApps/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'ts-loader?silent=true'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;html$/i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'html-loader'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;css$/i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;isDevBuild&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'css-loader'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'css-loader?minimize'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;png|jpg|jpeg|gif|svg&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'url-loader?limit=25000'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webpack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DefinePlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;IS_DEV_BUILD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isDevBuild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webpack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DllReferencePlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;manifest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'./wwwroot/dist/vendor-manifest.json'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AureliaPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aureliaApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'boot'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isDevBuild&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webpack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SourceMapDevToolPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'[file].map'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Remove this line if you prefer inline source maps&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;moduleFilenameTemplate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bundleOutputDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'[resourcePath]'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// Point sourcemap entries to the original file locations on disk&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webpack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;optimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UglifyJsPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;This configuration will now generate one bundle for each application. For exemple, the &lt;code class=&quot;highlighter-rouge&quot;&gt;app1&lt;/code&gt; application will be bundled 
  in the &lt;code class=&quot;highlighter-rouge&quot;&gt;wwwroot/dist/app1.js&lt;/code&gt; file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lastly, load the proper bundles and bootstrap your Aurelia app in the proper Razor view:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aurelia-app=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;boot&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

@section scripts {
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;~/dist/vendor.js&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;asp-append-version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;~/dist/app1.js&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;asp-append-version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Before you run the application, make sure to generate the &lt;code class=&quot;highlighter-rouge&quot;&gt;vendor&lt;/code&gt; bundle by running &lt;code class=&quot;highlighter-rouge&quot;&gt;webpack --config webpack.config.vendor.js&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check this &lt;a href=&quot;https://github.com/manuel-guilbault/blog-post-aspnet-core-multiple-aurelia-apps&quot;&gt;GitHub repository&lt;/a&gt; for a code sample.&lt;/p&gt;
</description>
        <pubDate>Thu, 29 Jun 2017 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2017/06/29/Multiple-Aurelia-apps-in-single-dotnet-core-project/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2017/06/29/Multiple-Aurelia-apps-in-single-dotnet-core-project/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>Aurelia</category>
        
        <category>JS</category>
        
        <category>Web</category>
        
        <category>.NET</category>
        
        
      </item>
    
      <item>
        <title>Aurelia - feedback from the trenches</title>
        <description>&lt;p&gt;I’ve been using &lt;a href=&quot;http://aurelia.io/&quot; target=&quot;_blank&quot;&gt;Aurelia&lt;/a&gt; to write multiple applications of 
various size for more than a year now, and I thought it might be interesting for some people to get 
some real-life feedback.&lt;/p&gt;

&lt;p&gt;All in all, the framework is awesome. Aurelia’s developer experience is much better than that of
other frameworks I’ve worked with (*&lt;em&gt;cough&lt;/em&gt;* Angular *&lt;em&gt;cough&lt;/em&gt;*). It’s flexible, 
extensible, and very little intrusive. You don’t have to fight it to do things the right way, whatever 
that right way might be.&lt;/p&gt;

&lt;p&gt;However, entropy is constantly at work, and things can quickly become messy if you don’t pay attention.
Here are a few things I’ve learnt along the way.&lt;/p&gt;

&lt;h2 id=&quot;type-everything&quot;&gt;Type everything&lt;/h2&gt;

&lt;p&gt;If you don’t use &lt;a href=&quot;https://www.typescriptlang.org/&quot; target=&quot;_blank&quot;&gt;TypeScript&lt;/a&gt; yet, you really should.
Its type system adds tremedous value when compared to plain JS.&lt;/p&gt;

&lt;h3 id=&quot;why&quot;&gt;Why?&lt;/h3&gt;

&lt;p&gt;First, it serves as API documentation for the developers working on your code base. Let’s imagine the 
following JS code snippet:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getFullName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check the &lt;code class=&quot;highlighter-rouge&quot;&gt;getFullName&lt;/code&gt; function. What is the expected type of this &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; parameter? Is it an object or 
something else? What are its properties and methods? Here, we can probably guess that it is a &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; 
instance. However, if the class and the function live in distinct files or directories, good luck with that.&lt;/p&gt;

&lt;p&gt;Exploring such a JS code base can be a nightmare, as you would need to search for calls to &lt;code class=&quot;highlighter-rouge&quot;&gt;getFullName&lt;/code&gt; 
to try to figure out what parameters are passed to it. The type system makes understanding existing code
much easier:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getFullName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is now pretty clear that the &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; argument is an instance of the &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; class. Additionally, most 
modern IDEs will let you to navigate to the definition of &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; in a single click.&lt;/p&gt;

&lt;p&gt;The type system also prevents a whole class of bugs, where some part of the code uses something -
a variable, method, function, or parameter - the wrong way - call an undefined method, or assign a value
to an unexisting property, for example. In plain JS, such bugs must be covered by unit tests. In TypeScript,
the transpiler will complain about such a misuse if things are properly typed. The type system can completely 
replace a whole category of unit tests.&lt;/p&gt;

&lt;p&gt;Let’s imagine the following code:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getFullName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code has a bug. The &lt;code class=&quot;highlighter-rouge&quot;&gt;getFullName&lt;/code&gt; function contains typos: it uses the &lt;code class=&quot;highlighter-rouge&quot;&gt;firstname&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;lastname&lt;/code&gt;
properties, while the &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; class defines those properties using a capital &lt;code class=&quot;highlighter-rouge&quot;&gt;N&lt;/code&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;firstName&lt;/code&gt; and
&lt;code class=&quot;highlighter-rouge&quot;&gt;lastName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here, the error is easy to spot, as the class definition and the function sit side by side. However,
if they live in distinct files, such a bug can be hard to spot without either a unit test or the 
TypeScript transpiler. Relying on the type system for such things is much simpler and quicker than 
writing unit tests.&lt;/p&gt;

&lt;h3 id=&quot;everything-really&quot;&gt;Everything, really?&lt;/h3&gt;

&lt;p&gt;Yes. Everything. For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create an interface for the route parameters passed to the &lt;code class=&quot;highlighter-rouge&quot;&gt;activate&lt;/code&gt; method of route components,&lt;/li&gt;
  &lt;li&gt;Specify the type of the &lt;code class=&quot;highlighter-rouge&quot;&gt;@bindable&lt;/code&gt; properties of custom elements or attributes,&lt;/li&gt;
  &lt;li&gt;Use typed events when working with the &lt;code class=&quot;highlighter-rouge&quot;&gt;EventAggregator&lt;/code&gt;,&lt;/li&gt;
  &lt;li&gt;Use classes or interfaces for objects sent or retrieved using the &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt; client,&lt;/li&gt;
  &lt;li&gt;Didn’t I say everything?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By typing everything, you eliminate a whole category of bugs. Additionally, you add documentation
for other developers and your future self. Lastly, you give more informations to your tools
(auto-completers, linters, bundlers, etc.) so they can do a better job assisting you.&lt;/p&gt;

&lt;p&gt;Those advantages largely outweight the additional cost of properly typing things, trust me.
Or don’t, and try it for yourself. You’ll see.&lt;/p&gt;

&lt;h2 id=&quot;small-apps-over-big-apps&quot;&gt;Small apps over big apps&lt;/h2&gt;

&lt;p&gt;Whenever possible, it’s better to write multiple small applications over writing one huge application.
A small code base is more manageable than a large one, and it’s easier to have multiple small teams 
maintaining multiple small applications than one huge team working on a single huge code base.
Small applications can be released independently and at different paces, while releasing huge apps
require much more planification and coordination between team members, and such a process is most of
the time rigid and not agile at all.&lt;/p&gt;

&lt;p&gt;I didn’t invent anything here; microservices have been around long enough now, and they are based on the
same principles.&lt;/p&gt;

&lt;h2 id=&quot;bundle-intelligently&quot;&gt;Bundle intelligently&lt;/h2&gt;

&lt;p&gt;If you can’t keep your application small as suggested before, at least use bundles intelligently so your 
application loads faster.&lt;/p&gt;

&lt;p&gt;For example, if your application’s users are segregated in roles, you can group features 
in role-based bundles. This way, a given user would load only the bundle(s) he needs to do his job
based on its role(s).&lt;/p&gt;

&lt;p&gt;If you won’t reap the benefits of small code bases - faster and independent release cycles, smaller
teams, reduced code complexity - at least you won’t hurt too much your users’ experience by forcing them
to load a 10 MB bundle when all they need really is 10% of it.&lt;/p&gt;

&lt;h2 id=&quot;organize-by-features&quot;&gt;Organize by features&lt;/h2&gt;

&lt;p&gt;Aurelia &lt;a href=&quot;http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/app-configuration-and-startup/6&quot; target=&quot;_blank&quot;&gt;features&lt;/a&gt;
are extremely useful to organize your code.&lt;/p&gt;

&lt;p&gt;For example, I often have a &lt;code class=&quot;highlighter-rouge&quot;&gt;validation&lt;/code&gt; feature in my Aurelia applications. This feature handles
some simple tasks related to form validation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It loads the 
&lt;a href=&quot;http://aurelia.io/hub.html#/doc/article/aurelia/validation/latest/validation-basics&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;aurelia-validation&lt;/code&gt;&lt;/a&gt;
plugin,&lt;/li&gt;
  &lt;li&gt;It registers at least one 
&lt;a href=&quot;http://aurelia.io/hub.html#/doc/article/aurelia/validation/latest/validation-basics/7&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ValidationRenderer&lt;/code&gt;&lt;/a&gt;
in the DI container,&lt;/li&gt;
  &lt;li&gt;It registers any
&lt;a href=&quot;http://aurelia.io/hub.html#/doc/article/aurelia/validation/latest/validation-basics/10&quot; target=&quot;_blank&quot;&gt;custom validation rules&lt;/a&gt;
needed across the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Features can also be used to group components, models, and services related to specific domains.&lt;/p&gt;

&lt;p&gt;A chapter of &lt;a href=&quot;https://www.packtpub.com/web-development/learning-aurelia&quot; target=&quot;_blank&quot;&gt;my book&lt;/a&gt; is 
dedicated to this subject.&lt;/p&gt;

&lt;h2 id=&quot;decompose&quot;&gt;Decompose&lt;/h2&gt;

&lt;p&gt;A route component fetching data from an HTTP endpoint and rendering a complex view all by itself will
likely be hard to maintain, as it has too many responsibilities. It also makes it hard - or even impossible -
to reuse parts of its behavior or UI elements.&lt;/p&gt;

&lt;p&gt;Components should be &lt;strong&gt;loosely coupled&lt;/strong&gt; and &lt;strong&gt;highly cohesive&lt;/strong&gt;. They should be small and solve a 
well-defined problem. Larger components should be composed of smaller ones, and their task should be
to configure and coordinate interactions between their constituents.&lt;/p&gt;

&lt;p&gt;A lot of Aurelia’s features can be of great help in decomposing components and making them flexible
enough to be used in larger components: one-way and two-way data-binding, event aggregation,
content projection, and template injection, are the cornerstones of well-taylored and highly
configurable components. Make sure you master each of those techniques well enough, so you can make
enlightened design decisions and use Aurelia to its full potential.&lt;/p&gt;

&lt;h2 id=&quot;separate-ui--domain-concerns&quot;&gt;Separate UI &amp;amp; domain concerns&lt;/h2&gt;

&lt;p&gt;Similarly, keeping UI and domain artifacts separated in your code base will make it easier
to maintain.&lt;/p&gt;

&lt;p&gt;By &lt;strong&gt;UI artifact&lt;/strong&gt;, I mean:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Components,&lt;/li&gt;
  &lt;li&gt;Custom elements &amp;amp; attributes,&lt;/li&gt;
  &lt;li&gt;Value converters,&lt;/li&gt;
  &lt;li&gt;Binding behaviors,&lt;/li&gt;
  &lt;li&gt;Templates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By &lt;strong&gt;Domain artifact&lt;/strong&gt;, I mean:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Domain models,&lt;/li&gt;
  &lt;li&gt;Domain services,&lt;/li&gt;
  &lt;li&gt;Back-end clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a route component should be built by composing UI and domain artifacts together.
If it fetches its own data from an HTTP endpoint, defines its own view model for this data and has a complex
template to present it to the user, it will likely be hard to maintain and its constituents
impossible to reuse. Additionally, domain intelligence will be lost in UI concerns.&lt;/p&gt;

&lt;p&gt;However, if you design distinct domain models and services, isolated from UI components, then
aggregate them all in a route component, this route component will be smaller because it will simply coordinate 
interactions between its domain and UI constituents. Plus, your code will likely be saner and easier to 
understand.&lt;/p&gt;

&lt;p&gt;Again, it didn’t invent anything here. This idea comes from both
&lt;a href=&quot;http://dddcommunity.org/&quot; target=&quot;_blank&quot;&gt;Domain-Driven Design&lt;/a&gt;
and &lt;a href=&quot;http://alistair.cockburn.us/Hexagonal+architecture&quot; target=&quot;_blank&quot;&gt;Hexagonal architecture&lt;/a&gt;,
and has been around for years. Even though people apply this to back-end design most of the
time, front-end application design can also greatly benefit from those principles.&lt;/p&gt;

&lt;h2 id=&quot;have-fun&quot;&gt;Have fun!&lt;/h2&gt;

&lt;p&gt;I had a lot of fun in the last year working with Aurelia. I hope you do too!&lt;/p&gt;

&lt;p&gt;Don’t hesitate to let me know about your own experience with Aurelia. Just leave a comment! ☺&lt;/p&gt;
</description>
        <pubDate>Thu, 08 Jun 2017 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2017/06/08/Aurelia-feedback-from-the-trenches/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2017/06/08/Aurelia-feedback-from-the-trenches/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>Aurelia</category>
        
        <category>JS</category>
        
        <category>Web</category>
        
        
      </item>
    
      <item>
        <title>What is Aurelia's .two-way command for?</title>
        <description>&lt;p&gt;One of &lt;a href=&quot;https://www.packtpub.com/web-development/learning-aurelia&quot; target=&quot;_blank&quot;&gt;my book&lt;/a&gt;’s reader sent me an email 
earlier this week. He was confused by a statement from the book and thought it might contain a typo or some kind of error. 
This statement said that the &lt;code class=&quot;highlighter-rouge&quot;&gt;.two-way&lt;/code&gt; binding command didn’t adapt to its context, while &lt;code class=&quot;highlighter-rouge&quot;&gt;.bind&lt;/code&gt; did.&lt;/p&gt;

&lt;p&gt;I answered that it was no error, that &lt;code class=&quot;highlighter-rouge&quot;&gt;.bind&lt;/code&gt; uses two-way binding when the target property supports it and falls
back to one-way binding when it doesn’t, while &lt;code class=&quot;highlighter-rouge&quot;&gt;.two-way&lt;/code&gt; forces two-way binding. Although it may not be as clear as 
I intended, that’s what I meant when I wrote that it didn’t adapt to its context.&lt;/p&gt;

&lt;p&gt;That question made me realize that at first glance, &lt;code class=&quot;highlighter-rouge&quot;&gt;.two-way&lt;/code&gt; seems pretty useless. Why would someone want to force
two-way binding on a property that doesn’t support it? It makes no sense.&lt;/p&gt;

&lt;p&gt;I gave it some thought, and found a use case where it does actually make sense.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;.bind&lt;/code&gt; command uses the target property’s &lt;em&gt;default binding mode&lt;/em&gt;. Aurelia considers that the default binding mode
for all native HTML elements’ attributes and properties is one-way, except for properties affected by user inputs 
on form-related elements, such as an &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s or a  &lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;value&lt;/code&gt;, whose default binding mode is two-way.&lt;/p&gt;

&lt;p&gt;However, when creating custom elements and attributes, you have complete control over the default binding mode of
your component’s properties. One could easily imagine a custom attribute - or a custom element’s property - whose 
default binding mode is one-way, but which supports two-way binding by being somehow related to user input. In such a 
case, using the &lt;code class=&quot;highlighter-rouge&quot;&gt;.two-way&lt;/code&gt; command would be the only way to force two-way binding for this property, since the &lt;code class=&quot;highlighter-rouge&quot;&gt;.bind&lt;/code&gt; 
command would use one-way binding by default.&lt;/p&gt;

&lt;p&gt;Such a scenario doesn’t seem very likely to me, but Aurelia nevertheless supports it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to Christian for his question.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 11 Feb 2017 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2017/02/11/What-is-Aurelias-two-way-binding-command-for/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2017/02/11/What-is-Aurelias-two-way-binding-command-for/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>Aurelia</category>
        
        <category>JS</category>
        
        <category>TypeScript</category>
        
        <category>Web</category>
        
        
      </item>
    
      <item>
        <title>Building an image files picker with Aurelia</title>
        <description>&lt;p&gt;&lt;em&gt;This post was initially published on 
&lt;a href=&quot;http://blog.aurelia.io/2017/02/01/announcing-learning-aurelia-from-packt-publishing/&quot; target=&quot;_blank&quot;&gt;Aurelia’s official blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my book &lt;a href=&quot;https://www.packtpub.com/web-development/learning-aurelia&quot; target=&quot;_blank&quot;&gt;Learning Aurelia&lt;/a&gt;,
you can see, among other things, how to build an image file picker component, supporting 
drag and drop and showing a preview of the selected image.&lt;/p&gt;

&lt;p&gt;In this post, we’ll use the techniques described in the book to build a multi-select
image file picker, also supporting drag and drop, with a gallery-style preview feature.&lt;/p&gt;

&lt;h2 id=&quot;picking-files&quot;&gt;Picking files&lt;/h2&gt;

&lt;p&gt;Let’s start by creating a custom &lt;code class=&quot;highlighter-rouge&quot;&gt;file-picker&lt;/code&gt; element, which will encapsulate 
an &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt; element:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;resources/elements/file-picker.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;customElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;useView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'aurelia-framework'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;customElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'file-picker'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;useView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'./file-picker.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FilePicker&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;bindable&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;accept&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;bindable&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;multiple&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;bindable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;defaultBindingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;twoWay&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FileList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nl&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;HTMLInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;filesChanged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;clearSelection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;clearSelection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'file'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This view-model declares three bindable properties:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;accept&lt;/code&gt;: will be bound to the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;accept&lt;/code&gt; attribute, which is used
to limit the type of files the browser’s dialog will show to the user.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;multiple&lt;/code&gt;: will be bound to the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;multiple&lt;/code&gt; attribute, which
tells the browser’s dialog if it should support selection of multiple files 
or not.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt;: will be bound to the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; attribute. This property is
bound two way by default, so the file(s) selected by the user are assigned
back to the bound property.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The view-model also declares an &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt; property, to which the template will
assign a reference on the &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;Lastly, since the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; property is read-only and the DOM API
doesn’t expose a method to clear the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s file selection (other than
calling the &lt;code class=&quot;highlighter-rouge&quot;&gt;reset&lt;/code&gt; method on the whole surrounding &lt;code class=&quot;highlighter-rouge&quot;&gt;form&lt;/code&gt;), the view-model 
uses a hack to clear the selected files when an empty value is assigned
to the &lt;code class=&quot;highlighter-rouge&quot;&gt;file-picker&lt;/code&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; property: it sets the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;type&lt;/code&gt; to
an empty string then resets it back to &lt;code class=&quot;highlighter-rouge&quot;&gt;file&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;resources/elements/file-picker.html&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;file&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;accept&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;multiple&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;multiple&quot;&lt;/span&gt; 
         &lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;files&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ref=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input&quot;&lt;/span&gt;
         &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;visibility: hidden; width: 0; height: 0;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-primary&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input.click()&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;slot&amp;gt;&lt;/span&gt;Select&lt;span class=&quot;nt&quot;&gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;file-picker&lt;/code&gt;’s template defines an &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt; element,
styled so it is invisible and so it occupies no space in the DOM.
Its &lt;code class=&quot;highlighter-rouge&quot;&gt;accept&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;multiple&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; attributes are also properly
bound to their corresponding property on the view-model. Lastly, it 
assigns a reference on the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt; to the view-model’s &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;
property.&lt;/p&gt;

&lt;p&gt;The template also declares a &lt;code class=&quot;highlighter-rouge&quot;&gt;button&lt;/code&gt; element, styled using Bootstrap’s 
classes. Inside it, a default content projection slot, with the 
&lt;em&gt;Select&lt;/em&gt; text as its default content. Additionally, the &lt;code class=&quot;highlighter-rouge&quot;&gt;button&lt;/code&gt;’s
&lt;code class=&quot;highlighter-rouge&quot;&gt;click&lt;/code&gt; event calls the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;click&lt;/code&gt; method. Thanks to this, 
the browser’s file dialog will show up when the user clicks the 
button, even though the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt; element is not visible.&lt;/p&gt;

&lt;p&gt;This component basically just replaces the ugly native file picker
with a sexier button.&lt;/p&gt;

&lt;h2 id=&quot;adding-a-file-drop-target&quot;&gt;Adding a file drop target&lt;/h2&gt;

&lt;p&gt;Next, let’s create a custom attribute allowing to transform any
element into a file drag and drop target:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;resources/attributes/file-drop-target.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;customAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;autoinject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'aurelia-framework'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;customAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'file-drop-target'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;twoWay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;autoinject&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FileDropTarget&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nl&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FileList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FileList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  
  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;attached&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'dragover'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onDragOver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'drop'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onDrop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'dragend'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onDragEnd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;onDragOver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stopPropagation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dataTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dropEffect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'copy'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

  &lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;onDrop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stopPropagation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'function'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dataTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dataTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

  &lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;onDragEnd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stopPropagation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    
    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dataTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;clearData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;detached&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'dragend'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onDragEnd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'drop'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onDrop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'dragover'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onDragOver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The attribute’s target element will be injected in the view-model’s
constructor. When the attribute is &lt;code class=&quot;highlighter-rouge&quot;&gt;attached&lt;/code&gt; to the DOM, it starts 
listening for the &lt;code class=&quot;highlighter-rouge&quot;&gt;dragover&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;drop&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;dragend&lt;/code&gt; events on the 
target element. When the attribute is &lt;code class=&quot;highlighter-rouge&quot;&gt;detached&lt;/code&gt; from the DOM, the 
event listeners are removed.&lt;/p&gt;

&lt;p&gt;The attribute is bound two way by default, so the file(s) assigned 
to its &lt;code class=&quot;highlighter-rouge&quot;&gt;value&lt;/code&gt; when a user drops them on the target element are 
assigned back to the bounded property, if any. However, upon files
being dropped on the target element, the view-model checks if the 
&lt;code class=&quot;highlighter-rouge&quot;&gt;value&lt;/code&gt; is a function or not. This means that the attribute can be 
used either with the &lt;code class=&quot;highlighter-rouge&quot;&gt;.bind&lt;/code&gt; command, so the dropped files are 
assigned to the bound expression, or with the &lt;code class=&quot;highlighter-rouge&quot;&gt;.call&lt;/code&gt; command, so 
the bound expression is called and passed the dropped files whenever 
a &lt;code class=&quot;highlighter-rouge&quot;&gt;drop&lt;/code&gt; event occurs.&lt;/p&gt;

&lt;h2 id=&quot;chunking-an-array&quot;&gt;Chunking an array&lt;/h2&gt;

&lt;p&gt;In order to display the selected images as a gallery, we’ll use 
Bootstrap’s grid system. This means we’ll need to break the files
array down in chunks, so we can iterate on chunks to render rows, 
then on each chunk’s files to render columns.&lt;/p&gt;

&lt;p&gt;The best way to do this in Aurelia is with a value converter:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;resources/value-converters/chunk.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;valueConverter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'aurelia-framework'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;valueConverter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'chunk'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Chunk&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;toView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[][]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nbChunks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nbChunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;chunk&lt;/code&gt; value converter expects an array and the chunks’ size 
as its parameter and returns an array of array.&lt;/p&gt;

&lt;h2 id=&quot;displaying-a-blob-object-as-an-image&quot;&gt;Displaying a Blob object as an image&lt;/h2&gt;

&lt;p&gt;The last part we’ll need is some way to display a &lt;code class=&quot;highlighter-rouge&quot;&gt;File&lt;/code&gt; instance
inside an &lt;code class=&quot;highlighter-rouge&quot;&gt;img&lt;/code&gt; element. To do this, we’ll leverage the browser’s 
&lt;code class=&quot;highlighter-rouge&quot;&gt;URL.createObjectURL&lt;/code&gt; function, which takes a &lt;code class=&quot;highlighter-rouge&quot;&gt;Blob&lt;/code&gt; object as a 
parameter and returns a special URL leading to this resource. Our 
custom attribute, which will be used essentially on &lt;code class=&quot;highlighter-rouge&quot;&gt;img&lt;/code&gt; elements, 
will be bound to a &lt;code class=&quot;highlighter-rouge&quot;&gt;Blob&lt;/code&gt; object, will generate an object URL from it, 
and will assign this URL to the &lt;code class=&quot;highlighter-rouge&quot;&gt;img&lt;/code&gt; element’s &lt;code class=&quot;highlighter-rouge&quot;&gt;src&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;Some of you might think that a value converter would be a better fit for
this type of feature, and I would absolutely agree. A value converter 
could take as an input a &lt;code class=&quot;highlighter-rouge&quot;&gt;Blob&lt;/code&gt; object and return the object URL. It 
could then be used on a binding between an &lt;code class=&quot;highlighter-rouge&quot;&gt;img&lt;/code&gt; element’s &lt;code class=&quot;highlighter-rouge&quot;&gt;src&lt;/code&gt; 
attribute and a property containing a &lt;code class=&quot;highlighter-rouge&quot;&gt;Blob&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;However, in this particular case, each object URL must be released after 
usage in order to prevent memory leaks, and value converters offer no 
mechanism to be notified when a value is no longer used. On the contrary, 
HTML behaviors offer a much richer workflow and a wider set of extension 
points. That’s why we will create a custom attribute instead:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;resources/attributes/blob-src.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;customAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'aurelia-framework'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;customAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'blob-src'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BlobSrc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;HTMLImageElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

  &lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;disposeObjectUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;valueChanged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;disposeObjectUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;unbind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;disposeObjectUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;bringing-it-all-together&quot;&gt;Bringing it all together&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Each of the parts we saw up to this point is shown in the book,
even though some have been modified to fit the current context.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The last missing piece is the one that brings everything together:
an &lt;code class=&quot;highlighter-rouge&quot;&gt;image-files-picker&lt;/code&gt; custom element.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;resources/elements/image-files-picker.html&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;jumbotron jumbotron-fluid&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;file-drop-target&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;call=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;add(files)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text-center&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You can drop image files anywhere inside this area&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;row&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;row of files | chunk:3&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;col-md-4&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;file of row&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;card card-inverse&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;card-img img-fluid&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;alt=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Preview for ${file.name &amp;amp; oneTime}&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;blob-src&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;one-time=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;file&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;card-img-overlay&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;close&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Remove&quot;&lt;/span&gt; 
                      &lt;span class=&quot;na&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;remove($parent.$index * 3 + $index)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-hidden=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;ni&quot;&gt;&amp;amp;times;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;card-text&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;small&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text-muted&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;${file.name &lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt; oneTime}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/small&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;file-picker&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;accept&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;multiple&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;one-time=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt; 
               &lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;selectedFiles&quot;&lt;/span&gt; 
               &lt;span class=&quot;na&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;addSelectedFiles()&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    Add
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/file-picker&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The template starts with a &lt;code class=&quot;highlighter-rouge&quot;&gt;jumbotron&lt;/code&gt; container, which acts as a
&lt;code class=&quot;highlighter-rouge&quot;&gt;file-drop-target&lt;/code&gt;. When files are dragged and dropped on this element,
the view-model’s &lt;code class=&quot;highlighter-rouge&quot;&gt;add&lt;/code&gt; method will be called and passed the dropped
&lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inside this container, the &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; array is rendered on three columns
using the &lt;code class=&quot;highlighter-rouge&quot;&gt;chunk&lt;/code&gt; value converter, each file displayed inside a Bootstrap
&lt;code class=&quot;highlighter-rouge&quot;&gt;card&lt;/code&gt; component. Each &lt;code class=&quot;highlighter-rouge&quot;&gt;card&lt;/code&gt; displays the file in an &lt;code class=&quot;highlighter-rouge&quot;&gt;img&lt;/code&gt; element 
using the &lt;code class=&quot;highlighter-rouge&quot;&gt;blob-src&lt;/code&gt; attribute, a &lt;code class=&quot;highlighter-rouge&quot;&gt;button&lt;/code&gt; whose &lt;code class=&quot;highlighter-rouge&quot;&gt;click&lt;/code&gt; event calls the 
view-model’s &lt;code class=&quot;highlighter-rouge&quot;&gt;remove&lt;/code&gt; method, and the file’s &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, underneath the image gallery, a &lt;code class=&quot;highlighter-rouge&quot;&gt;file-picker&lt;/code&gt; element allows
the user to select image files. The selected files are bound to the
view-model’s &lt;code class=&quot;highlighter-rouge&quot;&gt;selectedFiles&lt;/code&gt; property, then the &lt;code class=&quot;highlighter-rouge&quot;&gt;change&lt;/code&gt; event 
dispatched by the underlying &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt; element and bubbling
up the DOM triggers a call to the &lt;code class=&quot;highlighter-rouge&quot;&gt;addSelectedFiles&lt;/code&gt; method. The 
&lt;code class=&quot;highlighter-rouge&quot;&gt;file-picker&lt;/code&gt;’s default projection slot is also overwritten with the text 
&lt;em&gt;Add&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;resources/elements/image-files-picker.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;customElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;useView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'aurelia-framework'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;customElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'image-files-picker'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;useView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'./image-files-picker.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ImageFilesPicker&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;bindable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;defaultBindingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;twoWay&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;

  &lt;span class=&quot;nl&quot;&gt;selectedFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FileList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FileList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;addSelectedFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;selectedFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;selectedFiles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The view-model declares a &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; bindable property, which is bound
two way by default. This property is expected to initially contain 
an empty array.&lt;/p&gt;

&lt;p&gt;When files are dropped on the drop target element, the &lt;code class=&quot;highlighter-rouge&quot;&gt;add&lt;/code&gt; method
is called and the dropped &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; are appended to the &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; property.
When the user selects files using the &lt;code class=&quot;highlighter-rouge&quot;&gt;file-picker&lt;/code&gt;, the selected files
are assigned back to the &lt;code class=&quot;highlighter-rouge&quot;&gt;selectedFiles&lt;/code&gt; property, then the &lt;code class=&quot;highlighter-rouge&quot;&gt;change&lt;/code&gt;
event handler calls the &lt;code class=&quot;highlighter-rouge&quot;&gt;addSelectedFiles&lt;/code&gt;, which appends the
&lt;code class=&quot;highlighter-rouge&quot;&gt;selectedFiles&lt;/code&gt; to the &lt;code class=&quot;highlighter-rouge&quot;&gt;files&lt;/code&gt; property, and finally assigns &lt;code class=&quot;highlighter-rouge&quot;&gt;null&lt;/code&gt; to 
the &lt;code class=&quot;highlighter-rouge&quot;&gt;selectedFiles&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This last step makes sure that the underlying &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt; 
element has its selection cleared. Without it, if a user tries to add 
the same file twice in a row, the &lt;code class=&quot;highlighter-rouge&quot;&gt;change&lt;/code&gt; event would not be triggered 
the second time, because the &lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt;’s value would not change, so the 
second file selection would fail from the user’s perspective.&lt;/p&gt;

&lt;h2 id=&quot;using-the-image-files-picker&quot;&gt;Using the image files picker&lt;/h2&gt;

&lt;p&gt;Using the &lt;code class=&quot;highlighter-rouge&quot;&gt;image-files-picker&lt;/code&gt; element is then pretty simple. We first
need to declare a property hosting the array of files on the &lt;code class=&quot;highlighter-rouge&quot;&gt;App&lt;/code&gt; 
view-model:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, we simply need to add the custom element in the template of
our &lt;code class=&quot;highlighter-rouge&quot;&gt;App&lt;/code&gt; component:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app.html&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;require&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;from=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bootstrap/css/bootstrap.min.css&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/require&amp;gt;&lt;/span&gt;
  
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;image-files-picker&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;files&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/image-files-picker&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, the various parts need to be loaded, either using
the &lt;code class=&quot;highlighter-rouge&quot;&gt;require&lt;/code&gt; statement in the &lt;code class=&quot;highlighter-rouge&quot;&gt;app.html&lt;/code&gt; template, or in the
&lt;code class=&quot;highlighter-rouge&quot;&gt;resources/index.ts&lt;/code&gt; feature’s &lt;code class=&quot;highlighter-rouge&quot;&gt;configure&lt;/code&gt; function.&lt;/p&gt;

&lt;h2 id=&quot;filtering-out-non-image-files&quot;&gt;Filtering out non-image files&lt;/h2&gt;

&lt;p&gt;At this point, a user can select or drop any type of files using our
component. Some logic allowing only image files should be somehow added.&lt;/p&gt;

&lt;p&gt;A basic filtering logic, using the same syntax as the &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt;
element’s &lt;code class=&quot;highlighter-rouge&quot;&gt;accept&lt;/code&gt; attribute, is implemented in the complete code sample,
which you can find 
&lt;a href=&quot;https://github.com/manuel-guilbault/learning-aurelia-image-files-picker&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
A more complete solution, showing error messages to the user, can easily be
implemented. I’ll leave this as an exercise to the reader.&lt;/p&gt;

&lt;h2 id=&quot;exploiting-the-selected-images&quot;&gt;Exploiting the selected images&lt;/h2&gt;

&lt;p&gt;Typically, such a component would be used to first select a bunch of image files,
then to upload those files to some remote endpoint. This is pretty easy to do with 
Aurelia’s Fetch client and the &lt;code class=&quot;highlighter-rouge&quot;&gt;FormData&lt;/code&gt; class from the Fetch API.&lt;/p&gt;

&lt;p&gt;Here’s an example of a client service used to upload an array of &lt;code class=&quot;highlighter-rouge&quot;&gt;File&lt;/code&gt; instances
to some remote endpoint:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;autoinject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'aurelia-framework'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;HttpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'aurelia-fetch-client'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;autoinject&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SomeAPI&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;HttpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;uploadFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FormData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`files[&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'some/url'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'POST'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The Mozilla Developer Network has 
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects&quot; target=&quot;_blank&quot;&gt;some great doc&lt;/a&gt;
about the &lt;code class=&quot;highlighter-rouge&quot;&gt;FormData&lt;/code&gt; class.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Once again, Aurelia makes things easy. Its various constructs, such as custom attributes,
elements, and value converters, help us decompose a problem and solve each of its parts
with a generic, reusable solution, and then recombine them together to address our initial,
specific problem. &lt;strong&gt;Shameless plug alert&lt;/strong&gt;: this aspect is one of the many topics addressed 
in &lt;a href=&quot;https://www.packtpub.com/web-development/learning-aurelia&quot; target=&quot;_blank&quot;&gt;Learning Aurelia&lt;/a&gt;. You should
definitely give it a look!&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;update-2018-03-02&quot;&gt;Update 2018-03-02&lt;/h2&gt;

&lt;p&gt;Reader @sokratismanolis pointed out in &lt;a href=&quot;http://disq.us/p/1qifjgc&quot;&gt;this comment&lt;/a&gt; that the code
doesn’t work on IE 11 and Edge. I didn’t have time to find out the core of the issue yet, but
it seems to be caused by a delegated event listener being fired before a data binding instruction
is refreshed. I worked around the issue by removing the delegated event listener and by using
the &lt;code class=&quot;highlighter-rouge&quot;&gt;@observable&lt;/code&gt; attribute on &lt;code class=&quot;highlighter-rouge&quot;&gt;selectedFiles&lt;/code&gt;, as you can see on
&lt;a href=&quot;https://github.com/manuel-guilbault/learning-aurelia-image-files-picker/tree/2018-03-02-fix-on-edge&quot;&gt;this branch&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 01 Feb 2017 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2017/02/01/Building-an-image-files-picker-with-Aurelia/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2017/02/01/Building-an-image-files-picker-with-Aurelia/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>Aurelia</category>
        
        <category>JS</category>
        
        <category>TypeScript</category>
        
        <category>Web</category>
        
        
      </item>
    
      <item>
        <title>Building Aurelia's focus attribute</title>
        <description>&lt;p&gt;&lt;a href=&quot;http://aurelia.io/&quot; target=&quot;_blank&quot;&gt;Aurelia&lt;/a&gt; is a next generation SPA framework, initiated by 
&lt;a href=&quot;http://robeisenberg.com/&quot; target=&quot;_blank&quot;&gt;Rob Eisenberg&lt;/a&gt;, creator of 
&lt;a href=&quot;http://durandaljs.com/&quot; target=&quot;_blank&quot;&gt;Durandal&lt;/a&gt;. I’m really proud to be featured on the 
blog this week. 
&lt;a href=&quot;http://blog.aurelia.io/2015/06/05/building-aurelias-focus-attribute/&quot; target=&quot;_blank&quot;&gt;Check it out&lt;/a&gt;!&lt;/p&gt;
</description>
        <pubDate>Fri, 05 Jun 2015 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2015/06/05/Building-Aurelias-focus-attribute/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2015/06/05/Building-Aurelias-focus-attribute/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>Aurelia</category>
        
        <category>JS</category>
        
        <category>Web</category>
        
        
      </item>
    
      <item>
        <title>Hard-coded IDs suck, and enums won't help</title>
        <description>&lt;p&gt;Sometimes an application requires that some features are enabled (or rules applied), or not, 
based on some context. What is the best way to solve such a problem?&lt;/p&gt;

&lt;h2 id=&quot;hard-coded-ids-hard-coded-ids-everywhere&quot;&gt;Hard-coded IDs… Hard-coded IDs everywhere…&lt;/h2&gt;

&lt;p&gt;A C# app I was working on for a worldwide company was used in many countries. Of course, each 
country had specific features that were enabled and rules that were applied only for them. 
The previous programmers had used a very naive approach to solve this problem: they tested 
the user’s country ID:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Country&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ID&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Show a button available only for country #1&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// By the way, country #1 is France for the sake of this example&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The app ended up being full of those ugly checks. Plus, if you don’t know the countries and 
their IDs and don’t have the database table under hand, good luck trying to figure out what 
users of which country should see this button.&lt;/p&gt;

&lt;h2 id=&quot;enums-to-the-rescue---or-not&quot;&gt;Enums to the rescue - or not&lt;/h2&gt;

&lt;p&gt;I was absolutely not convinced by the first idea that came to my mind, but I decided to give 
it a try. I created an enum listing the countries:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CountryEnum&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;France&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Canada&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// and so on&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then I added a method to the Country entity:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Country&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// snip...&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CountryEnum&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ToEnum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CountryEnum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// snip...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the checks turned into:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Country&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToEnum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CountryEnum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;France&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Show a button available only for France&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Well… it was a little better. It was much easier to see what features and rules applied 
to which country. But it still sucked. Mainly because it was hard to change. Imagine 
having a rule for France that would require to show or enable multiple buttons, links, 
table columns or whatever on multiple views. And imagine that Canada wants that same 
feature in the next release. It was still a huge “oh God no” for me to go and update all 
those checks to include those pesky Canadians. Add to that the fact that the pages and user 
controls (yes, it was a web forms app) were bloated with those checks, sometimes many of 
them entangled together, and you’ve got a huge mess on your hands. Time to sit back and think.&lt;/p&gt;

&lt;h2 id=&quot;feature-toggles&quot;&gt;Feature toggles&lt;/h2&gt;

&lt;p&gt;I remembered reading a while ago about 
&lt;a href=&quot;https://martinfowler.com/bliki/FeatureToggle.html&quot;&gt;feature toggles&lt;/a&gt; on Martin Fowler’s bliki, 
and I realized that this solution, although brought up in a static context (the way Fowler 
describes them, feature toggles are enabled or disabled generally in configuration - in our 
example, each country would have its own live version of the app, configured for its own 
needs), could be applied in a dynamic context like mine.&lt;/p&gt;

&lt;p&gt;So I rolled all my changes back, and started from scratch:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CountryFeatures&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CanDoThis&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CanSeeThat&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MustConfirmBeforeThatAction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// and so on for every country-specific feature or rule&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You get the idea for the next step, I guess:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Country&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// snip...&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CountryFeatures&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Features&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// snip...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;How I initialized the Features property for each country is not relevant to this article: 
you could store the feature switches for each country in a database table, in a 
configuration file, or even hard-code them for all I care. 
What’s important is that the feature checks were now much clearer:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Country&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CanDoThis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Show the button used to do this&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that is a much better solution. The single responsibility principle is respected: 
the view only needs to know if a given feature is enabled or not to decide if it must 
render the button. It doesn’t need to know for which country the feature is enabled anymore.&lt;/p&gt;

&lt;p&gt;Additionally, even if this check was found in multiple places, there was no need to change 
all those occurrences anymore to enable the feature for Canada. I only needed to toggle the 
feature on for the countries that needed it.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Feature toggle is a powerful tool that can be leveraged not only statically, through 
configuration, to enable or disable features on a per-deployment basis, but it can also be 
used dynamically, using some context information in a single deployment setup.&lt;/p&gt;
</description>
        <pubDate>Tue, 07 Oct 2014 00:00:00 +0000</pubDate>
        <author>manuel.guilbault@gmail.com (Manuel Guilbault)</author>
        <link>http://manuelguilbault.com/blog/2014/10/07/Hard-coded-IDs-suck-and-enums-wont-help/</link>
        <guid isPermaLink="true">http://manuelguilbault.com/blog/2014/10/07/Hard-coded-IDs-suck-and-enums-wont-help/</guid>
        <source url="http://manuelguilbault.com/rss.xml">Manuel Guilbault's Blog</source>
        
        <category>CSharp</category>
        
        <category>Design</category>
        
        
      </item>
    
  </channel>
</rss>
