Exercise - Use a storage account to host a static website

Completed

Now that the API is deployed to the cloud, as a Tailwind Traders engineer, you need to update the client code and deploy it to support the SignalR messages coming for Azure Functions.

Update the client application

  1. In Visual Studio Code, open ./start/client/src/index.js and replace all the code to listen for SignalR messages. The code gets the initial stock list with an HTTP request and then listens for updates from the SignalR connection. When a stock is updated, the client updates the stock price in the UI.

    import './style.css';
    import { BACKEND_URL } from './env';
    
    const app = new Vue({
        el: '#app',
        data() {
            return {
                stocks: []
            }
        },
        methods: {
            async getStocks() {
                try {
    
                    const url = `${BACKEND_URL}/api/getStocks`;
                    console.log('Fetching stocks from ', url);
    
                    const response = await fetch(url);
                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status} - ${response.statusText}`);
                    }
                    app.stocks = await response.json();
                } catch (ex) {
                    console.error(ex);
                }
            }
        },
        created() {
            this.getStocks();
        }
    });
    
    const connect = () => {
    
        const signalR_URL = `${BACKEND_URL}/api`;
        console.log(`Connecting to SignalR...${signalR_URL}`)
    
        const connection = new signalR.HubConnectionBuilder()
                                .withUrl(signalR_URL)
                                .configureLogging(signalR.LogLevel.Information)
                                .build();
    
        connection.onclose(()  => {
            console.log('SignalR connection disconnected');
            setTimeout(() => connect(), 2000);
        });
    
        connection.on('updated', updatedStock => {
            console.log('Stock updated message received', updatedStock);
            const index = app.stocks.findIndex(s => s.id === updatedStock.id);
            console.log(`${updatedStock.symbol} Old price: ${app.stocks[index].price}, New price: ${updatedStock.price}`);
            app.stocks.splice(index, 1, updatedStock);
        });
    
        connection.start().then(() => {
            console.log("SignalR connection established");
        });
    };
    
    connect();
    
  2. Open ./start/client/index.html and paste the following code in place of the current DIV with the ID of app.

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css"
            integrity="sha256-8B1OaG0zT7uYA572S2xOxWACq9NXYPQ+U5kHPV1bJN4=" crossorigin="anonymous" />
        <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
            integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
        <title>Stock Notifications | Enable automatic updates in a web application using Azure Functions and SignalR</title>
    </head>
    
    <body>
        <div id="app" class="container">
            <h1 class="title">Stocks</h1>
            <div id="stocks">
                <div v-for="stock in stocks" class="stock">
                    <transition name="fade" mode="out-in">
                        <div class="list-item" :key="stock.price">
                            <div class="lead">{{ stock.symbol }}: ${{ stock.price }}</div>
                            <div class="change">Change:
                                <span
                                    :class="{ 'is-up': stock.changeDirection === '+', 'is-down': stock.changeDirection === '-' }">
                                    {{ stock.changeDirection }}{{ stock.change }}
                                </span>
                            </div>
                        </div>
                    </transition>
                </div>
            </div>
        </div>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"
            integrity="sha256-chlNFSVx3TdcQ2Xlw7SvnbLAavAQLO0Y/LBiWX04viY=" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
        <script src="bundle.js" type="text/javascript"></script>
    </body>
    </html>
    

    This markup includes a transition element, which allows Vue.js to run a subtle animation as stock data changes. When a stock is updated, the tile fades out and quickly back in to view. This way if the page is full of stock data, users can easily see which stocks have changed.

  3. Add the following script block just above the reference to bundle.js to include the SignalR SDK.

    <script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.0.3/dist/browser/signalr.js"></script>
    

Update the client .env

  1. Create an environment variables file in the client/start folder named .env.

  2. Add a variable named BACKEND_URL and add its value you copied from unit 5.

    BACKEND_URL=https://YOUR-FUNTIONS-APP-NAME.azurewebsites.net
    

Create an Azure Static Web apps resource and deploy client

  1. Open the Azure portal to create a new Azure Static Web Apps resource.

  2. Use the following information to complete the resource creation Basics tab.

    Name Value
    Subscription Select your subscription.
    Resource group Use the resource group stock-prototype.
    Static Web App name Postpend your name to client. For example, client-jamie.
    Hosting plan type Select Free.
    Deployment source Select GitHub.
    Organization Select your GitHub account
    Repository Search and select mslearn-advocates.azure-functions-and-signalr.
    Branch Select the main branch.
    Build Presets Select Vue.js.
    App location Enter /start/client.
    Api Location Leave empty.
    Output Location Enter dist.
  3. Select Preview workflow file to review the deployment settings. The Build And Deploy step should look like the following:

    - name: Build And Deploy
      id: builddeploy
      uses: Azure/static-web-apps-deploy@v1
      with:
        azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_123 }}
        repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
        action: "upload"
        ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
        # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
        app_location: "/solution/client" # App source code path
        api_location: "" # Api source code path - optional
        output_location: "dist" # Built app content directory - optional
        ###### End of Repository/Build Configurations ######
    
  4. Select Close to save the change.

  5. Select Review + Create, then select Create to create the resource. Wait for the deployment to complete before continuing.

  6. Select Go to resource to open the new Azure Static Web App resource.

  7. On the Overview page, copy the URL value. This is the base URL of the deployed static web app.

Add the BACKEND_URL variable to the repository

The workflow needs to have the BACKEND_URL environment variable set to the deployed Azure Functions app base URL from unit 5.

  1. In a browser for your GitHub fork of the sample repository, select Settings -> Security -> Secrets and variables -> Actions.

  2. Select Variables, then select New repository variable.

  3. Use the following table to create the variable:

    Name Value
    BACKEND_URL The base URL of the deployed Azure Functions app. The URL should be in the format of https://<FUNCTIONS-RESOURCE-NAME>.azurewebsites.net
  4. Select Add variable to save the variable to the repository.

Edit GitHub deployment workflow

  1. In Visual Studio Code terminal, pull down the new workflow file from your fork (origin).

    git pull origin main
    
  2. Open the .github/workflows/azure-static-web-apps-*.yml file.

  3. Change the name value at the top of the file to Client.

  4. Edit the Build And Deploy step to add the env property for the BACKEND_URL environment variable.

    ```yaml
        name: Client - Azure Static Web Apps CI/CD
        
        on:
          push:
            branches:
              - main
          pull_request:
            types: [opened, synchronize, reopened, closed]
            branches:
              - main
        
        jobs:
          build_and_deploy_job:
            if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
            runs-on: ubuntu-latest
            name: Build and Deploy Job
            steps:
    
              - uses: actions/checkout@v3
                with:
                  submodules: true
                  lfs: false
    
              #Debug only - Did GitHub action variable get into workflow correctly?
              - name: Echo
                run: |
                  echo "BACKEND_URL: ${{ vars.BACKEND_URL }}"
    
              - name: Build And Deploy
                id: builddeploy
                uses: Azure/static-web-apps-deploy@v1
                with:
                  azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_123 }}
                  repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
                  action: "upload"
                  ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
                  # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
                  app_location: "/solution/client" # App source code path
                  api_location: "" # Api source code path - optional
                  output_location: "dist" # Built app content directory - optional
                  ###### End of Repository/Build Configurations ######
                env: 
                  BACKEND_URL: ${{ vars.BACKEND_URL }}
    
          close_pull_request_job:
            if: github.event_name == 'pull_request' && github.event.action == 'closed'
            runs-on: ubuntu-latest
            name: Close Pull Request Job
            steps:
              - name: Close Pull Request
                id: closepullrequest
                uses: Azure/static-web-apps-deploy@v1
                with:
                  azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_123 }}
                  action: "close"
        ```
    
  5. Save and push the changes to the repository.

    git add .
    git commit -m "Add BACKEND_URL environment variable"
    git push
    
  6. Open the Actions tab in the GitHub fork repository to watch the deployment.

Update CORS in the function app

By default, function apps don't allow CORS requests. You need to update the function app to allow requests from the static web app.

  1. In the Azure portal, navigate to the Azure Functions app created in unit 5.

  2. In the left-hand menu, select API -> CORS.

  3. Select Enable Access-Control-Allow-Credentials.

  4. Add the value you copied for the Static Web Apps resource URL.

    Property Value
    Allowed origins The base URL of the deployed static web app.
  5. Select Save to save the CORS settings.

Test the deployment of the client

  1. In a browser, use the URL of the deployed static web app to open the client.
  2. Open developer tools to watch the Console to see when the SignalR data for updated stock is received. Remember these aren't HTTP requests, so you won't see them in the Network tab.

Congratulations! You've deployed your stock app improved with SignalR!