<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel><title>Edi Wang</title><description>Microsoft MVP for Azure</description><link>https://edi.wang/</link><pubDate>2026-11-09T19:58:14Z</pubDate><copyright>© 2009 - 2026 edi.wang</copyright><generator>Moonglade v16.10.0-preview.1</generator><language>en-US</language><item><title>How to Set no-cache Header for index.html in Azure Static Web Apps</title><link>https://edi.wang/post/2026/5/5/avoiding-stale-angular-builds-with-azure-static-web-apps</link><description>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;When hosting an Angular application on Azure Static Web Apps, there is one small caching detail that can cause a surprisingly annoying issue:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; can be cached by the browser.&lt;/p&gt;
&lt;p&gt;That might sound harmless at first, but for a single-page application it can prevent users from getting the latest version of the app after a deployment.&lt;/p&gt;
&lt;p&gt;The fix is simple: tell Azure Static Web Apps to instruct the browser not to cache &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Angular production builds usually generate files like this:&lt;/p&gt;
&lt;pre class="language-plaintext"&gt;&lt;code&gt;main.8f3a1c2d.js
polyfills.1d4a9b7e.js
styles.2c9f0a11.css&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These files are fingerprinted with a hash in the file name. That is great for caching because when the content changes, the file name changes too.&lt;/p&gt;
&lt;p&gt;So caching files like JavaScript, CSS, images, and fonts is normally fine.&lt;/p&gt;
&lt;p&gt;The problem is &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;index.html&lt;/code&gt; file is the entry point for the Angular app. It contains references to the generated JavaScript and CSS files.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre class="language-xml"&gt;&lt;code&gt;&amp;lt;script src="main.8f3a1c2d.js" type="module"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a new deployment, Angular may generate a new file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main.a91b72ff.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;But if the browser still uses an old cached version of index.html, it may keep trying to load the old JavaScript bundle.&lt;/p&gt;
&lt;p&gt;Depending on your deployment setup, this can result in issues such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;users not seeing the latest version of the app&lt;/li&gt;
&lt;li&gt;old JavaScript bundles being loaded&lt;/li&gt;
&lt;li&gt;ChunkLoadError errors&lt;/li&gt;
&lt;li&gt;the app appearing broken until the user does a hard refresh&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not a great experience.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/20260505-08a4244a.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;The solution is to configure Azure Static Web Apps to return cache headers for index.html that prevent the browser from caching it.&lt;/p&gt;
&lt;p&gt;In Azure Static Web Apps, this is done using the &lt;code&gt;staticwebapp.config.json&lt;/code&gt; file. Typically, you will put this file in the &lt;code&gt;public &lt;/code&gt;folder of your Angular application.&lt;/p&gt;
&lt;p&gt;Here is an example configuration:&lt;/p&gt;
&lt;pre class="language-json"&gt;&lt;code&gt;{
  "navigationFallback": {
    "rewrite": "/index.html",
    "exclude": [
      "/*.{css,scss,sass,js,mjs,map,ico,png,jpg,jpeg,gif,svg,webp,woff,woff2,ttf,eot,txt,xml}",
      "/assets/*"
    ]
  },
  "routes": [
    {
      "route": "/index.html",
      "headers": {
        "Cache-Control": "no-cache, no-store, must-revalidate"
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is this route:&lt;/p&gt;
&lt;pre class="language-json"&gt;&lt;code&gt;{
  "route": "/index.html",
  "headers": {
    "Cache-Control": "no-cache, no-store, must-revalidate"
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells the browser that index.html should not be stored and reused without checking for a fresh version.&lt;/p&gt;
&lt;p&gt;After deploying, you can verify the response headers using your browser dev tools or &lt;code&gt;curl&lt;/code&gt;. You should see something like &lt;code&gt;Cache-Control: no-cache, no-store, must-revalidate&lt;/code&gt;&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;edi@edi-pn61:~$ curl -I https://tools.edi.wang
HTTP/2 200
content-type: text/html
date: Tue, 05 May 2026 03:44:29 GMT
accept-ranges: bytes
cache-control: no-cache, no-store, must-revalidate
etag: "93063574"
last-modified: Mon, 04 May 2026 05:42:45 GMT
content-length: 3704
strict-transport-security: max-age=10886400; includeSubDomains; preload
referrer-policy: same-origin
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
x-dns-prefetch-control: off&lt;/code&gt;&lt;/pre&gt;</description><category>Web Development</category><category>Microsoft Azure</category><guid isPermaLink="false">df46b276-635f-4df7-a422-e65be640181c</guid><pubDate>2026-05-05T03:46:34Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2026-05-05T03:46:34Z</updated></item><item><title>Adding Azure Bicep Language Support for highlight.js</title><link>https://edi.wang/post/2025/5/18/adding-azure-bicep-language-support-for-highlightjs</link><description>&lt;p&gt;I am using highlight.js to render code snippets on this blog system. However, the official pack of highlight.js does not have &lt;a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Bicep&lt;/a&gt; as one of the languages. To add support for the Bicep language in highlight.js, you need to implement a Bicep language definition module. Below are the detailed steps and code examples.&lt;/p&gt;
&lt;h2&gt;Bicep Language Features&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Bicep is a declarative, Azure-oriented Infrastructure-as-Code language. Its main features include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Keywords&lt;/strong&gt;: &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;module&lt;/code&gt;, &lt;code&gt;param&lt;/code&gt;, &lt;code&gt;var&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt;, &lt;code&gt;targetScope&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;in&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;existing&lt;/code&gt;, &lt;code&gt;import &amp;nbsp;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Types&lt;/strong&gt;: &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;object&lt;/code&gt;, &lt;code&gt;array&lt;/code&gt;, &lt;code&gt;any &amp;nbsp;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comments&lt;/strong&gt;: // single-line comment, /* ... */ multi-line comment &amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strings&lt;/strong&gt;: single quotes &lt;code&gt;'...'&lt;/code&gt; and double quotes &lt;code&gt;"..."&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;highlight.js Language Definition&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;The language definitions for highlight.js are usually as follows:&lt;/p&gt;
&lt;pre class="language-javascript"&gt;&lt;code&gt;export default function(hljs) {
  return {
    name: 'Bicep',
    keywords: { ... },
    contains: [ ... ]
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can refer to this example of the&amp;nbsp;&lt;code&gt;cmake &lt;/code&gt;language definition here:&amp;nbsp;&lt;a href="https://github.com/highlightjs/highlight.js/blob/main/src/languages/cmake.js"&gt;https://github.com/highlightjs/highlight.js/blob/main/src/languages/cmake.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let's create a file with name &lt;code&gt;highlight.bicep.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="language-javascript"&gt;&lt;code&gt;export default function (hljs) {
    return {
        name: 'Bicep',
        keywords: {
            keyword:
                'resource module param var output targetScope for in if existing import',
            type:
                'string int bool object array any',
            literal:
                'true false null'
        },
        contains: [
            hljs.COMMENT('//', '$'),
            hljs.COMMENT('/\\*', '\\*/'),
            {
                className: 'string',
                variants: [
                    hljs.QUOTE_STRING_MODE,
                    hljs.APOS_STRING_MODE
                ]
            },
            {
                className: 'number',
                begin: '\\b\\d+(\\.\\d+)?\\b'
            }
        ]
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Register Bicep Language and Example Usage&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;To register Bicep language, call &lt;code&gt;hljs.registerLanguage('bicep', bicep)&lt;/code&gt;&amp;nbsp;before rendering your code.&amp;nbsp;&lt;/p&gt;
&lt;pre class="language-javascript"&gt;&lt;code&gt;import bicep from './highlight.bicep.js';

hljs.registerLanguage('bicep', bicep);
hljs.highlightAll();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can refer to the official document here: &lt;a href="https://highlightjs.readthedocs.io/en/latest/api.html#registerlanguage"&gt;https://highlightjs.readthedocs.io/en/latest/api.html#registerlanguage&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In my case, my blog use a slightly complicated way to render code in my blog post.&amp;nbsp;&lt;/p&gt;
&lt;pre class="language-javascript"&gt;&lt;code&gt;import bicep from '../3rd/highlight.bicep.js'

export function renderCodeHighlighter() {
    const pres = document.querySelectorAll('pre');
    pres.forEach(pre =&amp;gt; {
        // Find &amp;lt;pre&amp;gt; that doesn't have a &amp;lt;code&amp;gt; inside it.
        if (!pre.querySelector('code')) {
            const code = document.createElement('code');
            while (pre.firstChild) {
                code.appendChild(pre.firstChild);
            }
            pre.appendChild(code);
        }

        // For code that can't be automatically detected, fall back to use XML
        if (pre.classList.contains('language-markup')) {
            pre.querySelector('code').classList.add('lang-xml');
        }
    });

    hljs.registerLanguage('bicep', bicep);

    const codeBlocks = document.querySelectorAll('pre code');
    codeBlocks.forEach(block =&amp;gt; {
        hljs.highlightElement(block);
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's all. Now we can test the example code:&lt;/p&gt;
&lt;pre class="language-xml"&gt;&lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code class="bicep"&amp;gt;
param location string = resourceGroup().location
resource storage 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: 'mystorage'
  location: location
}
&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything goes well, you will see this result:&lt;/p&gt;
&lt;pre class="language-bicep"&gt;&lt;code&gt;param location string = resourceGroup().location
resource storage 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: 'mystorage'
  location: location
}&lt;/code&gt;&lt;/pre&gt;</description><category>Web Development</category><category>Microsoft Azure</category><guid isPermaLink="false">6daf2659-5cf5-4339-923a-1a791c85f807</guid><pubDate>2025-05-18T12:36:49Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2025-05-18T12:36:49Z</updated></item><item><title>Add X-Content-Type-Options Header to Resources in Azure Storage Account</title><link>https://edi.wang/post/2024/5/11/add-header-to-resources-in-azure-storage-account</link><description>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;By default, resources served from an Azure Storage Account do not include the&amp;nbsp;&lt;code&gt;X-Content-Type-Options&lt;/code&gt; header in their responses. This omission can pose a security risk. According to webhint:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Sometimes the metadata browsers need to know how to interpret the content of a resource is either incorrect, not reliable, or absent. In those cases, browsers use contextual clues that inspect the bytes of the response to detect the file format. This is known as &lt;a href="https://mimesniff.spec.whatwg.org/" target="_blank" rel="noopener"&gt;MIME sniffing&lt;/a&gt;&amp;nbsp;and it is done regardless of the specified&amp;nbsp;&lt;code&gt;Content-Type&lt;/code&gt; HTTP header sent by servers."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="/image/img-704a8a0a-0a7c-43b6-beaa-4476a419a4f7.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-3c2067c6-d354-41af-80af-93cbc78c34f7.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;p dir="auto"&gt;To address this issue, you need to integrate a Content Delivery Network (CDN) with your storage account. Follow the steps outlined in this&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/azure/cdn/cdn-create-a-storage-account-with-cdn?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;official Azure documentation&lt;/a&gt;&amp;nbsp;to set up a CDN.&lt;/p&gt;
&lt;p dir="auto"&gt;Once the CDN is in place,&amp;nbsp;go to "&lt;strong&gt;Rules engine&lt;/strong&gt;" section of the CDN, under &lt;strong&gt;Global&lt;/strong&gt; rule, add an action to "&lt;strong&gt;Modify Response Header&lt;/strong&gt;".&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-0d1bca6a-4efe-4195-993c-2817ec686a96.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;With values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Action: &lt;code&gt;Append&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTTP header name: &lt;code&gt;X-Content-Type-Options&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTTP header value: &lt;code&gt;nosniff&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/image/img-22ccd1c6-144b-4380-afd9-b02eced20ab2.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;After saving the rule, allow a few moments for the changes to take effect. In some cases, you might also need to purge the CDN endpoint to ensure that the new settings are applied immediately.&lt;/p&gt;
&lt;p&gt;With these changes, all outgoing requests from your Azure Storage Account via the CDN will include the&amp;nbsp;&lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt; header in the response, enhancing the security of your served resources.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-487ba7c2-ba14-4e93-9b51-5ed57802cb39.png" border="0" loading="lazy"&gt;&lt;/p&gt;</description><category>Web Development</category><category>Microsoft Azure</category><guid isPermaLink="false">951d3888-92b4-4162-92b4-791958f4ff00</guid><pubDate>2024-05-11T01:14:12Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2024-05-11T01:14:12Z</updated></item><item><title>Call API with Windows Authentication from Blazor Web Assembly</title><link>https://edi.wang/post/2023/10/20/call-api-with-windows-authentication-from-blazor-web-assembly</link><description>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;I was rewriting an old Angular application to Blazor Web Assembly a couple of days ago. The App is an internal tool, which uses a backend API that has Windows Authentication. In Angular, we used to add &lt;code&gt;withCredentials: true&lt;/code&gt; to HTTP interceptor to make it work. The most equivalent method in .NET is typically adding &lt;code&gt;UseDefaultCredentials = true&lt;/code&gt; in HttpClient. However, Blazor WASM will gives you an exception for this as indicated in &lt;a href="https://stackoverflow.com/questions/56542696/using-blazor-client-side-consuming-web-api-with-windows-authentication" target="_blank" rel="noopener"&gt;this StackOverflow thread&lt;/a&gt;: WASM: System.PlatformNotSupportedException: System.Net.Http.HttpClientHandler is not supported on the current platform.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The majority of internet search result so far tell you it's not possible to call Windows Authentication API from Blazor. Really? Let's see how to make it work with a few lines of code.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;Approach A&lt;/h3&gt;
&lt;p&gt;The original&amp;nbsp;&lt;a href="https://stackoverflow.com/questions/56542696/using-blazor-client-side-consuming-web-api-with-windows-authentication" target="_blank" rel="noopener"&gt;StackOverflow thread&lt;/a&gt; has 2 answers currently. The answer by &lt;a href="https://stackoverflow.com/users/27754/ca8msm"&gt;ca8msm&lt;/a&gt; works, but the cost is not to use &lt;code&gt;HttpClient&lt;/code&gt;, that we need to do everything manually in order to add &lt;code&gt;BrowserRequestCredentials.Include&lt;/code&gt; to the request header. This approach sacrifices what we love about the &lt;code&gt;HttpClient&lt;/code&gt;, but gives you ultimate control over the http request details.&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;string APIURL = "https://localhost:44390/api/models";

// create request object and pass windows authentication credentials
var request = new HttpRequestMessage(HttpMethod.Get, APIURL);
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);

// send the request and convert the results to a list
var httpResponse = await Http.SendAsync(request);
models = await httpResponse.Content.ReadFromJsonAsync&amp;lt;myModel[]&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Approach B&lt;/h3&gt;
&lt;p&gt;I come up with a better approach, which can keep the &lt;code&gt;HttpClient &lt;/code&gt;we already like.&lt;/p&gt;
&lt;p&gt;First, add a custom &lt;code&gt;DelegatingHandler&lt;/code&gt;, this is used for adding &lt;code&gt;BrowserRequestCredentials.Include&lt;/code&gt; to each request.&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;using Microsoft.AspNetCore.Components.WebAssembly.Http;

public class CredentialsMessageHandler : DelegatingHandler
{
    protected override Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
        return base.SendAsync(request, cancellationToken);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second, create your typed client as usual. I prefer typed client, you may also use &lt;code&gt;HttpClientFactory&lt;/code&gt;, it doesn't matter. You can find more information about how to make http request in best practice in &lt;a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;this document&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public class CardClient
{
    private readonly HttpClient http;

    public CardClient(HttpClient http)
    {
        // ...
        this.http = http;
    }

    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, register the client in DI, with the &lt;code&gt;CredentialsMessageHandler&lt;/code&gt; we wrote in the first step.&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;builder.Services.AddHttpClient&amp;lt;CardClient&amp;gt;(client =&amp;gt; client.BaseAddress = new Uri(ApiEndpoint.FullUrl)).AddHttpMessageHandler&amp;lt;CredentialsMessageHandler&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can still use the &lt;code&gt;HttpClient&lt;/code&gt; the way you always been using it with Windows Authentication enabled API!&lt;/p&gt;
&lt;p&gt;BTW, Why I am not answering this in StackOverflow? Because I lost access to my account years ago :(&lt;/p&gt;</description><category>Web Development</category><category>.NET</category><guid isPermaLink="false">9c4d64ca-5b5b-432e-8ae5-4c06125658be</guid><pubDate>2023-10-20T07:17:07Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2023-10-20T07:17:07Z</updated></item><item><title>Solving "npm install" ECONNRESET Error on Azure DevOps</title><link>https://edi.wang/post/2023/8/16/npm-econnreset-azure-devops</link><description>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;A few months ago, my Angular project on Azure DevOps started to have build failure very often. Error message is "&lt;span style="color: rgb(224, 62, 45);"&gt;npm ERR! network read ECONNRESET&lt;/span&gt;", which indicates npm can't connect to internet. This problem does not happen every time, usually re-run failed jobs several times can produce a good build. But still, this is very annoying. So, I took some time to investigate and solve it.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5c2f53b9-9306-495b-beb4-2dd4998edae5.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;First, I thought it was a IP rate limit by npm that reject Azure pipeline's agent. So I set up a VM on Azure as a self-host build agent. However, the problem continues.&lt;/p&gt;
&lt;p&gt;Then, I setup the same build agent on my laptop, which is outside of Azure environment, the build succeed every time. It seems to be npm rejecting the entire Azure IP address. But I can open &lt;a href="https://registry.npmjs.org/"&gt;https://registry.npmjs.org/&lt;/a&gt; without any problem on my Azure VM, how come the IP limit?&lt;/p&gt;
&lt;p&gt;Finally, I added &lt;strong&gt;verbose &lt;/strong&gt;log to the npm install task in my pipeline yaml file like this:&lt;/p&gt;
&lt;pre class="language-yaml"&gt;&lt;code&gt;jobs:
- job: Angular
  pool:
    vmImage: 'ubuntu-latest'
  steps:

  - task: Npm@1
    displayName: 'npm install'
    inputs:
      workingDir: '$(System.DefaultWorkingDirectory)/src'
      command: 'install'
      verbose: true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the build again, check it's output&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-29179f16-8207-4c66-b91e-af755c7246df.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;The log indicates that some packages are being fetched from Tencent registry instead of the official &lt;a href="https://registry.npmjs.org/"&gt;registry.npmjs.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Azure has problems communicating with Tencent server, this is why the build will fail very often.&lt;/p&gt;
&lt;p&gt;The reason why these packages are going to use Tencent server is because the URLs are written in &lt;code&gt;package-lock.json&lt;/code&gt; file. This file is automatically modified when developers run &lt;code&gt;npm install&lt;/code&gt; on their machines. A typical developer in China would have setup thier npm to use Tencent or Taobao source, due to the national firewall usually block official npm registry.&amp;nbsp;&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;npm config set registry https://mirrors.cloud.tencent.com/npm/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when the developer submit the code, &lt;code&gt;package-lock.json&lt;/code&gt; will have this URL inside it.&lt;/p&gt;
&lt;h2&gt;Fix&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;First, let all developers in the team use official npm registry.&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;npm config set registry https://registry.npmjs.org/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, delete &lt;code&gt;node_modules &lt;/code&gt;folder and &lt;code&gt;package-lock.json&lt;/code&gt; under the project directory&lt;/p&gt;
&lt;p&gt;Then, run &lt;code&gt;npm install &lt;/code&gt;on the dev machine again.&lt;/p&gt;
&lt;p&gt;Now, all packages will be updated to use the official npm registry.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-70a6638c-7c62-425e-9537-5b2a35a72450.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now, submit the code, Azure DevOps now runs &lt;code&gt;npm install&lt;/code&gt; without network error!&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8584a7d9-9e75-4cba-b59d-43af932cd5a8.png" border="0" loading="lazy"&gt;&lt;/p&gt;</description><category>DevOps</category><category>Web Development</category><guid isPermaLink="false">0b86092d-313b-4ac6-a64e-635fe38196e4</guid><pubDate>2023-08-16T13:34:49Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2023-08-16T13:34:49Z</updated></item><item><title>How to Activate Kendo UI License in Azure Static Web Apps Without Exposing Secret Value</title><link>https://edi.wang/post/2023/1/8/how-to-activate-kendo-ui-license-in-azure-static-web-apps-without-exposing-secret-value</link><description>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;I have an open-source project that uses Kendo UI for Angular. I have my own license to use legally, and I wish end users to bring their own licenses. So, I could not include the license file in my public repository. This typically is not a problem because CI/CD can read license values from repository secret. Kendo UI document also have &lt;a href="https://www.telerik.com/kendo-angular-ui/components/my-license/#toc-github-actions" target="_blank" rel="noopener"&gt;example of setting up license in GitHub Actions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, I am using &lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Azure Static Web Apps&lt;/a&gt;. There is no example of how to activate Kendo license during SWA build process. I &lt;a href="https://996.icu/" target="_blank" rel="noopener"&gt;worked 996&lt;/a&gt; for 1 hour and finally figured out how to activate Kendo UI license during the build process of &lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Azure Static Web Apps&lt;/a&gt; without including the license itself in repository. Let's see how to do it.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;Create Repository Secret&lt;/h3&gt;
&lt;p&gt;In your GitHub repository. Go to Settings, Secrets, Actions, and set the name of the secret to &lt;strong&gt;KENDO_UI_LICENSE&lt;/strong&gt; and paste the contents of the license file as a value.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-b44bd421-29cf-4e64-93b2-99db7801fa29.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;Modify SWA YAML file&lt;/h3&gt;
&lt;p&gt;Open your SWA YAML file, it is located at &lt;code&gt;.\.github\workflows&lt;/code&gt; with different names. For me, it's "azure-static-web-apps-nice-ocean-0afdf4600.yml"&lt;/p&gt;
&lt;p&gt;Find "&lt;strong&gt;Build And Deploy&lt;/strong&gt;" step, it is typically using "&lt;code&gt;Azure/static-web-apps-deploy@v1&lt;/code&gt;".&lt;/p&gt;
&lt;p&gt;Our goal is to run "&lt;code&gt;npx kendo-ui-license activate&lt;/code&gt;" before "&lt;code&gt;ng build&lt;/code&gt;" or "&lt;code&gt;npm run build&lt;/code&gt;". Azure document has a description for &lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/build-configuration?WT.mc_id=AZ-MVP-5002809&amp;amp;tabs=github-actions#custom-build-commands" target="_blank" rel="noopener"&gt;how to use custom build commands&lt;/a&gt;, but it's example only runs one command, it didn't tell you how to run multiple commands. To run commands in sequence, use &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, we will add a new parameter under "&lt;code&gt;with: &lt;/code&gt;" statement.&lt;/p&gt;
&lt;pre class="language-yaml"&gt;&lt;code&gt;app_build_command: "npx kendo-ui-license activate &amp;amp;&amp;amp; npm run build"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add environment variable for referencing Kendo UI license we setup in the first step.&lt;/p&gt;
&lt;pre class="language-yaml"&gt;&lt;code&gt;env:
  KENDO_UI_LICENSE: ${{ secrets.KENDO_UI_LICENSE }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The whole section now looks like this&lt;/p&gt;
&lt;pre class="language-yaml"&gt;&lt;code&gt;- 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_NICE_OCEAN_0AFDF4600 }}
    repo_token: ${{ secrets.GITHUB_TOKEN }}
    action: "upload"
    app_location: "/src/Admin/elf-admin" 
    api_location: ""
    output_location: "dist/elf-admin"
    app_build_command: "npx kendo-ui-license activate &amp;amp;&amp;amp; npm run build"
  env:
    KENDO_UI_LICENSE: ${{ secrets.KENDO_UI_LICENSE }}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Run build&lt;/h3&gt;
&lt;p&gt;Now, run your build again, Kendo UI license will be successfully activated.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8ca7870c-d8fa-4998-9248-34cccb2e8560.png" border="0" loading="lazy"&gt;&lt;/p&gt;</description><category>Web Development</category><category>Microsoft Azure</category><guid isPermaLink="false">950d3c34-1ec0-427f-be54-d8a7feca7e5e</guid><pubDate>2023-01-08T09:59:23Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2023-01-08T09:59:23Z</updated></item><item><title>Deploy to Azure Static Web App from Local Machine without GitHub Action</title><link>https://edi.wang/post/2022/11/17/deploy-to-azure-static-web-app-from-local-machine-without-github-action</link><description>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;When creating &lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Azure Static Web App&lt;/a&gt; in Azure portal. The deployment options did not provide clear instructions for how to deploy without creating a project and CI/CD pipeline on GitHub or Azure DevOps. Many times, we just want to deploy a simple one-time project from local computer. Let's see how to do this.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-a79b8791-9f15-4e14-9651-929c9f56b397.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;Install SWA CLI Tool&lt;/h3&gt;
&lt;p&gt;We need to use Microsoft official tool "Static Web Apps CLI" to deploy from local machine. First, install it from npm.&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;npm install -g @azure/static-web-apps-cli&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Build Your App&lt;/h3&gt;
&lt;p&gt;This step is only required when you are using a front-end framework that requires build, such as Angular.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In my case, I am using Angular. I run "&lt;code&gt;ng build&lt;/code&gt;" and generate the website content files in ./dist/test996. This is the folder to deploy to Azure Static Web App.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5351da08-5fef-4d71-9edc-3f65625efbae.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now, inside `dist` folder. Open a terminal and run:&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;swa deploy ./test996 --env production&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SWA CLI will let you login to Azure, then choose or create the target instance of Azure Static Web App, then zip the website folder and upload to Azure.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-cf0fee21-2bb7-4670-be3d-72fdc66135a5.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;After it is done, we can browse the website in a second.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-953cb406-bf5d-4d6b-ba16-849a45e76f51.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;Bug or feature?&lt;/h3&gt;
&lt;p&gt;There seems to be a bug that&lt;span style="color: #e03e2d;"&gt;&lt;strong&gt; you cannot deploy the current directory to Azure Static Web App&lt;/strong&gt;&lt;/span&gt;. It won't throw errors, but your website will have empty content deployed. So, we must specify a directory for now.&lt;/p&gt;
&lt;p&gt;For example, you have your index.html and other stuff inside `C:\your-project`.&lt;/p&gt;
&lt;p&gt;&lt;span style="color: #e03e2d;"&gt;&lt;strong&gt;DO NOT&lt;/strong&gt;&lt;/span&gt; use this&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;C:\your-project\&amp;gt; swa deploy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move your files to "build" folder and use this&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;C:\your-project\&amp;gt; swa deploy ./build&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2024/12 Update&lt;/h3&gt;
&lt;p&gt;The latest SWA CLI seems to having difficulties finding existing Static Web App instances in Azure. If you meet this problem, you need to workaround it by:&lt;/p&gt;
&lt;p&gt;Run&amp;nbsp;&lt;code&gt;swa --config .&lt;/code&gt; under project root directory and create a config file. Make sure your &lt;code&gt;appName &lt;/code&gt;and the node name under configurations section are the same as your existing app's name in Azure, like this (&lt;strong&gt;testapp1 &lt;/strong&gt;is my existing Azure Static Web App name):&lt;/p&gt;
&lt;pre class="language-json"&gt;&lt;code&gt;{
  "$schema": "https://aka.ms/azure/static-web-apps-cli/schema",
  "configurations": {
    "testapp1": {
      "appLocation": ".",
      "outputLocation": "build",
      "appBuildCommand": "npm run build",
      "run": "npm start",
      "appDevserverUrl": "http://localhost:3000",
      "appName": "testapp1",
      "resourceGroup": "test-rg"
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Login to SWA by specifying subscription id, resource group, and app name, like this:&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;swa login --subscription-id ******* --resource-group test-rg --app-name testapp1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, under the project root directory (where &lt;code&gt;swa-cli.config.json&lt;/code&gt; file exists) run:&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;swa deploy --env production&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;DO NOT&lt;/strong&gt;&amp;nbsp;cd to your &lt;code&gt;./build&lt;/code&gt;&amp;nbsp;or &lt;code&gt;./dist&lt;/code&gt;&amp;nbsp;folder, because the &lt;code&gt;outputLocation &lt;/code&gt;in the &lt;code&gt;swa-cli.config.json&lt;/code&gt; is already telling SWA CLI where your files are. If you cd to &lt;code&gt;./build&lt;/code&gt;&amp;nbsp;or &lt;code&gt;./dist&lt;/code&gt; folder, then SWA CLI won't find your existing app!&amp;nbsp;&lt;/p&gt;</description><category>Web Development</category><category>Microsoft Azure</category><guid isPermaLink="false">cd0908cc-c27a-4ce2-9d2b-e033327ec9ee</guid><pubDate>2022-11-17T08:29:50Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2022-11-17T08:29:50Z</updated></item><item><title>Make NPM Install with Legacy Peer Deps in Azure Static Web Apps Builds</title><link>https://edi.wang/post/2022/11/16/make-npm-install-with-legacy-peer-deps-in-azure-static-web-apps-builds</link><description>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;After upgrading a JS library, GitHub Action for build and deploy &lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Azure Static Web Apps&lt;/a&gt; blows up sky high. According to the error message. &lt;code&gt;npm install&lt;/code&gt; command need to run with --legacy-peer-deps. However, there is no step of &lt;code&gt;npm install&lt;/code&gt; in Azure Static Web Apps's yml file. How can we tell npm to use legacy peer deps?&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-ed7dbb45-35ed-45cc-b270-6801117b28de.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Although there is no way to change the npm step details for Oryx build engine, there is an environment variable to control &lt;code&gt;npm install&lt;/code&gt; behaviour.&lt;/p&gt;
&lt;p&gt;Set &lt;code&gt;NPM_CONFIG_LEGACY_PEER_DEPS &lt;/code&gt;to &lt;code&gt;true &lt;/code&gt;has the same effect with &lt;code&gt;npm install --legacy-peer-deps&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To set environment variables in yml, we can use &lt;code&gt;env:&lt;/code&gt; keyword. In this case, it will be&lt;/p&gt;
&lt;pre class="language-yaml"&gt;&lt;code&gt;env:
  NPM_CONFIG_LEGACY_PEER_DEPS: true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full build and deploy job will now look like this&lt;/p&gt;
&lt;pre class="language-yaml"&gt;&lt;code&gt;jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' &amp;amp;&amp;amp; github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true
      - 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_NICE_OCEAN_0AFDF4600 }}
          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: "/src/Admin/elf-admin" # App source code path
          api_location: "" # Api source code path - optional
          output_location: "dist/elf-admin" # Built app content directory - optional
          ###### End of Repository/Build Configurations ######
        env:
          NPM_CONFIG_LEGACY_PEER_DEPS: true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we can have a happy build!&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-7dc8f35b-b314-44e4-8265-139d0a2e18c5.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href="https://github.com/Azure/static-web-apps/issues/989"&gt;https://github.com/Azure/static-web-apps/issues/989&lt;/a&gt;&lt;/p&gt;</description><category>Web Development</category><category>Microsoft Azure</category><guid isPermaLink="false">5567398a-5c22-4f96-9732-823adf0b0867</guid><pubDate>2022-11-16T06:20:29Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2022-11-16T06:20:29Z</updated></item><item><title>How to Add Parameters for 'npm install' in Azure DevOps</title><link>https://edi.wang/post/2022/7/7/how-to-add-parameters-for-npm-install-in-azure-devops</link><description>&lt;p&gt;Recently my Azure DevOps build pipeline failed because Azure is using npm 8.x, which will have issue for angular projects when running `&lt;code&gt;npm install&lt;/code&gt;`.&amp;nbsp; This is a known issue which can be solved by using `&lt;code&gt;--legacy-peer-deps&lt;/code&gt;` or `&lt;code&gt;--force&lt;/code&gt;` for the `&lt;code&gt;npm install&lt;/code&gt;` command. However, Azure DevOps's npm task does not take parameters by default. Let's see how we can use these parameters in Azure DevOps.&lt;/p&gt;
&lt;h2&gt;Old YAML file&lt;/h2&gt;
&lt;hr&gt;
&lt;pre class="language-yaml"&gt;&lt;code&gt;  - task: Npm@1
    displayName: 'npm install'
    inputs:
      workingDir: '$(System.DefaultWorkingDirectory)/src/Web/work996'
      command: 'install'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It produces error:&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-d12a29c7-4d19-470e-b952-818b83fdb44f.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Custom Command&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;To solve this issue. We need to use custom command for the &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/package/npm?view=azure-devops&amp;amp;WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;npm task&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="language-yaml"&gt;&lt;code&gt;  - task: Npm@1
    displayName: 'npm install with legacy-peer-deps'
    inputs:
      workingDir: '$(System.DefaultWorkingDirectory)/src/Web/work996'
      command: 'custom'
      customCommand: 'install --legacy-peer-deps'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the build will work fine.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-da7b709d-525c-4acf-80fa-3f03d0b9f16c.png" border="0" loading="lazy"&gt;&lt;/p&gt;</description><category>Web Development</category><category>Microsoft Azure</category><guid isPermaLink="false">5026a3d2-15a8-4cca-a19d-a5cdf1a1ad83</guid><pubDate>2022-07-07T05:25:31Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2022-07-07T05:25:31Z</updated></item><item><title>How to Specify Node.js Version When Building Azure Static Web App</title><link>https://edi.wang/post/2022/1/27/how-to-specify-nodejs-version-when-building-azure-static-web-app</link><description>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Azure Static Web App&lt;/a&gt; creates a GitHub Action that uses &lt;a href="https://github.com/microsoft/Oryx" target="_blank" rel="noopener"&gt;Oryx&lt;/a&gt; to build your web app. I have a React App that uses node.js to build. However, the default version of node Oryx is using is currently 14.x. I would like to use version 16.x for building my App.&lt;/p&gt;
&lt;p&gt;In the build log of GitHub Action, you can see node version 14.x is being used.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-ce39db2a-c901-4811-bdb4-05654d1e15ba.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Unlike what you usually do with YAML build definitions. There's no parameter for node versions in &lt;a href="https://github.com/Azure/static-web-apps-deploy" target="_blank" rel="noopener"&gt;Azure/static-web-apps-deploy@v1&lt;/a&gt; action currently.&lt;/p&gt;
&lt;p&gt;We must edit our project's &lt;code&gt;package.json&lt;/code&gt; to specify a node version. Add the following code into your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class="language-json"&gt;&lt;code&gt;"engines": {
    "node": "&amp;gt;=16.0.0"
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-1b340cf2-97b3-4cf0-a1c1-a7e6b928638b.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now, the build will use Node.js 16.x.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-faa05d73-3449-4731-b3f7-afbd1fcf77e8.png" border="0" loading="lazy"&gt;&lt;/p&gt;</description><category>Web Development</category><category>Microsoft Azure</category><guid isPermaLink="false">d250f557-18c1-4595-8f2b-b8172fc8af28</guid><pubDate>2022-01-27T04:15:05Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2022-01-27T04:15:05Z</updated></item><item><title>RMB capitalization converter in Blazor WASM</title><link>https://edi.wang/post/2021/3/1/blazor-wasm-rmb-capitalize</link><description>&lt;p&gt;.NET 5 正式发布已经有一段时间了，其中 Blazor 技术是该版本的亮点之一。作为微软技术的被坑者，年少的我曾经以为 SilverLight 能血虐 Flash，Zune 能团灭 iPod，WP 能吊打 iPhone，UWP 能统一全平台&amp;hellip;&amp;hellip; 可是后&amp;hellip;&amp;hellip; 最终步入大龄程序员的我发现，只有陪伴了我将近 20 年的 ASP.NET 还没有完蛋。于是我这两天花了点时间，尝试将我的一个 UWP 小工具用 Blazor 重写，分享给大家。&lt;/p&gt;
&lt;h2&gt;无法抢救的 UWP&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src="/image/img-cf224fc4-e8b3-434a-8364-f8144703ad2b.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;&lt;strong&gt;人民币大写转换器&lt;/strong&gt;&amp;rdquo; 是我年少无知时开发的小工具之一，它的主要功能有：&lt;/p&gt;
&lt;p&gt;- 将数字金额转化为大写中文&lt;/p&gt;
&lt;p&gt;- 复制结果&lt;/p&gt;
&lt;p&gt;- 使用中文语音朗读结果&lt;/p&gt;
&lt;p&gt;- 显示参照表&lt;/p&gt;
&lt;p&gt;可惜 UWP 不论是充满 Bug 的 SDK，Runtime，还是微软的龟速更新与混乱的规划，都已经无可救药了，是时候给应用找个新家了。&lt;/p&gt;
&lt;h2&gt;Blazor&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Blazor 是 .NET Core 时代微软推出的用于 Web 应用开发的新框架，它可以运行在服务器端，也可使用 WASM 运行在客户端，即浏览器中。&lt;/p&gt;
&lt;p&gt;对我来说，这个技术最吸引人的，就是 WASM。像我这种已经30多岁，学不了新东西的 .NET 程序员，根本搞不定 Angular、Vue、React 这些花里胡哨的框架，而 Blazor WASM 是把 .NET 运行时搬到了浏览器端，和 SilverLight 类似，但这次是以WASM标准的形式运行，不需要安装插件，并且也能跨平台。&lt;/p&gt;
&lt;p&gt;于是我可以继续使用熟悉的 .NET 和 C# 开发 SPA Web 应用。更重要的是，既然是原汁原味的 .NET，就可以很方便的重用以前的代码，以及现成的成千上万个 NuGet 包，而不用像一个新发明的框架那样从0开始积累生态。&lt;/p&gt;
&lt;p&gt;我 996 了 2 小时，成功将&amp;ldquo;人民币大写转换器&amp;rdquo;重写到 Blazor WASM，效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-61f3aa6b-3899-4399-8860-2525b2855e32.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://rmbcap.blazor.ediwang.dev/" target="_blank" rel="noopener"&gt;Demo&lt;/a&gt; | &lt;a href="https://github.com/EdiWang/RMBCapitalization-Blazor" target="_blank" rel="noopener"&gt;源代码&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;由于篇幅关系，本文不叙述重写的每处细节，只参数关键点。其他细节大家可到 GitHub 阅读源代码了解。&lt;/p&gt;
&lt;h4&gt;创建 Blazor WASM 工程&lt;/h4&gt;
&lt;hr&gt;
&lt;p&gt;我们可以使用 Visual Studio 2019 创建 Blazor WASM 工程。&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-0dd03f4c-a240-4942-8636-4f11ad979340.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;选择 &lt;strong&gt;Blazor WebAssembly App&lt;/strong&gt; 就可以了&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-537af2b2-7ba0-4555-a725-29f2634e7341.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;工程结构&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src="/image/img-641a0a55-2913-420c-87f9-9327feef03c1.png" border="0" alt="" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;一个 Blazor WASM 项目的典型结构如上图。&lt;code&gt;Program.cs&lt;/code&gt; 包含应用如何启动与承载的逻辑。&lt;/p&gt;
&lt;p&gt;wwwroot 中的文件为纯 HTML/CSS/JS 文件，不包含.NET的逻辑。其中 &lt;code&gt;index.html&lt;/code&gt; 为承载应用的默认页面，和 Angular 等 SPA 框架非常类似，它将会把应用页面加载到 &lt;code&gt;&amp;lt;div id="app"&amp;gt;&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MainLayout.razor&lt;/code&gt; 是整个应用的布局页面，如果你有多个页面和视图，那么通常这里会放 Header，Footer 等内容。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Index.razor&lt;/code&gt; 为应用的默认主页。我这个应用只有一个页面，所以一切逻辑都在这里实现就可以了。&lt;/p&gt;
&lt;h3&gt;可重用的代码&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;人民币大写的转换类与框架和平台无关，因此完全可以直接复制到Blazor工程里用，即 RMBConverter.cs。&lt;/p&gt;
&lt;p&gt;UWP 应用的视图通常采用 MVVM 模式开发，这些逻辑可以很方便的迁移到 Blazor。&lt;/p&gt;
&lt;h4&gt;Index.razor&lt;/h4&gt;
&lt;p&gt;就像写 MVC 的 cshtml 一样，使用熟悉的 Razor 语法，就能绑定数据和事件。&lt;/p&gt;
&lt;p&gt;对于 input，简单的双向数据绑定可以直接用 &lt;code&gt;@bind="属性"&lt;/code&gt; 实现。但我这个应用里要求用户一边输入金额一边进行实时计算，所以只能写成事件绑定。&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;h3&amp;gt; @Result &amp;lt;/h3&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;input type="text" @bind-value="InputAmount" @bind-value:event="oninput" /&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;button @onclick="CopyResult"&amp;gt;复制&amp;lt;/button&amp;gt;
        &amp;lt;button @onclick="ReadAloud"&amp;gt;朗读&amp;lt;/button&amp;gt;
        &amp;lt;button @onclick="Clear"&amp;gt;清除&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于有参数的事件处理函数，要注意它和正常 C# 写事件一样，是个 Lambda 表达式，如果放在循环里的话要注意变量的值是在循环里被修改。&lt;/p&gt;
&lt;p&gt;下面的代码必须使用 &lt;code&gt;var num = i&lt;/code&gt; 来存储 i 的值，如果直接使用 &lt;code&gt;KeyPadClicked(i)&lt;/code&gt;，那么 i 一定永远等于1。&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;div class="row"&amp;gt;
    @for (int i = 1; i &amp;lt;= 9; i++)
    {
        var num = i;
        &amp;lt;div class="col-4"&amp;gt;
           &amp;lt;button class="btn btn-light key" @onclick="() =&amp;gt; KeyPadClicked(num.ToString())"&amp;gt;@i&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    }
&amp;lt;/div&amp;gt;
&amp;lt;div class="row"&amp;gt;
    &amp;lt;div class="col-8"&amp;gt;
        &amp;lt;button class="btn btn-light key" @onclick='() =&amp;gt; KeyPadClicked("0")'&amp;gt;0&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class="col"&amp;gt;
        &amp;lt;button class="btn btn-light key" @onclick='() =&amp;gt; KeyPadClicked(".")'&amp;gt;.&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和 XAML 的 MVVM 以及 Angular 稍有不同的是，处理逻辑不是在 code behind 文件里写的，而是可以在 razor 页面本身写。你也可以自己新建 Index.razor.cs 作为部分类来达到HTML与C#相分离的目的。&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;@code {
    private string _inputAmount;
    public string InputAmount
    {
        get =&amp;gt; _inputAmount;
        set
        {
            _inputAmount = value;

            // 验证和处理逻辑...
            Result = string.IsNullOrWhiteSpace(_inputAmount) ?
                    string.Empty :
                    RMBConverter.GetCapitalizedRmb(InputAmount);
        }
    }

    public string Result { get; set; }

    private async Task CopyResult()
    {
        // ...
    }

    private async Task ReadAloud()
    {
        // ...
    }

    private void Clear()
    {
        InputAmount = string.Empty;
    }

    private void KeyPadClicked(string value)
    {
        InputAmount += value switch
        {
            "0" when InputAmount != "0" =&amp;gt; 0,
            "." when !InputAmount.Contains(".") =&amp;gt; ".",
            _ =&amp;gt; value
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;需要重新实现的功能&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;在 UWP 中，复制可以调用 Windows 的 Clipboard API 来完成。但是在浏览器端，没有 Windows 的 API，Blazor 也没有封装剪切板 API，因此我们只能借用 JS 来完成。&lt;/p&gt;
&lt;h4&gt;index.html&lt;/h4&gt;
&lt;pre class="language-javascript"&gt;&lt;code&gt;window.clipboardCopy = {
    copyText: function (text) {
        navigator.clipboard.writeText(text).then(function () {
            console.log(text);
        })
            .catch(function (error) {
                alert(error);
            });
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Index.razor&lt;/h4&gt;
&lt;p&gt;使用依赖注入，引入 &lt;code&gt;IJSRuntime &lt;/code&gt;的实例。这是 Blazor 用于和 JavaScript 交互的接口。&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;@inject IJSRuntime JavaScriptRuntime&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以调用 JS 进行复制&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;private async Task CopyResult()
{
    if (!string.IsNullOrWhiteSpace(Result))
    {
        await JavaScriptRuntime.InvokeVoidAsync("clipboardCopy.copyText", Result);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类似的，在 UWP 里，朗读使用的是 Windows 的 SpeechSynthesizer API。浏览器端也有个类似的 &lt;code&gt;SpeechSynthesisUtterance&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;index.html&lt;/h4&gt;
&lt;pre class="language-javascript"&gt;&lt;code&gt;window.readAloud = {
    readText: function (text) {
        let utterance = new SpeechSynthesisUtterance(text);
        utterance.lang = 'zh-CN';
        speechSynthesis.speak(utterance);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Index.razor&lt;/h4&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;private async Task ReadAloud()
{
    if (!string.IsNullOrWhiteSpace(Result))
    {
        await JavaScriptRuntime.InvokeVoidAsync("readAloud.readText", Result);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;本地应用&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;目前 Blazor WASM 还没有本地应用的官方支持，必须打开浏览器才能使用，现有的版本只能使用PWA完成一部分本地应用化操作。但在今年即将发布的 .NET 6 版本中，Blazor 会迎来官方最纯正的本地应用支持。只要不出自 SilverLight、Zune、WP、WinRT、UWP 团队之手，就不会被坑！&lt;/p&gt;
&lt;h2&gt;现存的问题&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Blazor WASM 虽然看着香，但目前有一些痛点还有待解决。&lt;/p&gt;
&lt;p&gt;首先，框架本身的体积依然较大，由于众所周知而不可描述的原因，如果服务器部署在海外，那么我国网络加载 Blazor 应用会比较慢。&lt;/p&gt;
&lt;p&gt;另外，不是所有版本的浏览器都可以跑 WASM，尤其是手机端。Blazor 的兼容性相比 Angular，Vue，React 等，还有些差距。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;使用 Blazor WASM 开发 Web 应用能够让 .NET 程序员充分利用既有的知识和技能快速上手，结合 Web 的强大生态 与 .NET 的高效生产力，成就不凡。而 UWP 只能哭晕在厕所也没人听见&amp;hellip;&amp;hellip;&lt;/p&gt;</description><category>Web Development</category><category>.NET</category><guid isPermaLink="false">bea172a6-6c09-4d56-9d91-1fe8188b11de</guid><pubDate>2021-03-01T06:19:39Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2021-03-01T06:19:39Z</updated></item><item><title>JQuery搞定SyntaxHighlighter v3.x长代码自动换行</title><link>https://edi.wang/post/2012/9/12/jquery-fix-line-wrap-in-syntaxhighlighter-v3</link><description>&lt;p&gt;长代码换行是SyntaxHighlighter3.0版本之前的功能，但3.0之后就木了，本人认为主要猿因是gutter（代码行号指示器）的换行难做。因为3.x版是将代码行号和代码部分分开生成HTML的，猿来的老版本是在一起的。这样做是便于直接复制代码，而不会复制到行号上去。&lt;/p&gt;
&lt;p&gt;很多人都在想办法让它实现换行，但都卡在代码行号的问题上。我关注这个问题已经差不多一年了，今天终于有了个比较漂亮的解决办法。&lt;/p&gt;
&lt;p&gt;首先，强制给代码部分换行的CSS还是要写的：&lt;/p&gt;
&lt;pre class="brush: css;fontsize: 100; first-line: 1; "&gt;body .syntaxhighlighter .line
{
    white-space: pre-wrap !important;
}&lt;/pre&gt;
&lt;p&gt;这段CSS换行的前提是，pre标签的外边一定要有一个固定宽度的容器对象。比如一个600px宽度的div就可以。&lt;/p&gt;
&lt;p&gt;然后，我们需要一段JQuery，用来计算被换行的代码显示在页面上的高度，然后赋值给它的行标。这段代码是国外一个论坛上找的，但我测试的时候发现它有点小问题，我的环境是JQuery1.7+Firefox15，我顺便做了一个修正，全部代码如下：&lt;/p&gt;
&lt;pre class="brush: jscript;fontsize: 100; first-line: 1; "&gt;$(function () {
    // Line wrap back
    var shLineWrap = function () {
        $('.syntaxhighlighter').each(function () {
            // Fetch
            var $sh = $(this),
                $gutter = $sh.find('td.gutter'),
                $code = $sh.find('td.code')
                ;
            // Cycle through lines
            $gutter.children('.line').each(function (i) {
                // Fetch
                var $gutterLine = $(this),
                    $codeLine = $code.find('.line:nth-child(' + (i + 1) + ')')
                    ;
                //alert($gutterLine);
                // Fetch height
                var height = $codeLine.height() || 0;
                if (!height) {
                    height = 'auto';
                }
                else {
                    height = height += 'px';
                    //alert(height);
                }
                // Copy height over
                $gutterLine.attr('style', 'height: ' + height + ' !important'); // fix by Edi, for JQuery 1.7+ under Firefox 15.0
                console.debug($gutterLine.height(), height, $gutterLine.text(), $codeLine);
            });
        });
    };

    // Line wrap back when syntax highlighter has done it's stuff
    var shLineWrapWhenReady = function () {
        if ($('.syntaxhighlighter').length === 0) {
            setTimeout(shLineWrapWhenReady, 10);
        }
        else {
            shLineWrap();
        }
    };

    // Fire
    shLineWrapWhenReady();
});&lt;/pre&gt;
&lt;p&gt;现在，行号的高度就能和代码的高度保持一致了。&lt;/p&gt;</description><category>Web Development</category><guid isPermaLink="false">74f35952-b8f9-473c-91dd-b4691dcfc4d3</guid><pubDate>2012-09-12T06:12:30Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2012-09-12T06:12:30Z</updated></item><item><title>JQuery文本框自动完成，通过AJAX调用ASP.NET WebService</title><link>https://edi.wang/post/2012/5/23/jquery-auto-complete-using-ajax-webservice-in-aspnet</link><description>&lt;p&gt;最近在搞博客搜索框的自动完成功能，用的是JQuery UI里的autocomplete插件。插件的数据源是用AJAX调用一个WebService，网站后台有个标签库，WebService返回的是符合条件的标签。最终效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-d09806b9-de29-4fb0-b84f-da796b762346.png" alt="" width="296" height="165" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我们先来看WebService的代码，方法很简单：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="brush: csharp;fontsize: 100; first-line: 1; "&gt;[WebMethod]
public List&amp;lt;EdiBlog.Core.DataModel.TagCloud&amp;gt; GetAllTagsForAutoComplete(string tagName)
{
    return optTag.GetModelList().Where(p =&amp;gt; p.Name.ToLower().StartsWith(tagName.ToLower())).ToList();
}&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;这个方法的作用是根据输入的内容，检索所有以tagName开头的标签。我没有返回全部标签，是因为autocomplete插件的默认行为不符合我的需求。在默认情况下，如果我输入&amp;ldquo;as&amp;rdquo;，则"ascx"和"javascript"都会被显示。所以我做的这个WebService进行了一定的处理，只返回StartsWith关键词的标签。&lt;/p&gt;
&lt;p&gt;这个TagCloud类型很简单，仅仅只有2个属性。一个是Name，表示名称，另一个是Count，表示出现次数。这里我们只需要Name。&lt;/p&gt;
&lt;pre class="brush: csharp;fontsize: 100; first-line: 1; "&gt;public class TagCloud
{
    public string Name { get; set; }
    public int Count { get; set; }
}&lt;/pre&gt;
&lt;p&gt;另外，别忘了把&amp;ldquo;[System.Web.Script.Services.&lt;span style="color: #2b91af;"&gt;ScriptService&lt;/span&gt;] &amp;rdquo;取消注释。不然脚本调用不了WebService。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我们再来看看脚本和HTML部分。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;需要引用的除了JQuery库，还要JQuery UI库，我用的版本是&amp;ldquo;jquery-ui-1.8.16.custom.min.js&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;HTML代码（ASPX）：&lt;/p&gt;
&lt;pre class="brush: xml;fontsize: 100; first-line: 1; "&gt;&amp;lt;asp:TextBox ID="txtSearch" CssClass="searchtext" MaxLength="30" runat="server" ToolTip="Search"&amp;gt;&amp;lt;/asp:TextBox&amp;gt;&lt;/pre&gt;
&lt;p&gt;它最后表现在网页上，是一个input type="text"的搜索框。&lt;/p&gt;
&lt;p&gt;JQuery代码：&lt;/p&gt;
&lt;pre class="brush: jscript;fontsize: 100; first-line: 1; "&gt;$(function () {
    $(".searchtext").autocomplete({
        source: function (request, response) {
            $.ajax({
                url: "/API/BlogService.asmx/GetAllTagsForAutoComplete",
                data: "{ 'tagName': '" + request.term + "'}",
                dataType: "json",
                type: "POST",
                contentType: "application/json; charset=utf-8",
                dataFilter: function (data) {
                    return data;
                },
                success: function (data) {
                    response($.map(data.d, function (item) {
                        return {
                            value: item.Name
                        }
                    }))
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert(textStatus + ": " + errorThrown);
                }
            })
        },
        minLength: 1
    });
});&lt;/pre&gt;
&lt;p&gt;这段代码里，&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13px; color: black; background: none repeat scroll 0% 0% white;"&gt;data:&amp;nbsp;&lt;span style="color: maroon;"&gt;"{&amp;nbsp;'tagName':&amp;nbsp;'"&lt;/span&gt;&amp;nbsp;+&amp;nbsp;request.term&amp;nbsp;+&amp;nbsp;&lt;span style="color: maroon;"&gt;"'}"&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;是给GetAllTagsForAutoComplete方法传参数，参数是用户输入的内容。&lt;/p&gt;
&lt;pre class="brush: jscript;fontsize: 100; first-line: 1; "&gt;minLength: 1&lt;/pre&gt;
&lt;p&gt;表示至少输入一个字符时，开始自动完成。&lt;/p&gt;
&lt;pre class="brush: jscript;fontsize: 100; first-line: 1; "&gt;value: item.Name&lt;/pre&gt;
&lt;p&gt;是访问Name属性，这个Name就是TagCloud类型里的Name，因为被解析成了JSON，我们可以直接用"."来访问，不用["Name"]去访问了。&lt;/p&gt;
&lt;p&gt;如果你没有引用JQuery UI的CSS库，可以自己做一些样式，比如本人博客的：&lt;/p&gt;
&lt;pre class="brush: css;fontsize: 100; first-line: 1; "&gt;ul.ui-autocomplete
{
    border: 1px solid #DDD;
    background-color: #FFF;
    width: 280px;
}

ul.ui-autocomplete li a
{
    padding: 5px;
    display: block;
}

.ui-state-hover
{
    background-color: #D8DFEA;
}&lt;/pre&gt;</description><category>Web Development</category><category>.NET</category><guid isPermaLink="false">664391cc-0e72-4e08-94a7-3a4068dbd788</guid><pubDate>2012-05-23T01:41:32Z</pubDate><updated xmlns="http://www.w3.org/2005/Atom">2012-05-23T01:41:32Z</updated></item></channel></rss>