<?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>Forem: Michael Crenshaw</title>
    <description>The latest articles on Forem by Michael Crenshaw (@crenshaw_dev).</description>
    <link>https://forem.com/crenshaw_dev</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%2F153302%2F975af3b4-47fa-4761-9c04-89e438e9f824.png</url>
      <title>Forem: Michael Crenshaw</title>
      <link>https://forem.com/crenshaw_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/crenshaw_dev"/>
    <language>en</language>
    <item>
      <title>The three meanings of "template" in Argo Workflows</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Fri, 21 Jan 2022 17:22:07 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/the-three-meanings-of-template-in-argo-workflows-2paf</link>
      <guid>https://forem.com/crenshaw_dev/the-three-meanings-of-template-in-argo-workflows-2paf</guid>
      <description>&lt;p&gt;The word "template" has 3 different meanings in Argo Workflows. Argo is much easier to understand once you know the differences between "template tags," "unit-of-work templates," and "(Cluster)WorkflowTemplates".&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Template tags
&lt;/h1&gt;

&lt;p&gt;In an Argo Workflow manifest (or in an Argo CronWorkflow, WorkflowTemplate, or ClusterWorkflowTemplate), some fields may contain "template tags". A "template tag" starts with &lt;code&gt;{{&lt;/code&gt; and ends with &lt;code&gt;}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are two types of template tags: variable-substitution template tags and expression template tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.1 Variable-substitution template tags
&lt;/h2&gt;

&lt;p&gt;Variable-substitution template tags look like &lt;code&gt;{{some.variable.here}}&lt;/code&gt;. When a Workflow runs, Argo will replace the template tag with the value of the &lt;a href="https://argoproj.github.io/argo-workflows/variables/"&gt;variable&lt;/a&gt;. Different variables are available in different parts of a Workflow manifest.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ubuntu:{{inputs.parameters.version}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1.2 Expression template tags
&lt;/h2&gt;

&lt;p&gt;Expression template tags look like &lt;code&gt;{{=some expression here}}&lt;/code&gt;. Expressions may also use &lt;a href="https://argoproj.github.io/argo-workflows/variables/"&gt;variables&lt;/a&gt;. But instead of simply inserting the variable's value, Argo executes &lt;code&gt;some expression here&lt;/code&gt; as &lt;a href="https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md"&gt;expr&lt;/a&gt; code. Then the expression template tag gets replaces with the result of that execution.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{=inputs.parameters['some-number']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Notice that there's a slightly different way to access parameters by name if the parameter name contains a hyphen. expr would have treated the hyphen as a subtraction operator.)&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Unit-of-work templates
&lt;/h1&gt;

&lt;p&gt;The second use of the word "template" in Argo Workflows is to refer to a unit of work. These templates are located in the Workflow manifest (or CronWorkflow, WorkflowTemplate, or ClusterWorkflowTemplate manifest) under &lt;code&gt;spec.templates&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example (from &lt;a href="https://github.com/argoproj/argo-workflows/blob/master/examples/coinflip.yaml"&gt;coinflip example&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;coinflip&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flip-coin&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flip-coin&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;heads&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;heads&lt;/span&gt;
        &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{steps.flip-coin.outputs.result}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;==&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;heads"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tails&lt;/span&gt;
        &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tails&lt;/span&gt;
        &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{steps.flip-coin.outputs.result}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;==&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tails"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flip-coin&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:alpine3.6&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;import random&lt;/span&gt;
        &lt;span class="s"&gt;result = "heads" if random.randint(0,1) == 0 else "tails"&lt;/span&gt;
        &lt;span class="s"&gt;print(result)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;heads&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:3.6&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sh&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;-c&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;it&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;heads&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tails&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:3.6&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sh&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;-c&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;it&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tails&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each item under &lt;code&gt;spec.templates&lt;/code&gt; is a unit-of-work template (referred to in documentation simply as a "template".)&lt;/p&gt;

&lt;p&gt;There are several &lt;a href="https://argoproj.github.io/argo-workflows/templates/"&gt;template types&lt;/a&gt;. The example above contains two &lt;code&gt;container&lt;/code&gt; templates, one &lt;code&gt;script&lt;/code&gt; template, and a &lt;code&gt;steps&lt;/code&gt; template (a type of composite template to execute the other templates in a certain order).&lt;/p&gt;

&lt;h1&gt;
  
  
  3. (Cluster)WorkflowTemplates
&lt;/h1&gt;

&lt;p&gt;The third and final use of the word "template" in Argo Workflows is to refer to the WorkflowTemplate and ClusterWorkflowTemplate resources. A ClusterWorkflowTemplate is just a WorkflowTemplate that lives at the cluster level instead of a namespace. For the purposes of this description, they're effectively the same.&lt;/p&gt;

&lt;p&gt;A WorkflowTemplate has two potential uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  3.1 WorkflowTemplate as an invokable workflow
&lt;/h2&gt;

&lt;p&gt;When a Workflow is installed on a cluster, the Argo Workflows controller picks it up and immediately starts running it. But sometimes you want a workflow &lt;em&gt;available&lt;/em&gt; to be run without actually running it immediately.&lt;/p&gt;

&lt;p&gt;You could store the Workflow in git. But then if you have multiple users, you have to rely on them to authenticate with git and pull the latest source whenever they need to run a workflow.&lt;/p&gt;

&lt;p&gt;Instead, you could define a whole Workflow and change the &lt;code&gt;kind&lt;/code&gt; field in the manifest to &lt;code&gt;WorkflowTemplate&lt;/code&gt;. After it's applied to the cluster, anyone with cluster access can run &lt;code&gt;kubectl submit --from workflowtemplate/your-workflow-template&lt;/code&gt;. There's no need for git access, and they'll always be running the latest version.&lt;/p&gt;

&lt;h2&gt;
  
  
  3.2 WorkflowTemplate as a template library
&lt;/h2&gt;

&lt;p&gt;In this case, "template" means the unit-of-work template.&lt;/p&gt;

&lt;p&gt;Suppose you have a script-type template that sends a Slack notification. You might want to use this in a lot of different Workflows. Rather than copying and pasting the same template YAML into every Workflow, you could reference a template which is defined in a WorkflowTemplate. &lt;/p&gt;

&lt;p&gt;Example WorkflowTemplate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WorkflowTemplate&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slack-workflow-template&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slack-template&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;message&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-slack-notification-image&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;send-to-slack&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{inputs.parameters.message}}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example Workflow that uses a template from a WorkflowTemplate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Workflow&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generateName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;task-with-slack-notification-&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;task&lt;/span&gt;
  &lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;task&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# other steps here&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slack&lt;/span&gt;
        &lt;span class="na"&gt;templateRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slack-workflow-template&lt;/span&gt;
          &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slack-template&lt;/span&gt;
        &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;message&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;done"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The three uses of the word "template" in Argo Workflows can cause confusion. I recommend using the terms "tag template," "unit-of-work template," and "(Cluster)WorkflowTemplate" to help differentiate.&lt;/p&gt;

</description>
      <category>argo</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>How to debug an Argo Workflow</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Mon, 01 Mar 2021 14:38:31 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/how-to-debug-an-argo-workflow-31ng</link>
      <guid>https://forem.com/crenshaw_dev/how-to-debug-an-argo-workflow-31ng</guid>
      <description>&lt;p&gt;Argo Workflows is a tool for running a series (or a graph) of containers on Kubernetes, tying them together into a workflow.&lt;/p&gt;

&lt;p&gt;It's a relatively young project, so things like error messages and documentation still need some work.&lt;/p&gt;

&lt;p&gt;In the meantime, here are some steps you can take when your Workflow doesn't behave as expected.&lt;/p&gt;

&lt;h1&gt;
  
  
  Inspect the Workflow with Argo CLI
&lt;/h1&gt;

&lt;p&gt;For simple issues, the Argo CLI will include a short description in the &lt;code&gt;MESSAGE&lt;/code&gt; column of its &lt;code&gt;argo get&lt;/code&gt; output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ argo get workflow-template-dag-diamond 
Name:                workflow-template-dag-diamond
Namespace:           default
ServiceAccount:      default
Status:              Succeeded
Conditions:          
 Completed           True
Created:             Mon Mar 01 08:51:26 -0500 (7 minutes ago)
Started:             Mon Mar 01 08:51:26 -0500 (7 minutes ago)
Finished:            Mon Mar 01 08:51:36 -0500 (7 minutes ago)
Duration:            10 seconds
Progress:            1/1
ResourcesDuration:   5s*(1 cpu),5s*(100Mi memory)

STEP                              TEMPLATE                                               PODNAME                                   DURATION  MESSAGE
 ✔ workflow-template-dag-diamond  diamond                                                                                                      
 └─✔ A                            workflow-template-whalesay-template/whalesay-template  workflow-template-dag-diamond-2997968480  6s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there's a message that's just not &lt;em&gt;quite&lt;/em&gt; detailed enough to figure out the problem, copy the &lt;code&gt;PODNAME&lt;/code&gt; of the failed step, and skip to the section about using kubectl to describe the Pod.&lt;/p&gt;

&lt;p&gt;If there's no useful message, try describing the Workflow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Use kubectl to describe the Workflow
&lt;/h1&gt;

&lt;p&gt;Sometimes there's a problem with the &lt;em&gt;whole Workflow&lt;/em&gt; that doesn't fit nicely in &lt;code&gt;argo get&lt;/code&gt;'s &lt;code&gt;MESSAGE&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl describe workflow&lt;/code&gt; will print a lot more details about the Workflow, including a list of Events. The Events often contain details about what went wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl describe workflow workflow-template-dag-diamond
Name:         workflow-template-dag-diamond

...

Events:
  Type    Reason                 Age    From                 Message
  ----    ------                 ----   ----                 -------
  Normal  WorkflowRunning        8m42s  workflow-controller  Workflow Running
  Normal  WorkflowNodeSucceeded  8m32s  workflow-controller  Succeeded node workflow-template-dag-diamond.A
  Normal  WorkflowNodeSucceeded  8m32s  workflow-controller  Succeeded node workflow-template-dag-diamond
  Normal  WorkflowSucceeded      8m32s  workflow-controller  Workflow completed

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Use kubectl to describe the Pod
&lt;/h1&gt;

&lt;p&gt;If a Pod fails, there are a number of places which may hold clues. One is the Events associated with the Pods.&lt;/p&gt;

&lt;p&gt;Pod names may be unpredictable (they often have random suffixes like &lt;code&gt;-93750129&lt;/code&gt;), so use &lt;code&gt;argo get&lt;/code&gt; to get the name of the suspect Pod.&lt;/p&gt;

&lt;p&gt;Then use &lt;code&gt;kubectl describe po&lt;/code&gt; to see the Pod details, including the Events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl describe po workflow-template-dag-diamond-2997968480
Name:         workflow-template-dag-diamond-2997968480

...

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  36s                default-scheduler  Successfully assigned default/workflow-template-dag-diamond-2997968480 to docker-desktop
  Normal   Pulled     35s                kubelet            Container image "argoproj/argoexec:v2.12.9" already present on machine
  Normal   Created    35s                kubelet            Created container wait
  Normal   Started    35s                kubelet            Started container wait
  Warning  Failed     31s                kubelet            Failed to pull image "docker/whalesay": rpc error: code = Unknown desc = Error response from daemon: Head https://registry-1.docker.io/v2/docker/whalesay/manifests/latest: x509: certificate is valid for auth.docker.io, not registry-1.docker.io
  Normal   Pulling    17s (x2 over 35s)  kubelet            Pulling image "docker/whalesay"
  Warning  Failed     16s (x2 over 31s)  kubelet            Error: ErrImagePull
  Warning  Failed     16s                kubelet            Failed to pull image "docker/whalesay": rpc error: code = Unknown desc = Error response from daemon: Get https://registry-1.docker.io/v2/: x509: certificate is valid for auth.docker.io, not registry-1.docker.io
  Normal   BackOff    5s (x2 over 30s)   kubelet            Back-off pulling image "docker/whalesay"
  Warning  Failed     5s (x2 over 30s)   kubelet            Error: ImagePullBackOff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Workflow failed because a proxy issue is preventing pulls from Docker Hub.&lt;/p&gt;

&lt;p&gt;Sometimes the problem doesn't show up in the Events, because the failure is &lt;em&gt;inside&lt;/em&gt; one of the step's containers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Use kubectl to read the Pod logs
&lt;/h1&gt;

&lt;p&gt;Pods run as part of Argo Workflows have two or three containers: &lt;code&gt;wait&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt;, and sometimes &lt;code&gt;init&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;wait&lt;/code&gt; sidecar is injected by Argo to keep an eye on the &lt;code&gt;main&lt;/code&gt; container (your code) and communicate with the Argo Workflow controller (another Pod) about the step's progress.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; container is the one you set up when you defined the Workflow in yaml. (Look for the &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;args&lt;/code&gt;, and &lt;code&gt;source&lt;/code&gt; items to see part of this Pod's configuration.)&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;init&lt;/code&gt; container, if present, is also injected by Argo. It does things like pulling artifacts into the Pod.&lt;/p&gt;

&lt;p&gt;To read the logs, use &lt;code&gt;kubectl logs&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl logs workflow-template-dag-diamond-2997968480 init
error: container init is not valid for pod workflow-template-dag-diamond-2997968480
C02D507EMD6P:test_workflows ekmed$ kubectl logs workflow-template-dag-diamond-2997968480 wait
time="2021-03-01T14:09:18.339Z" level=info msg="Starting Workflow Executor" version=v2.12.9
time="2021-03-01T14:09:18.346Z" level=info msg="Creating a docker executor"
time="2021-03-01T14:09:18.346Z" level=info msg="Executor (version: v2.12.9, build_date: 2021-02-16T22:51:48Z) initialized (pod: default/workflow-template-dag-diamond-2997968480) with template:\n{\"name\":\"whalesay-template\",\"arguments\":{},\"inputs\":{\"parameters\":[{\"name\":\"message\",\"value\":\"A\"}]},\"outputs\":{},\"metadata\":{},\"container\":{\"name\":\"\",\"image\":\"docker/whalesay\",\"command\":[\"cowsay\"],\"resources\":{}}}"
time="2021-03-01T14:09:18.346Z" level=info msg="Waiting on main container"
time="2021-03-01T14:14:17.998Z" level=info msg="Alloc=4699 TotalAlloc=14633 Sys=70080 NumGC=6 Goroutines=7"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs from &lt;code&gt;init&lt;/code&gt; and &lt;code&gt;wait&lt;/code&gt; may be a bit difficult to read, because they come from Argo. The logs for &lt;code&gt;main&lt;/code&gt; will be from your configured &lt;code&gt;image&lt;/code&gt;, so they'll probably be more familiar.&lt;/p&gt;

&lt;h1&gt;
  
  
  Use kubectl to read the Workflow controller logs
&lt;/h1&gt;

&lt;p&gt;Argo comes with a Pod called the "Workflow controller" to sort of usher a Workflow through the process of running all its steps.&lt;/p&gt;

&lt;p&gt;If all the other debugging techniques fail, the Workflow controller logs may hold helpful information.&lt;/p&gt;

&lt;p&gt;First, find the Pod name. If you used the default Argo installation command, the Pod will be in the &lt;code&gt;argo&lt;/code&gt; namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get po -n argo
NAME                                  READY   STATUS    RESTARTS   AGE
argo-server-6bb488c6c8-ff88g          1/1     Running   0          40m
workflow-controller-57db6b46f-7qfr9   1/1     Running   0          40m
$ kubectl logs workflow-controller-57db6b46f-7qfr9 -n argo

... lots of stuff here ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Ask for help
&lt;/h1&gt;

&lt;p&gt;If none of these solves your problem, &lt;a href="https://stackoverflow.com/questions/tagged/argo-workflows"&gt;ask a question on StackOverflow&lt;/a&gt;, &lt;a href="https://github.com/argoproj/argo-workflows/discussions/new"&gt;start a discussion on GitHub&lt;/a&gt;, or &lt;a href="https://argoproj.github.io/community/join-slack"&gt;ask in the Argo Slack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These are just a few of my go-to tools. If I'm missing anything, please comment!&lt;/p&gt;

</description>
      <category>kubernetes</category>
    </item>
    <item>
      <title>How do you know when to NOT refactor?</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Tue, 02 Jul 2019 20:50:27 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/how-do-you-know-when-to-not-refactor-14d9</link>
      <guid>https://forem.com/crenshaw_dev/how-do-you-know-when-to-not-refactor-14d9</guid>
      <description>&lt;h1&gt;
  
  
  What Sparked the Question
&lt;/h1&gt;

&lt;p&gt;I recently leafed through &lt;em&gt;The Power of TED*&lt;/em&gt;," a book-length parable about destructive cycles of human psychology in relationships.&lt;/p&gt;

&lt;p&gt;At first &lt;strong&gt;I was annoyed by the abstractions&lt;/strong&gt;. The "Dreaded Drama Triangle" made up of Victim, Persecutor, and Rescuer roles just seemed too neat and tidy to &lt;em&gt;actually&lt;/em&gt; describe reality. It seemed the author was under the delusion that they'd pushed aside some curtain and glimpsed Reality Proper.&lt;/p&gt;

&lt;p&gt;But then I realized the abstractions are just abstractions - useful heuristics to help people ask and answer the right questions. "Am I thinking of myself as a victim, and how might that impact my relationships? Do I recognize this kind of cycle in my life?"&lt;/p&gt;

&lt;p&gt;(Aside: recognition of victim-hood can be useful. From what I can tell, the book thoughtfully avoids judgment of people who recognize "persecution" and instead focus on how to break harmful cycles of behavior. If people choose to wield the book's ideas judgmentally, that's obviously bad.)&lt;/p&gt;

&lt;h1&gt;
  
  
  The Question
&lt;/h1&gt;

&lt;p&gt;Someone recently remarked to me, "A developer's first reaction to anyone else's code is &lt;strong&gt;'This is rubbage, let's redo it all.'&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;Though hyperbolic, the remark hit home. I refactor by default, and I don't have enough tools to recognize when I should leave less-than-perfect code alone.&lt;/p&gt;

&lt;p&gt;So I wonder: &lt;strong&gt;How do you determine when to &lt;em&gt;not&lt;/em&gt; refactor? What questions do you ask yourself or others? What kind of "gut feelings" do you encounter?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm also interested in hard-and-fast rules, but I suspect many readers will have more approximate tools at hand.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Don't forget to ask Babel to compile .mjs files</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Fri, 17 May 2019 15:29:02 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/don-t-forget-to-ask-babel-to-compile-mjs-files-h85</link>
      <guid>https://forem.com/crenshaw_dev/don-t-forget-to-ask-babel-to-compile-mjs-files-h85</guid>
      <description>&lt;p&gt;I spent several hours trying to understand why Babel wasn't transpiling &lt;code&gt;const&lt;/code&gt; statements in Jeremy Wagner's &lt;a href="https://github.com/malchata/yall.js/"&gt;yall.js library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It was because the actual filename is yall.&lt;strong&gt;m&lt;/strong&gt;js, and I hadn't configured Babel to handle that extension.&lt;/p&gt;

&lt;p&gt;The correct &lt;code&gt;test&lt;/code&gt; configuration ended up being as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nx"&gt;s$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you don't need TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;js$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suffer me not to ponder the ills and advantages of the &lt;code&gt;.mjs&lt;/code&gt; extension. That's for smarter people.&lt;/p&gt;

&lt;p&gt;But since some folks &lt;em&gt;do&lt;/em&gt; use that extension, it's probably worth updating your Babel config and related documentation/tutorials.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>babel</category>
    </item>
    <item>
      <title>Writing modular controls in ASP.NET Web Forms</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Fri, 03 May 2019 16:06:21 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/writing-modular-controls-in-asp-net-web-forms-47mc</link>
      <guid>https://forem.com/crenshaw_dev/writing-modular-controls-in-asp-net-web-forms-47mc</guid>
      <description>&lt;p&gt;Most sites have common components, like headers and footers. Some components exhibit only slight variations across the site. &lt;/p&gt;

&lt;p&gt;You can copy and paste the common parts of the markup. But that can lead to headaches when it's time to make a change to each copy of the component.&lt;/p&gt;

&lt;p&gt;ASP.NET provides a way to be more modular, with nested components.&lt;/p&gt;

&lt;p&gt;A familiar example of nesting is with the built-in Repeater control.&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;asp:Repeater&lt;/span&gt; &lt;span class="na"&gt;ID=&lt;/span&gt;&lt;span class="s"&gt;"ExampleRepeater"&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;HeaderTemplate&amp;gt;&amp;lt;/HeaderTemplate&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;ItemTemplate&amp;gt;&amp;lt;/ItemTemplate&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FooterTemplate&amp;gt;&amp;lt;/FooterTemplate&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/asp:Repeater&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Repeater&lt;/code&gt; has common behavior that's applied to the content in the &lt;code&gt;Template&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;Take a look at this markup from the boilerplate ASP.NET Web Form site that comes with Visual Studio 2017:&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-md-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Getting started&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            ASP.NET Web Forms lets you build dynamic websites using a familiar drag-and-drop, event-driven model.
        A design surface and hundreds of controls and components let you rapidly build sophisticated, powerful UI-driven sites with data access.
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-default"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://go.microsoft.com/fwlink/?LinkId=301948"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn more &lt;span class="ni"&gt;&amp;amp;raquo;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-md-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Get more libraries&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-default"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://go.microsoft.com/fwlink/?LinkId=301949"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn more &lt;span class="ni"&gt;&amp;amp;raquo;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-md-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Web Hosting&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            You can easily find a web hosting company that offers the right mix of features and price for your applications.
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-default"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://go.microsoft.com/fwlink/?LinkId=301950"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn more &lt;span class="ni"&gt;&amp;amp;raquo;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;That code renders the three columns visible on the homepage:&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%2Fuploads%2Farticles%2Fw106hhbj5kkeqag9inym.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%2Fuploads%2Farticles%2Fw106hhbj5kkeqag9inym.png" alt="Screenshot of Visual Studio 2017 boilerplate ASP.NET Web Forms application homepage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's consolidate the common parts of the markup into a modular user control.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up the project
&lt;/h1&gt;

&lt;p&gt;If you know how to set up an ASP.NET Web Forms project in Visual Studio 2017, or are doing so in a different IDE, feel free to skip to the next section.&lt;/p&gt;

&lt;p&gt;First, create a new project. I'm using the C# flavor of an ASP.NET Web Application project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;File &amp;gt; New &amp;gt; Project...&lt;/code&gt;&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%2Fuploads%2Farticles%2Fyt7m08xukp57rvsu1v73.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%2Fuploads%2Farticles%2Fyt7m08xukp57rvsu1v73.png" alt="Screenshot of the new project dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the Web Forms type of Web Application.&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%2Fuploads%2Farticles%2Fljx5v4j4zkmso9kp0eay.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%2Fuploads%2Farticles%2Fljx5v4j4zkmso9kp0eay.png" alt="Web application type dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(I know Web Forms is passé, but many sites still use it. As far as I can tell, it's still a perfectly acceptable way to build a site.)&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%2Fuploads%2Farticles%2Frfa55vi4pr4wvw4zh9fz.jpg" 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%2Fuploads%2Farticles%2Frfa55vi4pr4wvw4zh9fz.jpg" alt="Imperial guard from Star wars saying "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you launch the site in a browser, you'll see the default homepage as shown above.&lt;/p&gt;

&lt;p&gt;We're going to create a new user control to hold the common "column" code. &lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up the modular control
&lt;/h1&gt;

&lt;p&gt;I'm creating a new "Controls" folder, just to stay organized.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Solution Explorer &amp;gt; Right-click ModularControls project &amp;gt; Add &amp;gt; New Folder&lt;/code&gt;&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%2Fuploads%2Farticles%2Ffy4hgtrugc9ht50ggwxk.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%2Fuploads%2Farticles%2Ffy4hgtrugc9ht50ggwxk.png" alt="Screenshot showing how to create a new folder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now create the control. I'm calling it a "homepage card," though you'd probably only ever bother to create a modular control for something that's used on multiple pages.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Solution Explorer &amp;gt; Right-Click Controls folder &amp;gt; Add &amp;gt; Web Forms User Control&lt;/code&gt;&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%2Fuploads%2Farticles%2Fakbazbzrly0zezu62a6v.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%2Fuploads%2Farticles%2Fakbazbzrly0zezu62a6v.png" alt="Screenshot showing how to create a new user control"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy and paste the code for any of the columns to your new control. Then replace the contents of the header and paragraph tags with appropriately-named &lt;code&gt;&amp;lt;asp:PlaceHolder&amp;gt;&lt;/code&gt; controls.&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-md-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;asp:PlaceHolder&lt;/span&gt; &lt;span class="na"&gt;ID=&lt;/span&gt;&lt;span class="s"&gt;"TitlePlaceHolder"&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/asp:PlaceHolder&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;asp:PlaceHolder&lt;/span&gt; &lt;span class="na"&gt;ID=&lt;/span&gt;&lt;span class="s"&gt;"ContentPlaceHolder"&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/asp:PlaceHolder&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;asp:PlaceHolder&lt;/span&gt; &lt;span class="na"&gt;ID=&lt;/span&gt;&lt;span class="s"&gt;"FooterPlaceHolder"&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/asp:PlaceHolder&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Then set up the control's code-behind something like this:&lt;/p&gt;

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

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Web.UI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;ModularControls.Controls&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ParseChildren&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;DefaultProperty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ChildrenAsProperties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&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;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomepageCard&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserControl&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;HomepageCard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ControlCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ControlCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Footer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ControlCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerProperty&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;ControlCollection&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&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;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerDefaultProperty&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;ControlCollection&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&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;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerProperty&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;ControlCollection&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Page_Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;TitlePlaceHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ContentPlaceHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;FooterPlaceHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;There's a lot going on, so I'll break down the key parts.&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;ParseChildren&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;DefaultProperty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ChildrenAsProperties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The three arguments of the &lt;code&gt;ParseChildren&lt;/code&gt; attribute instruct ASP.NET to &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;parse nested content as &lt;code&gt;Control&lt;/code&gt; objects (&lt;code&gt;typeof(Control)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;if no section is specified (we'll look at how that's done below), parse controls into the &lt;code&gt;Content&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;parse children into properties, as defined below&lt;/li&gt;
&lt;/ol&gt;

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

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;HomepageCard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ControlCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ControlCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Footer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ControlCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The default constructor news up the control collections so they're ready to accept controls parsed by ASP.NET. The &lt;code&gt;this&lt;/code&gt; argument specifies the "owner" control of these control collections.&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;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerProperty&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;ControlCollection&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&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;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerDefaultProperty&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;ControlCollection&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&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;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PersistenceMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerProperty&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;ControlCollection&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;PersistentMode&lt;/code&gt; attributes tell ASP.NET how the contents are persisted into the rendered page. Note that the &lt;code&gt;Content&lt;/code&gt; property gets marked as the &lt;code&gt;InnerDefaultProperty&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;ControlCollection&lt;/code&gt; needs the setter explicitly set to &lt;code&gt;private&lt;/code&gt;. If you skip this step, you'll get difficult-to-debug runtime errors.&lt;/p&gt;

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

&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Page_Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TitlePlaceHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ContentPlaceHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FooterPlaceHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Load&lt;/code&gt; event handler loops through the parsed controls and drops them into the appropriate placeholders.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using the modular control
&lt;/h1&gt;

&lt;p&gt;First, register the control in Default.aspx.&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%@&lt;/span&gt; &lt;span class="na"&gt;Register&lt;/span&gt; &lt;span class="na"&gt;Src=&lt;/span&gt;&lt;span class="s"&gt;"~/Controls/HomepageCard.ascx"&lt;/span&gt; &lt;span class="na"&gt;TagPrefix=&lt;/span&gt;&lt;span class="s"&gt;"ex"&lt;/span&gt; &lt;span class="na"&gt;TagName=&lt;/span&gt;&lt;span class="s"&gt;"HomepageCard"&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Then replace the first column with the following code.&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;ex:HomepageCard&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Title&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;asp:PlaceHolder&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Getting started
        &lt;span class="nt"&gt;&amp;lt;/asp:PlaceHolder&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Content&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;asp:PlaceHolder&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            ASP.NET Web Forms lets you build dynamic websites using a familiar drag-and-drop, event-driven model.
            A design surface and hundreds of controls and components let you rapidly build sophisticated, powerful 
            UI-driven sites with data access.
        &lt;span class="nt"&gt;&amp;lt;/asp:PlaceHolder&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Content&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Footer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;asp:PlaceHolder&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-default"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://go.microsoft.com/fwlink/?LinkId=301948"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn more &lt;span class="ni"&gt;&amp;amp;raquo;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/asp:PlaceHolder&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Footer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ex:HomepageCard&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The Title, Content, and Footer tags tell ASP.NET where to place the contents.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;asp:PlaceHolder&amp;gt;&lt;/code&gt; controls allow ASP.NET to parse the markup as controls.&lt;/p&gt;

&lt;p&gt;It's a bit verbose. But once the verbose part is written, it never has to be changed. Common parts of the controls can be changed in HomepageCard.ascx.&lt;/p&gt;

&lt;p&gt;If you rebuild and reload the home page, it should look precisely the same as before you wrote the modular control. That's the idea! This pattern doesn't change the rendered content - it just makes it more modular and maintainable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Improvements?
&lt;/h1&gt;

&lt;p&gt;When researching this technique, I found &lt;em&gt;very&lt;/em&gt; little documentation. Since &lt;a href="https://en.wikipedia.org/wiki/ASP.NET_Web_Forms" rel="noopener noreferrer"&gt;the first version of ASP.NET Web Forms was released in 2002&lt;/a&gt;, it's possible most of the guides for this technique are in printed form.&lt;/p&gt;

&lt;p&gt;If you know tweaks to improve this pattern, please let me know! I'd love to drop the &lt;code&gt;PlaceHolder&lt;/code&gt; tags to tighten up the markup. And I'd really love to drop all the properties and placeholder population from the code-behind in favor of some more expressive syntax. But I'm not sure either is possible.&lt;/p&gt;

&lt;p&gt;I hope this is a helpful pattern! If so, please comment and let me know how you used it!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Should I finish loading lazy images while the browser is idle?</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Tue, 16 Apr 2019 13:51:45 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/should-i-finish-loading-lazy-images-while-the-browser-is-idle-3hgp</link>
      <guid>https://forem.com/crenshaw_dev/should-i-finish-loading-lazy-images-while-the-browser-is-idle-3hgp</guid>
      <description>&lt;p&gt;I've heard of JS packages that prefetch linked pages while the browser is idle. &lt;/p&gt;

&lt;p&gt;Would there be any downside to doing something similar with lazily-loaded images?&lt;/p&gt;

&lt;p&gt;At work, I'm on a fast connection. The Crutchfield home page loads very quickly, partly because below-the-fold images are lazy loaded.&lt;/p&gt;

&lt;p&gt;But when I scroll down, even though I've been idle a few seconds, lazy-loaded images still "snap" into place. Not an ideal experience.&lt;/p&gt;

&lt;p&gt;Shouldn't idle time be used to complete the page load? &lt;/p&gt;

&lt;p&gt;Questions that come to mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How do I determine "idle"? &lt;code&gt;requestIdleCallback&lt;/code&gt;, &lt;code&gt;setTimeout(loadImages, 5000)&lt;/code&gt;, etc.?&lt;/li&gt;
&lt;li&gt;How do I performantly order the images to be eagerly loaded? Slowly expand the margin of an IntersectionObserver? (Is that even possible?)&lt;/li&gt;
&lt;li&gt;How will this interact with &lt;a href="https://addyosmani.com/blog/lazy-loading/"&gt;the new &lt;code&gt;loading&lt;/code&gt; attribute&lt;/a&gt;? Would I simply adopt the same strategy, just setting &lt;code&gt;loading="eager"&lt;/code&gt; instead of changing the &lt;code&gt;src&lt;/code&gt; attribute?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'd love to know your thoughts, especially if you've implemented this - or explicitly chosen not to implement it for some reason.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>webperf</category>
      <category>html</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How do you support detailed validation error messages?</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Mon, 15 Apr 2019 18:17:04 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/how-do-you-offer-detailed-validation-errors-pe4</link>
      <guid>https://forem.com/crenshaw_dev/how-do-you-offer-detailed-validation-errors-pe4</guid>
      <description>&lt;p&gt;I was irked by &lt;a href="https://twitter.com/sebmck/status/1116868234324496384"&gt;this Tweet&lt;/a&gt; from Sebastian McKenzie:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re writing validation code, please never make it output “Incorrect foo” or “Invaid bar”. Pleeeaaase just tell me what I need to fix, and why it’s invalid.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It irked me not because Sebastian is wrong... on the contrary, it hits too close to home. I need to implement more detailed validation messages for some of Crutchfield's inputs, but it's not clear how I should do it.&lt;/p&gt;

&lt;p&gt;For example, take this email validation regex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^[-a-zA-Z0-9+.*!#$\/=_]+@((([a-zA-Z0-9][-a-zA-Z0-9]{0,61}[a-zA-Z0-9])|([a-zA-Z0-9]))\.)+(([a-zA-Z0-9][-a-zA-Z0-9]{0,61}[a-zA-Z0-9])|([a-zA-Z0-9]))$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(I know it's &lt;a href="https://stackoverflow.com/a/1903368/684776"&gt;not great&lt;/a&gt; to validate email with regex - but it works for 99.999% of real-world email addresses, and it helps users catch common typos.)&lt;/p&gt;

&lt;p&gt;A number of things can cause an address to fail validation, but the regex only communicates two states: &lt;em&gt;valid&lt;/em&gt; or &lt;em&gt;invalid&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So how do I communicate to the user what invalidated the input? I could imagine designing a series of validations using parts of the above pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pattern&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^[-a-zA-Z0-9+.*!#$&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;/=_]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your email must start with a letter, number, or one of these symbols: - + . * ! # $ &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;/ = _&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pattern&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your email address must contain @&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there are a couple problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There's code duplication, complicating maintenance.&lt;/li&gt;
&lt;li&gt;Applying the validation requires JavaScript (the &lt;code&gt;pattern&lt;/code&gt; attribute won't suffice).&lt;/li&gt;
&lt;li&gt;It becomes less clear for developers what is the "true" validation (which should also be used server-side) and what is just used for helpful messaging.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a potential solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call the additional patterns "typo checks" instead of "validation," making clear they're only used to help resolve simple typos, not provide a complete validity check.&lt;/li&gt;
&lt;li&gt;Don't treat input as invalid if &lt;em&gt;only&lt;/em&gt; the typo check fails. If the main validation pattern matches, treat the input as valid; if it doesn't match, check the other patterns to find a "friendly message."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At least that's my current plan of attack.&lt;/p&gt;

&lt;p&gt;Does that make sense? How does your site/app handle this?&lt;/p&gt;

</description>
      <category>html</category>
      <category>javascript</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How much of a page is occupied by images?</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Sun, 14 Apr 2019 16:41:51 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/how-much-of-a-page-is-occupied-by-images-g9g</link>
      <guid>https://forem.com/crenshaw_dev/how-much-of-a-page-is-occupied-by-images-g9g</guid>
      <description>&lt;p&gt;I was curious how much of a page's area was occupied by images. So I hacked together a rough approximation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PercentOfPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetHeight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For my purposes I'd use &lt;code&gt;const imgArea = PercentOfPage('img');&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It doesn't take into account whether the target elements are actually visible (for example, they might be positioned off-page or behind another element). But it's good enough for my purposes.&lt;/p&gt;

&lt;p&gt;Please offer suggestions! I'd love to fine-tune this utility a bit more.&lt;/p&gt;

&lt;p&gt;P.S.: this page is ~1% images.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
    </item>
    <item>
      <title>When the browser can't take a (preconnect) hint</title>
      <dc:creator>Michael Crenshaw</dc:creator>
      <pubDate>Sun, 07 Apr 2019 22:23:05 +0000</pubDate>
      <link>https://forem.com/crenshaw_dev/when-the-browser-can-t-take-a-preconnect-hint-6dn</link>
      <guid>https://forem.com/crenshaw_dev/when-the-browser-can-t-take-a-preconnect-hint-6dn</guid>
      <description>&lt;p&gt;Resource hints help pages load faster by telling the browser what assets it'll need in the future. For example, a &lt;code&gt;preload&lt;/code&gt; hint for a font file tells the browser to start downloading the font before stylesheets are parsed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- other stuff --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.example.com/font.woff2"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"font"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"font/woff2"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.example.com/styles.css"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes you don't know ahead of time what assets are needed in a page. But you know they'll be hosted on a particular domain. In that case, you can give the browser a head start with the &lt;code&gt;preconnect&lt;/code&gt; resource hint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- other stuff --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.example.com/"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Without resource hints
&lt;/h2&gt;

&lt;p&gt;Suppose a page on my site uses a stylesheet on another origin, and that stylesheet defines a custom font.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Custom Font'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(https://mac9416.com/demo/preconnect/font.ttf)&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;'truetype'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Custom Font'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without resource hints, my page's HTML must be parsed before the stylesheet can be downloaded.&lt;br&gt;
(In this example, I've moved the stylesheet link below a massive block of "lorem ipsum" text, to simulate a late-discovered stylesheet and make the waterfall chart easier to read.)&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%2Fuploads%2Farticles%2Fo5l5ho8clcbitrpyfs37.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%2Fuploads%2Farticles%2Fo5l5ho8clcbitrpyfs37.png" alt="WebPageTest waterfall chart showing no connection to the external domain until HTML is completely downloaded and parsed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above WebPageTest waterfall chart, the page is completely downloaded and parsed before Chrome connects to mac9416.com to download the stylesheet.&lt;/p&gt;
&lt;h2&gt;
  
  
  preconnect with crossorigin
&lt;/h2&gt;

&lt;p&gt;The asset will load faster if I add a &lt;code&gt;preconnect&lt;/code&gt; hint for mac9416.com. (Remember, we're pretending we don't know specifically which assets will be downloaded from mac9416.com. Otherwise we would add preload hints for an even greater performance boost.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- other stuff --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mac9416.com/"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fuploads%2Farticles%2Fd37ruvpkqbhwpuyuc5k4.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%2Fuploads%2Farticles%2Fd37ruvpkqbhwpuyuc5k4.png" alt="WebPageTest waterfall chart showing one connection to the external domain starting immediately after the first chunk of HTML is parsed, but the other one being delayed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a &lt;code&gt;preconnect&lt;/code&gt; hint in place, the waterfall looks better. A DNS lookup for mac9416.com happens immediately after the first chunk of HTML is downloaded. And the connection used to download my custom font happens immediately after the DNS lookup is finished. But it looks like there's another connection to mac9416.com that's initiated after HTML is downloaded and parsed. That second connection is used to download the custom font.&lt;/p&gt;

&lt;h2&gt;
  
  
  preconnect without crossorigin
&lt;/h2&gt;

&lt;p&gt;In the above code sample, I naiively copied an example and left the &lt;code&gt;crossorigin&lt;/code&gt; attribute in place. I just assumed it meant "this connection is to a different domain" -- which it is. Let's see what happens when I remove that attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- other stuff --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mac9416.com/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fuploads%2Farticles%2Ff460h9n55me1uiofkfno.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%2Fuploads%2Farticles%2Ff460h9n55me1uiofkfno.png" alt="WebPageTest waterfall chart showing one connection to the external domain starting immediately after the first chunk of HTML is parsed, but the other one beind delayed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this waterfall chart, we seem to have the same problem, but with the connections swapped. The connection used to download styles happens immediately, but the connection used to download fonts begins after the stylesheet is downloaded and parsed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did crossorigin break things?
&lt;/h2&gt;

&lt;p&gt;What's going on? I incorrectly assumed &lt;code&gt;crossorigin&lt;/code&gt; simply meant "the target is on another domain." But the browser could infer that by comparing the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element's &lt;code&gt;href&lt;/code&gt; attribute to the current page's origin. So what is the &lt;code&gt;crossorigin&lt;/code&gt; attribute for?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;crossorigin&lt;/code&gt; actually tells the browser that "resources on this connection are downloaded using CORS."&lt;br&gt;
By default, it specifically means "CORS without credentials."&lt;/p&gt;

&lt;p&gt;CORS improves web security. That's all I'll say about it here, because smarter people have explained it better elsewhere.&lt;/p&gt;

&lt;p&gt;To speed up this web page, all we need to know is that resources downloaded without CORS are downloaded on a separate connection from those that use CORS.&lt;/p&gt;

&lt;p&gt;A quick glance at a list of requests that use CORS shows that our font request will use CORS, but the stylesheet request will not.&lt;/p&gt;
&lt;h2&gt;
  
  
  preconnect with crossorigin and without
&lt;/h2&gt;

&lt;p&gt;So let's use two &lt;code&gt;preconnect&lt;/code&gt; hints, one for non-CORS requests, and the other for CORS requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- other stuff --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mac9416.com/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mac9416.com/"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fuploads%2Farticles%2F3k12mk2k4vfqjlqtdq29.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%2Fuploads%2Farticles%2F3k12mk2k4vfqjlqtdq29.png" alt="WebPageTest waterfall chart showing two connections to mac9416.com starting immediately after the first chunk of HTML is parsed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This waterfall chart looks much better. With two resource hints in place, both connections to mac9416.com are established immediately after the first chunk of HTML is parsed. Incidentally, we've achieved the fastest time to document complete (the blue line in the waterfall chart) of all the tests.&lt;/p&gt;

&lt;p&gt;Just for good measure, I'll add a &lt;code&gt;dns-prefetch&lt;/code&gt; hint for browsers that don't support the &lt;code&gt;preconnect&lt;/code&gt; hint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- other stuff --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"dns-prefetch"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mac9416.com/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mac9416.com/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://mac9416.com/"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no need for a &lt;code&gt;crossorigin&lt;/code&gt; attribute, since DNS queries are performed without CORS.&lt;/p&gt;

&lt;p&gt;For this experiment I've ignored &lt;code&gt;crossorigin="use-credentials"&lt;/code&gt;. I suspect CORS requests with credentials would require a third hint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The crossorigin attribute, when used with rel="preconnect", doesn't describe &lt;em&gt;where the target origin is&lt;/em&gt; but rather &lt;em&gt;what kind of assets will be downloaded from that origin&lt;/em&gt;. If the assets use CORS, crossorigin is needed. If CORS won't be used, crossorigin should be omitted. If both types of assets will be present, two resource hints are necessary.&lt;/p&gt;

&lt;p&gt;If you've found resource hints and crossorigin confusing, don't feel bad. There's a lot going on. If you find this guide confusing, please contact me! The fault is probably mine, and I'll be happy to clarify.&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://crenshaw.dev/preconnect-resource-hint-crossorigin-attribute" rel="noopener noreferrer"&gt;crenshaw.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webperf</category>
    </item>
  </channel>
</rss>
