<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: gjcampbell</title>
    <description>The latest articles on DEV Community by gjcampbell (@gjcampbell).</description>
    <link>https://dev.to/gjcampbell</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F231236%2Fb82f98de-6b68-4222-a155-1ed0294b8349.jpeg</url>
      <title>DEV Community: gjcampbell</title>
      <link>https://dev.to/gjcampbell</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gjcampbell"/>
    <language>en</language>
    <item>
      <title>Running Azure Functions in IIS</title>
      <dc:creator>gjcampbell</dc:creator>
      <pubDate>Mon, 20 Jul 2020 02:58:12 +0000</pubDate>
      <link>https://dev.to/gjcampbell/running-azure-functions-in-iis-32a</link>
      <guid>https://dev.to/gjcampbell/running-azure-functions-in-iis-32a</guid>
      <description>&lt;p&gt;Have you ever wanted to run Azure Functions in IIS? No? "Why would you want to run serverless functions in IIS," you ask.&lt;/p&gt;

&lt;p&gt;I won't try to answer &lt;em&gt;why&lt;/em&gt;, but I can tell you &lt;em&gt;how&lt;/em&gt;. I recently needed to do this and found a void where info should be, so this post is to fill that void.&lt;/p&gt;

&lt;p&gt;In Azure, functions are hosted in IIS. (Weird right? Like, I assumed it was something sophisticated and new, not just some duct tapey IIS plugin thing.) I want to find out how much of a function app we can get functioning on-prem with IIS. Yeah, we could just run azure function core tools, but what would we learn from following &lt;em&gt;that&lt;/em&gt; well-documented process?&lt;/p&gt;

&lt;h2&gt;
  
  
  🎪 Basic IIS setup
&lt;/h2&gt;

&lt;p&gt;Let's add a plain, empty site, and bind it to a really good domain.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frwoyeikv5hjz6lafc2ut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frwoyeikv5hjz6lafc2ut.png" alt="Plain empty site in IIS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we used a really good domain, we'll need to update our hosts file.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgi2s9477fno07mwcqggj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgi2s9477fno07mwcqggj.png" alt="Hosts file entry for no good reason"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's quick test to prove that IIS is set up OK.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fiaq7zfm1zaw7a4qcesnr.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fiaq7zfm1zaw7a4qcesnr.PNG" alt="Sweet new web page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cool&lt;/em&gt; so we have a site set up in IIS, and we can deploy a function app to it. Next we'll make a function app, give it a bunch of trigger types, just to see which triggers work in this janky setup. &lt;/p&gt;
&lt;h2&gt;
  
  
  🧪 A Guinea Pig Function App
&lt;/h2&gt;

&lt;p&gt;Here's a link to the source code.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/gjcampbell" rel="noopener noreferrer"&gt;
        gjcampbell
      &lt;/a&gt; / &lt;a href="https://github.com/gjcampbell/iis-azfunc-test" rel="noopener noreferrer"&gt;
        iis-azfunc-test
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Pretty basic, there's are 4 triggers, and each one just logs. There's a timer, HTTP, queue, and blob trigger. It's logging to a file, so that when it's silently running in IIS, I can see the function app is doing stuff.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestHttp&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;TestHttp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HttpTrigger ran"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Perfect! 🙃"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestQueue&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestQueue&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;QueueTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"iis-q-test"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"QueueTrigger ran value: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestStorage&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestStorage&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;BlobTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"iis-blob-test/{name}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"BlobTrigger ran file name: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, size: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestTimer&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestTimer&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;TimerTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"*/20 * * * * *"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="n"&gt;TimerInfo&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"TimerTrigger ran"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I have confirmed that, running from Visual Studio, the function app works as expected. All triggers work and log as configured.&lt;/p&gt;

&lt;p&gt;Next from Visual Studio, I'll publish locally to the web site folder. 💥&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgqk7rhfi436c8lrvlt6x.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgqk7rhfi436c8lrvlt6x.PNG" alt="func app site looking broke"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yeah, of course that doesn't work. I haven't configured IIS to know what to do with a function app.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤫 Azure Function IIS Secret Sauce
&lt;/h2&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/Azure/azure-functions-host" rel="noopener noreferrer"&gt;this cool github page&lt;/a&gt;. Azure Functions Host has releases like &lt;a href="https://github.com/Azure/azure-functions-host/releases/tag/v3.0.14191" rel="noopener noreferrer"&gt;this one&lt;/a&gt;, 3.0.BLAH which we can download and use with IIS.&lt;/p&gt;

&lt;p&gt;Download and unzip the Functions.3.0.BLAH.zip somewhere. Next make a web.config file where your function app is published. In the web.config, configure IIS to run aspNetCore module for all paths and verbs, and set the processPath for aspNetCore to the path of Function Host's Microsoft.Azure.WebJobs.Script.WebHost.exe file.&lt;/p&gt;

&lt;p&gt;Like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;system.webServer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;handlers&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;remove&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"aspNetCore"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;add&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"aspNetCore"&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;verb=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;modules=&lt;/span&gt;&lt;span class="s"&gt;"AspNetCoreModuleV2"&lt;/span&gt; &lt;span class="na"&gt;resourceType=&lt;/span&gt;&lt;span class="s"&gt;"Unspecified"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/handlers&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;aspNetCore&lt;/span&gt; &lt;span class="na"&gt;processPath=&lt;/span&gt;&lt;span class="s"&gt;"C:\hosting\Functions.3.0.14191\3.0.14191\64bit\Microsoft.Azure.WebJobs.Script.WebHost.exe"&lt;/span&gt; &lt;span class="na"&gt;hostingModel=&lt;/span&gt;&lt;span class="s"&gt;"InProcess"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;environmentVariables&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;environmentVariable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"AzureWebJobsStorage"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"UseDevelopmentStorage=true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/environmentVariables&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/aspNetCore&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/system.webServer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now what do we get?&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwx3b4c0nu6nkhwpj2zc5.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwx3b4c0nu6nkhwpj2zc5.PNG" alt="For real tho?"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Weird, right? It's that default page that you get in azure. What about when we hit the HttpTrigger route?&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcmsbzhosn03kog1uvf1n.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcmsbzhosn03kog1uvf1n.PNG" alt="Yeah"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So HTTP works of course. What about the other triggers? The log says&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;2020-07-19 21:28:00.0158|INFO|IIS.FuncTest.Tests|TimerTrigger ran&lt;br&gt;
2020-07-19 21:28:20.0162|INFO|IIS.FuncTest.Tests|TimerTrigger ran&lt;br&gt;
2020-07-19 21:28:36.0969|INFO|IIS.FuncTest.Tests|QueueTrigger ran value: adsf&lt;br&gt;
2020-07-19 21:28:40.0181|INFO|IIS.FuncTest.Tests|TimerTrigger ran&lt;br&gt;
2020-07-19 21:28:43.7516|INFO|IIS.FuncTest.Tests|HttpTrigger ran&lt;br&gt;
2020-07-19 21:28:44.1831|INFO|IIS.FuncTest.Tests|BlobTrigger ran file name: 2019_TaxReturn.pdf, size: 637435&lt;br&gt;
2020-07-19 21:29:00.0530|INFO|IIS.FuncTest.Tests|TimerTrigger ran&lt;br&gt;
2020-07-19 21:29:20.0069|INFO|IIS.FuncTest.Tests|TimerTrigger ran&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  🌻 Summary&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Yeah, you can run azure functions in IIS and the vanilla triggers work just fine, and there's really just 2 steps to get it working. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the functions runtime&lt;/li&gt;
&lt;li&gt;Point to it from your web.config&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'd love to see someone get durable functions working.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>serverless</category>
    </item>
    <item>
      <title>I Wrote a Tree Control for Enormous Trees</title>
      <dc:creator>gjcampbell</dc:creator>
      <pubDate>Sun, 12 Jan 2020 05:36:41 +0000</pubDate>
      <link>https://dev.to/gjcampbell/i-wrote-a-tree-control-for-enormous-trees-flo</link>
      <guid>https://dev.to/gjcampbell/i-wrote-a-tree-control-for-enormous-trees-flo</guid>
      <description>&lt;p&gt;A company I was contracting for had a legitimate need for a web-based (angular) tree control that could handle thousands of nodes. I looked around a ton and found nothing really good enough, so I made &lt;a href="https://github.com/gjcampbell/ooffice/tree/master/projects/of-tree"&gt;this one&lt;/a&gt; and it handles tens of thousands really well 👌&lt;/p&gt;

&lt;h2&gt;
  
  
  An Uncommon Problem, I Admit
&lt;/h2&gt;

&lt;p&gt;Most tree controls only need to support a pretty small data set. Like, 100 items max, and it becomes bad UX after that, right? I agree, but after having seen this same problem, huge trees, come up for the third time (three different companies, three different business domains), I wonder if it's not so rare after all.&lt;/p&gt;

&lt;p&gt;Yeah, most trees are small, but it happens occasionally, that devs are expected to implement a tree control to handle an amount of data that exposes the tree control's inefficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smells Like Bad UX, Right?
&lt;/h2&gt;

&lt;p&gt;In most software, a UI featuring an enormous tree of data would be cruel or repellent 🦨 to users. However, trees are ubiquitous, and in the right context, a tree is such a concise data visualization, and what a tree control may lack in elegance is redeemed by its familiarity to users.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's Challenging Though
&lt;/h2&gt;

&lt;p&gt;Regardless of whether it's right to use a tree for tons of data, it's a tough thing to implement. The file system explorer and your IDE have very well-written tree controls, but try to find a tree implementation as good as those. You'll find innumerable reusable components on github, packed with features, easy to drop in, but absolutely crippled by a large data set. They take forever to load or make interaction laggy and delayed.&lt;/p&gt;

&lt;p&gt;I've wondered why efficient tree controls are so hard to find. I assume it's because being super-efficient is rarely necessary for a tree control. And, the time needed to create an efficient reusable tree control is not justifiable, because demand for one is low. 🤷‍♂️&lt;/p&gt;

&lt;h2&gt;
  
  
  Have I Done it Right?
&lt;/h2&gt;

&lt;p&gt;I think my implementation is correct. It responds quickly to all interactions with a large data set loaded. It handles a huge amount of data, it has all the typical tree features built in, and has no limitations preventing extensibility into more exotic features. &lt;/p&gt;

&lt;h2&gt;
  
  
  It Works Like This
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Virtualization - (not to be confused with virtual dom) To render a very long list of nodes, &lt;a href="https://material.angular.io/cdk/scrolling/overview"&gt;virtual scrolling&lt;/a&gt; is used. In a scrollable container, only the items scrolled into view are visible. Outside of the visible area, is a huge blank space. So, even though the tree may have 10's of thousands of items expanded out, only a handful are rendered in the DOM. Angular has a built in CDK component for virtual scrolling. (It's not great though)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flattened DOM - Typical tree implementations nest DOM elements. That makes it super easy to implement expand/collapse, indentation, and data binding. However, nesting works against virtualization. Rendering only the visible items and not their parent nodes is complicated and inefficient. So, to accommodate virtualization, all items in the tree are DOM-siblings, regardless of the relationships of the data they represent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metadata Preserved - Fine, the DOM is flat in the tree, but the tree's look and feel must remain. Items have to be indented, their children collapsible, etc. So, although the DOM is flat and although it is bound to flattened view of the data, all the necessary relationship data of the hierarchy is preserved, depth, ancestry, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Short Version
&lt;/h2&gt;

&lt;p&gt;The recipe 🎂 for an efficient tree control supporting tons of data is pretty simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preserve metadata (so that the tree can look right even though flat)&lt;/li&gt;
&lt;li&gt;Flatten (so that you can virtual scroll)&lt;/li&gt;
&lt;li&gt;Virtual scroll (so that you can handle a huge list)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;🙏Check out &lt;a href="https://oofficestorage.z19.web.core.windows.net/"&gt;the demo&lt;/a&gt; and the &lt;a href="https://github.com/gjcampbell/ooffice/tree/master/projects/of-tree"&gt;source code&lt;/a&gt;, and let me know what you think! &lt;/p&gt;

</description>
      <category>angular</category>
      <category>tree</category>
      <category>tutorial</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
