<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Ash Grennan</title><link>https://www.ashgrennan.com/</link><description>Posts by Ash Grennan</description>
<item><title>Arise: Lessons from Solo Levelling for Software Engineers</title><link>https://www.ashgrennan.com/post/arise-lessons-from-solo-levelling-for-software-engineers/</link><guid>https://www.ashgrennan.com/post/arise-lessons-from-solo-levelling-for-software-engineers/</guid><pubDate>Fri, 16 May 2025 00:00:00 +0000</pubDate><description>&lt;p&gt;In this anime, the protagonist Sung Jin-woo starts by fighting
monsters. Initially, he’s weak relative to his peers. However, he
receives a unique opportunity to grow stronger, gradually becoming an
exceptional fighter. Eventually, he gains the ability to raise defeated
enemies as Shadows, who fight alongside him. These Shadows fight
autonomously, allowing him to step back. Is this where his strength
stagnates? No, it accelerates.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/arise-lessons-from-solo-levelling-for-software-engineers/img-1.jpg&quot; alt=&quot;Solo levelling: shadow&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;Solo levelling: shadow&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In software engineering, we can use LLM-based tooling to augment
ourselves. This has been the case for some time. Many of us tried these
tools in their infancy and decided they were more of a hindrance than a
help, an opinion I shared. With the advancements in tools like Cursor AI
and refined language models, should we continue to dismiss their
usefulness and not re-evaluate?&lt;/p&gt;
&lt;p&gt;As Sung encounters more challenging dungeons, using his Shadow army
becomes more valuable. From his new capability, Sung learns new skills,
such as instructing and strategising. Just like an engineer who gains
time by leveraging gen AI to handle the mundane, Sung’s reliance on his
Shadow army becomes an enabler for personal growth.&lt;/p&gt;
&lt;p&gt;The world is speeding up, and the amount of information thrust into
our view is accelerating. Time spent reading, comprehending, and
digesting articles in the era of digital information is on the
&lt;strong&gt;decline&lt;/strong&gt; and will &lt;strong&gt;continue to fall&lt;/strong&gt;.
Why, you ask? Because there’s a limit to how fast we can absorb
information, and there’s already too much. There’s &lt;em&gt;so&lt;/em&gt; much of
it that our default reading style is skimming through information, often
missing nuance. We are trying to &lt;strong&gt;drink from a firehose&lt;/strong&gt;
each day. We’re burnt out, not from overworking, but from
overstimulation.&lt;/p&gt;
&lt;h3 id=&quot;the-next-layer-of-abstraction&quot;&gt;The next layer of
abstraction&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/arise-lessons-from-solo-levelling-for-software-engineers/img-2.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Programming has drastically changed over the past 70 years, from
punch cards to assembly to OOP languages. Most developments are an
abstraction over the previous instructions, and I’m sure we’re all happy
about not having to use punch cards, waiting hours to retest our logic.
However, over this period, there were many who shunned the changes that
got us to this point; they resisted and rebelled against it.&lt;/p&gt;
&lt;p&gt;When our worldview is challenged by something that we believe impacts
our value addition, we choose ignorance. We measure our productivity by
keystrokes, not by the quality of our ideas or the creativity behind our
solutions. We’ve been conditioned this way, but original thought and
novel ideas are what true productivity really is.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/arise-lessons-from-solo-levelling-for-software-engineers/img-3.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We have an opportunity to use the next layer of abstraction, just
like a better paintbrush aiding a painter. It’s our responsibility to
leverage our expertise and apply technology selectively, ensuring we
understand the complexities and unknowns.&lt;/p&gt;
&lt;p&gt;It’s by taking on difficult and unclear problems that we sharpen our
internal tool, our mind. Using the cognitive bandwidth freed by
advancements in tooling, we are more able to apply ourselves fully, like
Sung Jin-woo. Do you &lt;em&gt;need&lt;/em&gt; to type every key stroke? Doesn’t
code get optimised by a compiler?&lt;/p&gt;
&lt;p&gt;Sure, I hear you, why not slow down? That’s the right solution
surely. This isn’t in our control to dictate. Though the pressure to
move faster may feel inevitable in a competitive environment,
intelligently applying these tools may offer us the cognitive breathing
room to slow down our thinking process, focusing on deeper insights
while maintaining output.&lt;/p&gt;
&lt;p&gt;If you ship faster, you in turn learn faster and with this
competitors will always be at least one step behind. They’ll never be
able to catch up if done correctly.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-a-history-lesson&quot;&gt;Conclusion: a history lesson&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/arise-lessons-from-solo-levelling-for-software-engineers/img-4.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In our control is the ability to squeeze the world to fit our
cognitive bandwidth; tools like LLMs can greatly help with this.
Socrates worried that writing things down would cause one to have a bad
memory, yet it’s empowered humanity’s collective memory to learn.
Similarly, LLMs, if used thoughtfully, won’t weaken our skills as
engineers; they’ll amplify our ability to manage complexity and
innovate. Wisdom isn’t fearing change and what that may take from us,
but instead embracing what positives it may unlock within us.&lt;/p&gt;
</description></item>
<item><title>Handling Zendesk events via EventBridge</title><link>https://www.ashgrennan.com/post/handling-zendesk-events-via-eventbridge/</link><guid>https://www.ashgrennan.com/post/handling-zendesk-events-via-eventbridge/</guid><pubDate>Wed, 02 Oct 2024 00:00:00 +0000</pubDate><description>&lt;blockquote&gt;
&lt;p&gt;Note: This article was written before the announcement on Zendesk
deprecating their partnered EventBridge integration in December which
can be replaced via a webhook integrating into EventBridge. However,
this article is relevant to any event-based broker integration and the
architecture discussions it entails are valuable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;In this post, we will cover how we solved some inefficiencies in our
business by using Zendesk’s EventBridge partnered connector in
conjunction with an event-driven architecture. If you’re unfamiliar with
Zendesk it’s a powerful customer service platform that helps
organisations manage and resolve customer inquiries efficiently. We use
this here at Moonpig for customer service operations.&lt;/p&gt;
&lt;p&gt;AWS EventBridge is a serverless bus that enables services to be
loosely coupled and event-driven. It can consume data from multiple
sources and publish these to different destinations.&lt;/p&gt;
&lt;h3 id=&quot;problem-space&quot;&gt;Problem Space&lt;/h3&gt;
&lt;p&gt;Within Moonpig when a customer interacts with us through our Chatbot
via Zendesk we’re aware due to processes such as looking up an order. We
are also aware of when a resolution has been reached. When this happens,
we add meta information to each ticket, saving our support team time.
One of the challenges we face is that Zendesk has a variable time window
for ticket creation, so we don’t know when tickets will be available for
our enrichment process.&lt;/p&gt;
&lt;p&gt;Initially, this was solved via long polling, a common but inefficient
pattern that can lead to increased infrastructure costs and higher API
rate limits. Polling regularly consumes unnecessary resources and
creates latency in data processing. However, thanks to the introduction
of Zendesk’s partner resource, we can now move to a more efficient,
event-driven process, which eliminates these challenges.&lt;/p&gt;
&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;
&lt;p&gt;For our architecture, we’ll be using AWS serverless offerings to
create a redundant, event-driven architecture. Here’s our starting
point:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/handling-zendesk-events-via-eventbridge/img-2.png&quot; alt=&quot;Figure 1) Proposed architecture&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;Figure 1) Proposed
architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;At a high level, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EventBridge&lt;/strong&gt; is our bus, where the Zendesk bridge is
connected and enables data to flow into our account. We’ll create &lt;a
href=&quot;https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule.html&quot;&gt;rules&lt;/a&gt;
that can generate filters and even transformations based on specific
schema patterns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SNS&lt;/strong&gt; broadcasts notifications of business domain
events, this enables a pub/sub setup that produces events to which other
services can subscribe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQS &amp;gt; Lambda&lt;/strong&gt; gives us a decoupled approach,
giving a host of benefits such as redundancy, high availability and
requiring no changes to any producer should this pattern change in any
manner. Within our Lambda we can query other services for additional
data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/handling-zendesk-events-via-eventbridge/img-3.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Our architecture for this example looks good, however, we’re only
dealing with &lt;strong&gt;two&lt;/strong&gt; CRUD events on a single resource which
is Ticket. But, what if we want more? There are over 100 different &lt;a
href=&quot;https://developer.zendesk.com/api-reference/amazon-eventbridge/tickets/&quot;&gt;events&lt;/a&gt;
across all resources, would our above architecture cope? The answer is
yes, but that doesn’t mean it’s necessarily the &lt;em&gt;best&lt;/em&gt;
solution.&lt;/p&gt;
&lt;p&gt;Being pragmatic we want to focus on value delivery and get the
smallest unit out quickly to gain knowledge, however, we need to
understand the tradeoffs if we’re to stick with the architecture in the
context of an enterprise which follows the &lt;a
href=&quot;https://aws.amazon.com/architecture/well-architected/?wa-lens-whitepapers.sort-by=item.additionalFields.sortDate&amp;amp;wa-lens-whitepapers.sort-order=desc&amp;amp;wa-guidance-whitepapers.sort-by=item.additionalFields.sortDate&amp;amp;wa-guidance-whitepapers.sort-order=desc&quot;&gt;AWS
Well-Architected Framework&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;considerations&quot;&gt;Considerations&lt;/h3&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/handling-zendesk-events-via-eventbridge/img-4.png&quot;
alt=&quot;Figure 2) Architecture illustration between accounts&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;Figure 2) Architecture illustration
between accounts&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;Although decoupled, the proposed architecture still presents a level
of rigidity because we need to manually update SNS subscriptions for
each consumer. This kind of tight coupling can become an anti-pattern in
event-driven architectures, as it reduces agility and increases
maintenance overhead.&lt;/li&gt;
&lt;li&gt;The second drawback is that this doesn’t empower our downstream
consumers with granularity. We could use Amazon EventBridge’s
cross-account event delivery feature. This approach would allow you to
decouple your event producers and consumers even further, reducing the
need to manage subscriptions directly on SNS topics.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;eb-to-eb&quot;&gt;EB to EB&lt;/h4&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/handling-zendesk-events-via-eventbridge/img-5.png&quot;
alt=&quot;Figure 3) These secondary EBs could be in separate accounts too&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;Figure 3) These secondary EBs could be in
separate accounts too&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Forwarding broad entity events into another EventBridge would allow
for the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Decoupling&lt;/strong&gt;: Cross-account EventBridge removes
the tight coupling of SNS subscriptions and allows better separation of
concerns.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Simplified Management&lt;/strong&gt;: Events are routed
directly to the target accounts’ EventBridge, simplifying the event
routing logic.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Granular Control&lt;/strong&gt;: Each target account can define
its &lt;strong&gt;own rules&lt;/strong&gt; and targets, providing more granular
control over event processing. This is impactful as it could save
significant costs via our consumers not needing to act on irrelevant
data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simple example of the power of this architecture would be a service
only cares about an entity that has specific attributes, e.g. in the
context of a ticket perhaps our service only wishes to ingest if the
tags on a ticket are either foo or bar. With this architecture, we can
use our own rules to make this possible and eliminate our consumers from
receiving irrelevant data.&lt;/p&gt;
&lt;p&gt;However, the architecture is &lt;strong&gt;crucial&lt;/strong&gt; here, if we
applied the above to our architecture in Figure 1 via highly specific
event patterns, this could become an anti-pattern, making the system
difficult to maintain and debug. It’s important to balance granularity
with simplicity, ensuring that patterns are easy to manage over
time.&lt;/p&gt;
&lt;h3 id=&quot;implementation&quot;&gt;Implementation&lt;/h3&gt;
&lt;p&gt;To create this setup we’d use OpenTofu, our preferred IaC tool. To
keep this agnostic we’ll go through the AWS console instead. This
section assumes the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EventBridge resource has been created and successfully connected as
a partnered resource and information is available, there is a guide &lt;a
href=&quot;https://support.zendesk.com/hc/en-us/articles/4408824521114-Setting-up-the-Zendesk-Events-Connector-for-Amazon-EventBridge&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You’ve created an SNS, or supported resource which can connect with
an EB rule.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;steps&quot;&gt;Steps&lt;/h4&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;EventBridge &amp;gt; Rules &amp;gt; Create a rule, and call it
ticket-created&lt;/li&gt;
&lt;li&gt;Event Source &amp;gt; Select ‘Other’&lt;/li&gt;
&lt;li&gt;We can ignore the Sample event section. However, this is useful for
finding pre-existing patterns and testing our event pattern against
specific data shapes.&lt;/li&gt;
&lt;li&gt;In the Event Pattern section, we’ll need to specify what event we
wish to receive, however, an important consideration is depending on
your configuration of Zendesk workflows, the data you require may not be
immediately available, which, because this is event-driven, would mean
we’re missing data. For us, the event of a status change is appropriate
so we’ll use this.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;Support Ticket: Custom Status Changed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means our example will look like the following in Event
Pattern:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode json&quot;&gt;&lt;code class=&quot;sourceCode json&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;quot;detail-type&amp;quot;&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;ot&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Support Ticket: Custom Status Changed&amp;quot;&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;quot;resources&amp;quot;&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;ot&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Support Ticket&amp;quot;&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;5&quot; type=&quot;1&quot;&gt;
&lt;li&gt;&lt;p&gt;You’ll also want to make sure Prefix matching is set in the
dropdown.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, we’ll want to set a target for the data which matches the
above rule, we’re going to choose our SNS, but this could easily be
Kinesis, Lambda or another EventBridge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The SNS will now publish events based upon our resource and
detail ruling, it’s important we test these rules to ensure our event
contains the data we require, as it will be a snapshot of the entity at
the time of changing&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;🤔 Conclusion&lt;/h3&gt;
&lt;p&gt;We’ve gone through setting up an event-driven system, from consuming
SaaS data into our estate for internal consumption and speaking about
the benefits of this being async and event-driven. We’ve also considered
our EventBridge broker in the context of our AWS strategy and how it may
be built upon, speaking about different architecture decisions we could
take and how they would set us up for future success. Finally, we
briefly touched on the high-level implementation using the AWS
Console.&lt;/p&gt;
</description></item>
<item><title>Reimagining Scrum Ceremonies</title><link>https://www.ashgrennan.com/post/reimagining-scrum-ceremonies/</link><guid>https://www.ashgrennan.com/post/reimagining-scrum-ceremonies/</guid><pubDate>Mon, 25 Mar 2024 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;effective-scrum-ceremonies-part-3&quot;&gt;Effective Scrum ceremonies —
Part 3&lt;/h3&gt;
&lt;p&gt;So far, we’ve covered the wins and drawbacks of communication methods
such as writing documents and meetings. Next, we spoke about the heart
of async, written words! We dove into using &lt;a
href=&quot;https://simonsinek.com/golden-circle/&quot;&gt;the golden circle&lt;/a&gt; to
help us stick with recording communications to facilitate discussion and
gain goodies such as inclusivity, thoughtfulness and flexibility.&lt;/p&gt;
&lt;h3 id=&quot;ceremonies&quot;&gt;Ceremonies&lt;/h3&gt;
&lt;p&gt;If you’re following &lt;a
href=&quot;https://www.scrum.org/resources/what-scrum-module&quot;&gt;Scrum&lt;/a&gt;,
you’ll have a series of ceremonies that repeat on a weekly, or bi-weekly
basis. An example of ceremonies for a 2-week sprint in a medium-sized
team:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Standups (15 min) × 10&lt;/li&gt;
&lt;li&gt;Sprint planning (3 hr) × 1&lt;/li&gt;
&lt;li&gt;Sprint Review (2 hr) × 1&lt;/li&gt;
&lt;li&gt;Sprint Retrospective (1.5 hr) × 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That adds up to 9 hours, just over a day, every two weeks for each
member of your team. Against a 38-hour working week (76 hours per
sprint), that’s roughly &lt;strong&gt;12% of 2 weeks on recurring
meetings&lt;/strong&gt;, and it’s pretty typical for these to run over too. We
can agree this is a significant amount of time; it’s therefore worth
exploring how we could improve on them from an efficiency
standpoint.&lt;/p&gt;
&lt;h3 id=&quot;standup-meetings&quot;&gt;Standup Meetings&lt;/h3&gt;
&lt;p&gt;One of the most adopted techniques in &lt;a
href=&quot;http://www.extremeprogramming.org/rules/standupmeeting.html&quot;&gt;Extreme
Programming&lt;/a&gt;, standup meetings generally include:&lt;/p&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;What we did yesterday&lt;/li&gt;
&lt;li&gt;What we’re doing today&lt;/li&gt;
&lt;li&gt;Blockers (if any)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With distributed people and teams, the standup can and does suffer.
It’s quite easy for a standup to run over when everyone is sat down, and
to become an environment to fixate on nuanced issues, derailing its
purpose. With this, engagement can be impacted, we get into a &lt;em&gt;we
have the meeting because we have the meeting&lt;/em&gt; mindset as its
original value and purpose erode.&lt;/p&gt;
&lt;p&gt;Focusing on the numbered list above, here are some methods of
improvement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Write updates&lt;/strong&gt; — Using tooling effectively,
points 1 &amp;amp; 2 can be answered by the board. If they can’t, it’s
either a tooling problem or a human one. Team members should update via
writing comments on stories and moving tickets into lanes / statuses.
What happens when someone is out of the office? Unable to make the
standup? &lt;strong&gt;Relying on sync communication creates frustration and
slows us down.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Flag stories&lt;/strong&gt; — If you have a blocker, you’re
likely already tackling and trying to resolve, not waiting until the
next standup! Flag the ticket if blocked, write a comment explaining
why, default to action, waiting to do this sync slows everything
down.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Actioning the above points, it can become apparent the daily standup
isn’t required. However, the key benefit of these meetings is the
connection opportunity they provide. If your team still want them,
remove the status update. Discuss the next actions, sprint goals, health
check, high risk branches or changes.&lt;/p&gt;
&lt;p&gt;There’s also a benefit of psychological safety via actioning the
above, as the ticket communicates the progress of the story, avoiding
lots of questions from multiple people.&lt;/p&gt;
&lt;h3 id=&quot;sprint-retrospective&quot;&gt;Sprint Retrospective&lt;/h3&gt;
&lt;p&gt;The agile process is all about enabling continuous improvement.
There’s no better way than having a retrospective! It’s a way to surface
issues, celebrate success and enable actions to be taken away for future
improvements. However, run poorly, they can do more harm than good.&lt;/p&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;😕 If they consistently don’t yield meaningful output, people lose
faith in the ceremony&lt;/li&gt;
&lt;li&gt;🔄 We all have a &lt;a
href=&quot;https://en.wikipedia.org/wiki/Recency_bias&quot;&gt;recency bias&lt;/a&gt;,
recording events that &lt;em&gt;recently&lt;/em&gt; happened, leaving more historic
issues to happen again&lt;/li&gt;
&lt;li&gt;⏱️ Running out of time, due to poor grouping, lack of voting, or
absence of a theme&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All these issues can be mostly solved by… you guessed it, async
means. Here’s how we’ll make our retros more effective, efficient, and
purposeful.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Capture inputs&lt;/strong&gt; — Enable and encourage the habit
of everyone writing topics for the next retro throughout the sprint,
these could be in a Google Form, doc, or even in their favourite note
taking app. These can be sent to the facilitator of the retro if
anonymity is not a concern.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Group inputs&lt;/strong&gt; — The host, ahead of time, groups
related topics. If this was a Miro board then sticky notes in clusters
achieves this. This helps with point 2.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Voting&lt;/strong&gt; — This enables focus and prioritises the
topics people feel strongly about. This helps with point 3. As this is
usually a quick process, it can be done before or during the
meeting.&lt;/p&gt;
&lt;h3 id=&quot;sprint-planning&quot;&gt;Sprint Planning&lt;/h3&gt;
&lt;p&gt;It’s a heavyweight ceremony and, unlike others, it involves many
different subprocesses. Discussing these would require an article of its
own. So, let’s discuss this purely from an async, efficiency
perspective. Here’s how we can streamline our planning sessions.&lt;/p&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;Have some form of async backlog refining where time is set aside to
enhance stories via metrics or more detail, ready for the sprint.&lt;/li&gt;
&lt;li&gt;Tightly prioritise the backlog for the function of increasing focus
around outcomes.&lt;/li&gt;
&lt;li&gt;Define the sprint goals before the meeting, the Product Owner /
Engineering Manager can draft this, it will give natural shape to the
start of the meeting. &lt;strong&gt;Make this known before the meeting to
all.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Encourage team members to look through the draft of stories to bring
into the sprint async, here there’s the opportunity to &lt;strong&gt;write
comments&lt;/strong&gt; in the stories for clarification.&lt;/li&gt;
&lt;li&gt;Have the meeting.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Doing the above will yield the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Allows the team to think deeply about stories, thus creating
better outcomes such as highlighting potential issues.&lt;/li&gt;
&lt;li&gt;✅ We’ll have a list of questions already available to discuss
during the meeting, this is huge.&lt;/li&gt;
&lt;li&gt;✅ Reduce risk that you’re pursuing the wrong thing.&lt;/li&gt;
&lt;li&gt;✅ We’ll already have thought about the sprint goal and seen the
approximate stories, which will give a better confidence indicator on if
the goal is achievable, or requires reduction.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;We’ve discussed simplifying each ceremony with straightforward,
asynchronous actions. As a result, we’re creating better fuel to
accelerate meeting outcomes, ensuring we get the best out of each sync
interaction. Furthermore, it enables contributions when it suits the
individual and unlocks thoughtfulness, which can create better, faster
delivery and greater working software.&lt;/p&gt;
</description></item>
<item><title>The synchronous analysis of meetings</title><link>https://www.ashgrennan.com/post/the-synchronous-analysis-of-meetings/</link><guid>https://www.ashgrennan.com/post/the-synchronous-analysis-of-meetings/</guid><pubDate>Fri, 08 Mar 2024 00:00:00 +0000</pubDate><description>&lt;h3
id=&quot;balancing-sync-and-async-communication-in-the-remote-work-era-part-1&quot;&gt;Balancing
sync and async communication in the remote work era — Part 1&lt;/h3&gt;
&lt;p&gt;Let’s set the stage. In a distributed work environment, getting the
balance between synchronous and asynchronous communication is difficult.
This post discusses my experience during my career in tech around the
nuances of both sync/async communication forms, exploring their
benefits, trade-offs, and stating strategies to help optimise
meetings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sync communication&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/the-synchronous-analysis-of-meetings/img-2.png&quot;
alt=&quot;We can draw parallels between cow milk and sync communication&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;We can draw parallels between cow milk
and sync communication&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;✅ Fast nourishment&lt;/li&gt;
&lt;li&gt;✅ Perishable&lt;/li&gt;
&lt;li&gt;✅ Easily digestible (&lt;em&gt;for most&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Async communication&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/the-synchronous-analysis-of-meetings/img-3.png&quot;
alt=&quot;Async communication is similar to an alcoholic spirit&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;Async communication is similar to an
alcoholic spirit&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;✅ Harder to produce&lt;/li&gt;
&lt;li&gt;✅ Strong and potent&lt;/li&gt;
&lt;li&gt;✅ Lasts a very long time&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;a-recap&quot;&gt;A recap&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;But we’re already using asynchronous communications and tooling&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Communication isn’t generally benchmarked in companies and their
application and efficiency are not known. Businesses are often so
focused on delivering value for customers or stakeholders that the tools
we use to do that aren’t scrutinised.&lt;/p&gt;
&lt;p&gt;Many of us thought, and correctly believed for a short time, that
working remotely would enable us to concentrate, no noise or
interruptions we thought! What in reality happened was that the noise
and interruptions followed in the form of meetings. It’s a behavioural
problem, it’s us.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/the-synchronous-analysis-of-meetings/img-4.gif&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Let’s recap the tradeoffs and benefits for meetings. This isn’t an
exhaustive list but it’s good to remind ourselves of some:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;👌 &lt;strong&gt;Easy:&lt;/strong&gt; This isn’t a direct flaw, it’s a culture /
behaviour one, but it’s worth restating. In a distributed company they
are the easiest communication option, making them prone to misuse.&lt;/li&gt;
&lt;li&gt;️ 🗣 &lt;strong&gt;Loudness&lt;/strong&gt;: In decisions we want the best idea to
win, not the loudest voice, with the nature of meetings that’s a hard
thing to achieve, not everyone wants to voice their opinions due to
nature, language, or simply needing to articulate, it’s not conducive to
being inclusive.&lt;/li&gt;
&lt;li&gt;💸 &lt;strong&gt;Can be expensive&lt;/strong&gt;: Does the cost of the time the
meeting requires create enough value to justify itself? There’s also
opportunity costs here too, is this delaying you from working on
something with higher value?&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Quick decisions:&lt;/strong&gt; The quality of our decisions
depends upon the quality of our thoughts. When we’re optimising for fast
thinking via communicating in a synchronous manner, it’s not possible to
ponder and think deeply, we use our experience which is biased by what
we’ve done before.&lt;/li&gt;
&lt;li&gt;👥 &lt;strong&gt;Inclusivity:&lt;/strong&gt; For people working in a second
language, following a fast conversation means translating what others
say while also assembling their own thoughts in a language they’ve
adopted. I have been told this is incredibly difficult.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A few benefits for meetings include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🔗 &lt;strong&gt;Connection:&lt;/strong&gt; Working for extended periods in
isolation whilst offering focus can be at the detriment of why we’re
doing what we’re doing in the first place. Meetings remind us of why,
our mission, and wanting to support and achieve outcomes as a
collective.&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Focusing on now:&lt;/strong&gt; If something blows up, or a
critical decision needs to be made, meetings make an excellent tool to
agree a course of action efficiently.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s one other large area which has been intentionally missed
because it can be both a strength and detriment which is breadth /
depth.&lt;/p&gt;
&lt;h3 id=&quot;meeting-etiquette-evolve-meetings&quot;&gt;Meeting etiquette — evolve
meetings&lt;/h3&gt;
&lt;p&gt;We could immediately descend into async ways of communication and
move &lt;em&gt;some&lt;/em&gt; of our existing communications to leverage async,
however there’s an issue that extends past simply taking action.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/the-synchronous-analysis-of-meetings/img-5.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Adopting a different communication style isn’t something that’s easy,
we need strong relationships in the team, understanding and agreement in
ways of working, resolving ceremonies, having conversations with
stakeholders and the list goes on. Instead, let’s look at first
improving what’s already there.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;No agenda, no attenda:&lt;/strong&gt; If you get invited to a
meeting which doesn’t have an agenda, this would imply that the
objectives and outcomes for this meeting haven’t been thought about.
Sure, the creator could have forgotten, but encouraging the habit by
asking for an agenda forces the exercise of thinking about the meeting’s
purpose.&lt;/li&gt;
&lt;li&gt;🍲 &lt;strong&gt;Preparation:&lt;/strong&gt; Share any artifacts such as
presentations, documents or video. Rather than the first half of the
meeting being trying to understand the problem or scenario, instead it
can start with clarifying questions, enabling better, faster
output.&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Start with information:&lt;/strong&gt; If people aren’t used to
reading documents before meetings, Jeff Bezos has spoken about a
technique, referring to it as “&lt;a
href=&quot;https://www.cnbc.com/2019/10/14/jeff-bezos-this-is-the-smartest-thing-we-ever-did-at-amazon.html&quot;&gt;The
smartest thing we ever did at Amazon&lt;/a&gt;”. This involves meeting
attendees spending the first 5–10 minutes of each meeting reading a well
prepared memo, everyone in the meeting does this and then the verbal
part of the meetings begins.&lt;/li&gt;
&lt;li&gt;⏺️ &lt;strong&gt;Allow thought recording:&lt;/strong&gt; Allowing people to
write their thoughts in a collaborative document can help those who
otherwise wouldn’t contribute to the discussion. There are incredible
benefits for such a simple action, greater inclusion and stronger
decision output due to a greater collective.&lt;/li&gt;
&lt;li&gt;📃 &lt;strong&gt;Document the outcomes&lt;/strong&gt;: This is a no-brainer: not
only does this have all the async benefits like write once, read by many
but it’s incredibly important to give people context for moving forward
on a decision if they’re unable to attend. It’s more efficient than
recording a meeting unless a verbatim record of what happened is
required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You may have noticed something about these points, each one leverages
async communications in some manner, we’re addressing some of the
weaknesses of sync by applying async tools and patterns.&lt;/p&gt;
&lt;h3 id=&quot;the-scheduled-meetings&quot;&gt;The scheduled meetings&lt;/h3&gt;
&lt;p&gt;What I’ve observed happening over time is the &lt;a
href=&quot;https://en.wikipedia.org/wiki/Boiling_frog&quot;&gt;boiling frog&lt;/a&gt;.
You’ll be involved in initiatives over your career which lead to
scheduled meetings. This slowly reduces the amount of hours available in
your working week and before you know it, a &lt;strong&gt;third&lt;/strong&gt; of
your working hours are eaten by scheduled meetings! You’re
metaphorically boiling alive without realising it, as the number of
working hours available for tasks gradually decreases.&lt;/p&gt;
&lt;p&gt;It’s an outdated viewpoint that being in meetings therefore means
you’re busy, thus in turn being productive. So, how to weed our
calendars? Here’s what I’ve found useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📰 &lt;strong&gt;Importance of an agenda:&lt;/strong&gt; We’ve already covered
this above, what exactly is the meeting’s purpose? Is it just
transferring information? Well, that sounds like documenting would be a
great fit here or a tool to post and distribute status updates.&lt;/li&gt;
&lt;li&gt;🙆 &lt;strong&gt;Participation:&lt;/strong&gt; How often do you actively
participate in the meeting? Are you just attending the meeting because
it’s in your calendar and that’s what you’re supposed to do?&lt;/li&gt;
&lt;li&gt;👍 &lt;strong&gt;Feedback:&lt;/strong&gt; If attendance is not mandated, are you
passionate about the topic? Is it making you better? Give feedback to
the meeting owner first if you’re thinking about removing it from your
calendar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not all scheduled meetings are bad, far from it. However, we need to
be objective again. Many long-running scheduled meetings &lt;strong&gt;lose
their impact&lt;/strong&gt; and deliver marginal value over time, it’s easy to
diverge from the initial intent. Many companies do forms of &lt;a
href=&quot;https://www.worklife.news/culture/meeting-purge/&quot;&gt;meeting
resets&lt;/a&gt; to combat this problem.&lt;/p&gt;
&lt;h3 id=&quot;next-time&quot;&gt;Next time ⏭️&lt;/h3&gt;
&lt;p&gt;We’ll be exploring what async looks like for ceremonies and some of
the issues around ‘async’ tooling!&lt;/p&gt;
</description></item>
<item><title>Unlock async communication for engineering teams — Part 2</title><link>https://www.ashgrennan.com/post/unlock-async-communication-for-engineering-teams-part-2/</link><guid>https://www.ashgrennan.com/post/unlock-async-communication-for-engineering-teams-part-2/</guid><pubDate>Fri, 08 Mar 2024 00:00:00 +0000</pubDate><description>&lt;p&gt;In this post, we’ll address the ‘bus factor’ and practical steps for
creating asynchronous habits. Finally, we’ll be walking the path of
creating a value system for async working by adopting a framework to aid
in defining why we want to do this and most importantly, create some
sticking power.&lt;/p&gt;
&lt;h3 id=&quot;encouraging-behaviours-why-async&quot;&gt;Encouraging behaviours, why
async&lt;/h3&gt;
&lt;p&gt;Let us begin with an example of the &lt;a
href=&quot;https://www.getclockwise.com/blog/what-is-your-bus-factor&quot;&gt;bus
factor&lt;/a&gt;. How many people leaving your team would impact value
delivery? How many would halt support capabilities? This can be a
sobering exercise. Sure, teams adapt to change, but wouldn’t it be nice
if no additional cognitive load were placed on colleagues during such a
loss? Wouldn’t it be ideal if velocity didn’t drop and your team were
just as capable when dealing with incidents?&lt;/p&gt;
&lt;p&gt;This is one of an abundant pool of reasons why async for information
capture and delivery is great. The higher the velocity an organisation
has, the less long-term memory it has by that very nature. Think of this
as a sliding window moving forward, but not expanding. We have a remedy
for such a symptom: write.&lt;/p&gt;
&lt;p&gt;Consider the wisdom of former &lt;a
href=&quot;https://en.wikipedia.org/wiki/Lewis_E._Platt&quot;&gt;HP CEO Lew Platt&lt;/a&gt;
who encapsulates the point beautifully:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If only HP knew what HP knows&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;taking-action&quot;&gt;Taking Action&lt;/h3&gt;
&lt;p&gt;Previously, we spoke about &lt;strong&gt;enhancing&lt;/strong&gt; meetings via
injecting async tools and processes. In this section we’ll touch on
&lt;em&gt;how&lt;/em&gt; to be a force for async, getting started, and building the
habits that make this a part of your daily toolset.&lt;/p&gt;
&lt;h3 id=&quot;starting-with-why&quot;&gt;1) Starting with why&lt;/h3&gt;
&lt;p&gt;You can be motivated and have the tooling to start async, but none of
this will yield value without buy-in from the right people, your team
members. Without this, you’re putting the cart before the horse. It’s
critical that this happens and everyone sees the value.&lt;/p&gt;
&lt;p&gt;To gain buy-in for capturing information which will enable async, we
can use &lt;a href=&quot;https://simonsinek.com/&quot;&gt;Simon Sinek’s&lt;/a&gt; &lt;a
href=&quot;https://simonsinek.com/golden-circle/&quot;&gt;Golden Circle&lt;/a&gt; model by
first defining a ‘Why’ to communicate the purpose and benefits.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/unlock-async-communication-for-engineering-teams-part-2/img-2.jpg&quot;
alt=&quot;Image source: https://simonsinek.com/golden-circle/&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;Image source:
https://simonsinek.com/golden-circle/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A brief example of this for our purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Why (The Purpose)&lt;/strong&gt; — This is the reason you’re doing
this; it has to mean something for everyone involved. Examples could be,
increasing inclusivity, reducing repetition, decreasing knowledge silos,
it shouldn’t be just a procedure (i.e. something you do), but justify
something strategic too (a mission to complete). The company behind
WordPress (&lt;a href=&quot;https://automattic.com/&quot;&gt;Automattic&lt;/a&gt;) uses both
async and the Golden Circle method, their Why is to enable a global
talent pool and maintain continuous productivity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How (The Process)&lt;/strong&gt; — Highlighting tools and how
they’ll be used to enable the Why&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What (The Outcome)&lt;/strong&gt; — What will the outcomes be,
this may be reduction in meeting times, increased innovation, this
should form a single sentence, and have a few lasting, purposeful
outcomes. Without outcomes that can be discussed and measured, you won’t
have a method of knowing if what you’re doing is actually working&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;get-good-at-writing&quot;&gt;&lt;strong&gt;2) Get good at
writing&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/unlock-async-communication-for-engineering-teams-part-2/img-3.gif&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;number one&lt;/strong&gt; tool for transferring information is
written words. In the right medium, a digital document can scale to be
read by millions of people over months or years. It can be versioned,
edited, revisited, translated, searched and made accessible around the
globe in seconds.&lt;/p&gt;
&lt;p&gt;I want to highlight that writing is hard. But, it’s the unlocker for
making async work. Just like starting at the gym after a long hiatus, it
may be difficult at first. But you will get better, it will get easier.
If that’s not enough, think about your &lt;em&gt;Why&lt;/em&gt;, your purpose in
doing this. Is it to empower people who are neurodiverse, non-native
English speakers, introverted? Remind yourself of Why when you don’t
want to write.&lt;/p&gt;
&lt;p&gt;Writing or recording enables more people to be involved in the
conversations, capturing perspectives we would miss otherwise. It’s not
just writing too, media recordings get a noteworthy mention as a medium
that captures non-verbal cues, tonality, and a demonstration of ideas.
Whilst writing gets first place, creating media to express information
is still on the podium.&lt;/p&gt;
&lt;h3 id=&quot;kill-the-mouse&quot;&gt;&lt;strong&gt;3) Kill the mouse&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You’re in a meeting and fortunate enough to have two screens, one
screen has your meeting, the other Google Docs etc. Unless the session
is interactive, you don’t need your mouse. It’s a navigator and we don’t
need to navigate. All it will do is welcome distraction via checking
email, messages, that unit test that won’t pass. Put it out of arm’s
reach.&lt;/p&gt;
&lt;h3 id=&quot;create-a-hub-of-information&quot;&gt;&lt;strong&gt;4) Create a hub of
information&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We need to put this information somewhere, naturally, this depends on
tooling options available. What is important though, is agreeing where
stuff will live, and how you’ll structure it (How). Some examples
include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Meeting minutes&lt;/strong&gt;, the outcomes. Initially, avoid
using AI if viable because you won’t have a benchmark for comparison.
Afterwards, assess whether tools capture key points and action items
more accurately. Are they searchable and summarised? From there your
team can make an informed decision&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ADR (Architectural Decision Records),&lt;/strong&gt; often seen
as a challenge for engineering teams to maintain, they give context and
save a tremendous amount of time reading about the evolution of a
service. Experiment, find a system that works for your team, perhaps
adding an ADR into the projects directory doesn’t work, and that’s ok,
you can just link to the ADRs in your project’s README&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stakeholder decisions&lt;/strong&gt; aka business decisions.
This is a no brainer, if you don’t have written down what you’re aiming
for and agreed timelines, you’re going to get into trouble and create
unwarranted pressure. Ever played the Telephone Game? You’re unwittingly
playing this if things aren’t recorded&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PRs (if applicable),&lt;/strong&gt; Placing links to these
alongside all the information above creates an effective audit trail,
seeing how a change happened relative to a business decision
change&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ideas,&lt;/strong&gt; Have a great idea? Write it down! Put it
in your information hub, enabling your team to review in their own time,
the more complex the idea, the more difficult to discuss synchronously,
enable thoughtfulness around the idea first before verbally
discussing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s more to talk about here, but this is a great start, use
introspection to decipher what you need to know first, define simple
templates, make recording information easy and
&lt;strong&gt;frictionless&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;a-mantra&quot;&gt;&lt;strong&gt;5) A Mantra&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Once you’ve decided what to record (starting small can help) next is
actually taking action, we’ve spoken about how writing is hard; this is
the &lt;strong&gt;remembering to record&lt;/strong&gt; part. Here are some useful
tips on ensuring this happens:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Who’s writing —&lt;/strong&gt; In a meeting / discussion, ask
&lt;strong&gt;at the start&lt;/strong&gt; who’s capturing the information? Don’t
write this in your Notepad or in a medium that’s local to you, always
record in the tool you’ve agreed, else you risk forgetting to
transfer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeat —&lt;/strong&gt; If warranted, at the end of a discussion,
the writer should recap what they’ve written in a summarised manner,
this may take 30 seconds, and not only is it useful to ensure
information is correct but gives everyone a high level recap&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Post it somewhere —&lt;/strong&gt; If you’ve created a new
document and added it to your information hub, consider also posting in
a dedicated IM channel for async updates, this gives a timeline of
discussion and recordings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;respond-with-a-link&quot;&gt;&lt;strong&gt;6) Respond with a
link&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Next on building your knowledge hub: if someone asks for information
that you have to explain, regardless of communication medium, write it
down and send a link. If you commit to doing this, over time you’ll have
more context, history and time-saving capabilities. It’s an investment
that pays dividends.&lt;/p&gt;
&lt;p&gt;There’s also the added win that people don’t receive a slightly
different interpretation or recollection of your thoughts and ideas each
time you state them.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The path to moving async isn’t easy to follow, however, defining our
purpose and commitment to change means we’re starting off strong. We
humans are imitation machines, so vote with your actions and people will
follow. It’s hard to start, but you’ll be creating a better present and
future too.&lt;/p&gt;
</description></item>
<item><title>Modern Software Engineering Book Reflections</title><link>https://www.ashgrennan.com/post/modern-software-engineering-review/</link><guid>https://www.ashgrennan.com/post/modern-software-engineering-review/</guid><pubDate>Tue, 28 Nov 2023 00:00:00 +0000</pubDate><description>&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/modern-software-engineering-review/img-1.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recently, I read &lt;a href=&quot;https://twitter.com/davefarley77&quot;&gt;Dave
Farley’s&lt;/a&gt; Modern Software Engineering &lt;a
href=&quot;https://www.oreilly.com/library/view/modern-software-engineering/9780137314942/&quot;&gt;publication&lt;/a&gt;
which outlines a modern, sensible approach to engineering. It
articulately describes what it means to be an engineer, and how to
tackle engineering challenges.&lt;/p&gt;
&lt;p&gt;Dave’s thought process and views on many topics felt familiar and
similar to my own, not because we’ve worked together, but because I have
the scars of trying many things that didn’t work. Reading each chapter
was akin to catching up with an old colleague who’s been there, has seen
what works and what does not. It was a refreshing read that I highly
recommend.&lt;/p&gt;
&lt;p&gt;Reflecting on Dave’s great work, I decided to write my own views
after being inspired by Dave, and other thought leaders in our
industry.&lt;/p&gt;
&lt;h3 id=&quot;building-incrementally&quot;&gt;⚙️ Building Incrementally&lt;/h3&gt;
&lt;p&gt;Performing heavy upfront analysis and design slows progress; in
simple software this may be OK. However, in the current era of building
scalable, reliable and fault-tolerant systems, simple is rare. Complex
systems aren’t produced by a single mind on the first iteration; instead
they are built incrementally, by learning what works.&lt;/p&gt;
&lt;p&gt;Some ideas which looked great on paper don’t satisfy requirements
because of unexpected unknowns. If you’ve ever worked using waterfall
methodology this was its biggest flaw, with many notable systems turning
into a &lt;a
href=&quot;https://blog.codinghorror.com/the-big-ball-of-mud-and-other-architectural-disasters/&quot;&gt;big
ball of mud&lt;/a&gt;, despite &lt;a
href=&quot;https://en.wikipedia.org/wiki/List_of_failed_and_overbudget_custom_software_projects&quot;&gt;hundreds
of millions&lt;/a&gt; of investment.&lt;/p&gt;
&lt;p&gt;We’re in a unique position as engineers, because there are many
disciplines that can’t just pivot like us, for example, take building a
bridge that spans several miles: a change of plan may be extremely
difficult, and could be impossible due to constraints. As software
engineers, we can delete code and remove resources.&lt;/p&gt;
&lt;h3 id=&quot;performing-engineering&quot;&gt;🔬 Performing Engineering&lt;/h3&gt;
&lt;p&gt;Being a software engineer is far more than just writing code. Getting
architecture and design right for the component you’re working on is
important, iteratively reflecting on approaches, learning what works,
creating hypotheses, experimenting and drawing conclusions is the value
proposition of a good engineer.&lt;/p&gt;
&lt;p&gt;As engineers, we need to select approaches and be aware of what our
choices really mean, what are the tradeoffs we’re making in our
approach? A simple example would be considering &lt;a
href=&quot;https://en.wikipedia.org/wiki/CAP_theorem&quot;&gt;CAP theorem&lt;/a&gt;. We are
trying to organise complexity all the time. It may scale linearly but,
ideally, not exponentially, and it is up to engineers to try and tame
it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Code is the output of good engineering, not the driver of it&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There’s not great value in clever code, we want code to be simple,
expressive, and easy to comprehend. In systems you’re building as part
of a team, you want to use code as a communication tool, things that
aren’t clear can lead to misunderstandings and therefore bugs.&lt;/p&gt;
&lt;p&gt;Compilers are very good at finding optimisations, so whether
something can be written in one or three lines shouldn’t be a concern of
performance, and if in doubt, test!&lt;/p&gt;
&lt;h3 id=&quot;change&quot;&gt;🌻 Change&lt;/h3&gt;
&lt;p&gt;Change is a constant, therefore enabling change should be a
first-class consideration in our builds. Following an agile approach of
small, incremental, iterative change forces us to build in ways that
adhere to principles we’ve already mentioned (modularity, SoC etc), the
payoff from this is that it greatly helps to flatten the cost of change
in our system, not only in its infancy, but across its entire
lifespan.&lt;/p&gt;
&lt;p&gt;As you’ve likely experienced, tightly coupled systems become more
difficult, more expensive and more dangerous to change. Releasing
software like this becomes a scheduled event; because of this, things
take longer and releases are delayed. This is far from ideal: we’re
slowing down the rate at which we can build, measure, and learn.
Meanwhile a competitor that can release more often has a competitive
advantage that compounds over time. They could learn faster, and
accelerate past you.&lt;/p&gt;
&lt;p&gt;So, we agree that change is important, and changes often require
having confidence in our releases. If we are confident that each release
is bug-free to the best of our knowledge, then the time it takes to
release working software (compile, test) should be the only blocker. We
can develop this confidence by following a test-driven approach (TDD);
following this forces good practices into our design and code.&lt;/p&gt;
&lt;p&gt;Furthermore, having a deterministic method of releasing software with
automated testing that increases confidence and can exercise our code at
different levels is only a good thing. A continuous
integration/deployment system is the tool for this; we can include
processes such as creating the binaries for our software, performing
automated tests from unit to E2E, scanning for library vulnerabilities
and so on.&lt;/p&gt;
&lt;p&gt;An effective continuous integration process that runs in a timely
manner is a strategy that enables some of the largest companies in the
world to release thousands of times a day across hundreds of engineering
teams.&lt;/p&gt;
&lt;h3 id=&quot;dogma&quot;&gt;🧚 Dogma&lt;/h3&gt;
&lt;p&gt;It can be easy for a team to fall into a trap of doing what they
know, why? Well, quite simply it’s worked in the past, or it’s been seen
in other projects. A trivial example of this could be installing half a
dozen different ruling configurations. These rules dictate how we write
specific idioms of code such as loops, types, order of function,
documentation, and so on.&lt;/p&gt;
&lt;p&gt;Whilst the intent is good, many of these dictator configs are pulled
from large companies with specific goals, reasons and styling choices.
Do these choices help our team in the specific context of what we’re
trying to achieve? Are we as engineers in agreement of following these
conventions because they’ll help us? It’s for us to decide, not to
blindly follow what has been done before and assume everything applies
to everyone.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Have no respect whatsoever for authority; forget who said it and
instead look what he starts with, where he ends up, and ask yourself,
‘Is it reasonable?’”&lt;/p&gt;
&lt;p&gt;— Richard Feynman, &lt;em&gt;What Do You Care What Other People Think?&lt;/em&gt;
(1988)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s our responsibility as engineers to make decisions based upon
data, this evidence helps us navigate the sea of possibilities and acts
as our compass to guide towards a desired outcome. We can also evaluate
if we are closer, or further away from our end goal too. Basing our
technical decisions on who was the loudest, or had the most conviction,
isn’t how we should work.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;🏁 Conclusion&lt;/h3&gt;
&lt;p&gt;Software engineering is difficult, period. Whilst tooling and
technology are ever advancing, so is the complexity of our software. We
rely on it for mission-critical systems, financial markets, and piloting
all manner of vehicles, and its capabilities are ever increasing. The
expectation even for general systems such as ecommerce is that they
should be available, fast and fault-free regardless of where they are
accessed.&lt;/p&gt;
&lt;p&gt;Things do go wrong, meeting expectations for customers, or complying
with legislation, presents many engineering challenges, and we should
recognise and build resilience and fault tolerance into what we do.&lt;/p&gt;
&lt;p&gt;How we design systems is critical to facilitate change. Good
engineering teams build great software on all frameworks and languages.
However, it’s difficult to overcome the wrong design choices, especially
if they’re cemented in. It is also not enough for our software to be
modular; we need modular organisations too.&lt;/p&gt;
&lt;p&gt;If your ability to release software quickly in order to learn is
blocked by constraints of depending on other teams, you’re not agile.
Being decoupled is a main metric of high-performing teams and
organisations; you can only go as fast as your slowest constraint.&lt;/p&gt;
&lt;p&gt;Finally, we need to internalise the fact that systems impact real
people such as you and me. An error or mistake could have a devastating
impact on a person’s life; it could lead to a refusal of a loan, visa,
mortgage, healthcare and much more.&lt;/p&gt;
</description></item>
<item><title>GitHub Actions — Supercharged</title><link>https://www.ashgrennan.com/post/github-actions-tooling/</link><guid>https://www.ashgrennan.com/post/github-actions-tooling/</guid><pubDate>Wed, 18 Oct 2023 00:00:00 +0000</pubDate><description>&lt;p&gt;Having worked with various CI/CD platforms like Bitbucket, GitLab and
AWS CodePipeline, I’ve come to deeply appreciate GitHub Actions. Its
implementation, in my opinion, surpasses the rest.&lt;/p&gt;
&lt;p&gt;Strengths of GitHub Actions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;♻️ &lt;strong&gt;Reusable workflows&lt;/strong&gt;: This makes it easy to
maintain and replicate processes across projects.&lt;/li&gt;
&lt;li&gt;👾 &lt;strong&gt;Concise YAML syntax&lt;/strong&gt;: For those who grumble about
indentation, a linter can be your best friend.&lt;/li&gt;
&lt;li&gt;🔁 &lt;strong&gt;Dedicated Repositories for Workflow Steps&lt;/strong&gt;: Each
step having its dedicated GitHub repository simplifies documentation
searches and troubleshooting.&lt;/li&gt;
&lt;li&gt;🤼 &lt;strong&gt;Community-Driven Workflows&lt;/strong&gt;: The community’s
involvement ensures continuous improvements and a wealth of shared
knowledge.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Migrating to GitHub Actions, both for personal and professional
projects, has been a mostly seamless experience. There were occasional
bumps, like misplaced artifact locations, but the overall impression
remains positive. So, where are the pitfalls?&lt;/p&gt;
&lt;h3 id=&quot;the-challenges-with-github-actions&quot;&gt;The Challenges with GitHub
Actions&lt;/h3&gt;
&lt;p&gt;Imagine assembling a flat-pack furniture item. The instructions are
clear, and assembling the pieces feels intuitive. However, ensuring that
you’ve used the right screw or bolt in the correct location can be a
tedious process. To know if you did it right, someone would need to
stress-test the assembled piece. Not the most efficient method,
right?&lt;/p&gt;
&lt;p&gt;Consider this GitHub Actions step:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
        with:
          ref: ${{ inputs.relase-tag }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Did you spot the issue? Despite inputs containing a release-tag, it’s
mistakenly referenced as relase-tag.&lt;/p&gt;
&lt;p&gt;Result? A failed build 🤦.&lt;/p&gt;
&lt;h3 id=&quot;tools-to-the-rescue&quot;&gt;Tools to the Rescue&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/github-actions-tooling/img-1.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There are some great tools out there that can help avoid the dreaded
Process completed with exit code 1 error.&lt;/p&gt;
&lt;h3 id=&quot;actionlint&quot;&gt;✨ 1) Actionlint&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/github-actions-tooling/img-2.gif&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Actionlint is a robust static checker for GitHub Actions workflow
files. It ensures your definitions are valid and sidesteps common
pitfalls. Its features include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Expression Checks:&lt;/strong&gt; It ensures that GitHub Actions’
special expression syntax, ${{ }}, is correctly formed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Syntax Verification:&lt;/strong&gt; Useful for identifying basic
YAML syntax errors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency Checks:&lt;/strong&gt; It verifies your job
dependencies to ensure that ‘needs’ references in a job correctly point
to an existing job&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema Validation:&lt;/strong&gt; Confirms that events, jobs, and
steps are correctly defined&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Download the binary from its official &lt;a
href=&quot;https://github.com/rhysd/actionlint&quot;&gt;repository&lt;/a&gt; and run
actionlint in your workflow directory. Its swift analysis makes it an
ideal addition to git hooks.&lt;/p&gt;
&lt;h3 id=&quot;vs-code-extension-github-actions&quot;&gt;✨ 2) VS Code Extension —
GitHub Actions:&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/github-actions-tooling/img-3.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This &lt;a
href=&quot;https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-github-actions&quot;&gt;extension&lt;/a&gt;
for VS Code enhances your GitHub Actions experience by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Offering &lt;strong&gt;Autocomplete and IntelliSense&lt;/strong&gt; for GitHub
Actions YAML.&lt;/li&gt;
&lt;li&gt;Providing an &lt;strong&gt;Intuitive Interface&lt;/strong&gt; to visualize
workflows, view run details, and get immediate status feedback.&lt;/li&gt;
&lt;li&gt;Showcasing &lt;strong&gt;Inline Error and Warning Annotations&lt;/strong&gt;
directly in the editor.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;nektosact&quot;&gt;✨ 3) nektos/act:&lt;/h3&gt;
&lt;p&gt;Want to emulate GitHub Actions on your local machine? &lt;a
href=&quot;https://github.com/nektos/act&quot;&gt;nektos/act&lt;/a&gt; is your solution. It
offers rapid feedback without the need for pushes, and for those mindful
of costs, it’s a saver 💰.&lt;/p&gt;
&lt;p&gt;The tool utilizes Docker containers to emulate GitHub Actions
runners, ensuring environment consistency. However, bear in mind that
sometimes these containers may lag behind the continually evolving
GitHub ecosystem. But for experimentation specific to GHA? It’s an
excellent tool which creates a rapid feedback loop. There’s a great
guide to get running &lt;a href=&quot;https://nektosact.com/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Harnessing the power of GitHub Actions combined with these tools can
streamline your CI/CD process. By preemptively catching errors and
refining workflows, you’ll spend less time troubleshooting and more time
deploying.&lt;/p&gt;
</description></item>
<item><title>A Waste of Time</title><link>https://www.ashgrennan.com/post/a-waste-of-time/</link><guid>https://www.ashgrennan.com/post/a-waste-of-time/</guid><pubDate>Mon, 28 Aug 2023 00:00:00 +0000</pubDate><description>&lt;p&gt;“That’s a waste of time.” We’ve all heard it, and most of us have
said it.&lt;/p&gt;
&lt;p&gt;Outside of collaboration, the application of the phrase has an air of
contempt, disapproval or even disgust about activities or life choices
that an individual is engaging in.&lt;/p&gt;
&lt;p&gt;Many people believe they possess superior wisdom and, therefore, feel
qualified to offer such advice. However, this is often not the case.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;N.B. In the context of business when you’re trying to achieve an
outcome, the phrase is perfectly valid and helps to create discussion
around focusing on the right problem or trying to ensure value delivery
against time. This post is about unwelcome opinions about how we spend
our own time.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;example-activities&quot;&gt;Example activities&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;🎮 Gaming is a waste of time&lt;/li&gt;
&lt;li&gt;🏊‍♂️ Swimming (or, insert your chosen sport) is a waste of time&lt;/li&gt;
&lt;li&gt;🎶 Creating music is a waste of time&lt;/li&gt;
&lt;li&gt;🎭 Performing arts is a waste of time&lt;/li&gt;
&lt;li&gt;🏫 That degree is a waste of time&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;no-youre-not-more-enlightened-the-loudest-voice-in-the-room&quot;&gt;No,
you’re not more enlightened — the loudest voice in the room&lt;/h3&gt;
&lt;p&gt;If you were to challenge a person that said this to quantify what a
‘waste of time’ actually was, you’d get some answers in the region
of:&lt;/p&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;Doesn’t have an output&lt;/li&gt;
&lt;li&gt;Could be doing ‘better’ things with the time&lt;/li&gt;
&lt;li&gt;Because you’re just sat there&lt;/li&gt;
&lt;li&gt;There’s no money in it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is all wrong of course, but where does this mindset come from?
There’s a prevalent societal notion: first get educated, then land a
job. That’s it. Therefore, if we aren’t working towards this, some
activities are construed as ‘time wasting’ during our younger life.&lt;/p&gt;
&lt;h3 id=&quot;the-joy-of-chance&quot;&gt;The joy of chance&lt;/h3&gt;
&lt;p&gt;The only way you could define whether something was a waste of time
would be to be an all-knowing deity who knew the beginnings and ends of
everything for an individual and their choices. I’d argue even this is
&lt;em&gt;very&lt;/em&gt; tricky due to &lt;a
href=&quot;https://en.wikipedia.org/wiki/Butterfly_effect&quot;&gt;the butterfly
effect&lt;/a&gt;, and negative outcomes have some positive ones.&lt;/p&gt;
&lt;h3 id=&quot;into-chaos-theory&quot;&gt;Into Chaos Theory&lt;/h3&gt;
&lt;p&gt;Every micro decision we make shapes our life, for better or worse,
but ultimately we can’t know that. Therefore, how could someone
&lt;strong&gt;know&lt;/strong&gt; an action can’t justify the time it takes? Taking
time to do things that give us joy is not only the best thing for us,
but can lead to making connections, relationships and all the things
that make us human.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ryan, a shy, somewhat introverted young man binge-watches a TV show
by his favourite director&lt;/li&gt;
&lt;li&gt;A few weeks later, there’s a conversation at a work lunch, and Ryan
unusually jumps into it&lt;/li&gt;
&lt;li&gt;There’s someone else who also adores the show and the director, from
this a friendship is formed&lt;/li&gt;
&lt;li&gt;Several months on Ryan is invited to his co-worker’s (now friend’s)
BBQ, he gets talking to a girl…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The ‘waste of time’ of watching a TV show led Ryan to friendships and
opportunities he wouldn’t have had otherwise.&lt;/p&gt;
&lt;h3 id=&quot;in-defence-of&quot;&gt;In defence of&lt;/h3&gt;
&lt;p&gt;However, despite the criticism, there could be some justification
here. One could argue that excessive time investment in a particular
task is what they’re trying to highlight, but how do you define
excessive? I think the simplest test is whether it comes at the
detriment of everything else, or of activities that likely have equal or
greater value than the one being pursued. Diminishing returns could also
play a factor; think of the 80/20 rule.&lt;/p&gt;
&lt;p&gt;Sometimes, putting most of your time into one thing is the best
course, but as a default choice? Other things will suffer when it takes
a higher percentage of the 24 hours we have in a day.&lt;/p&gt;
&lt;h3 id=&quot;to-conclude&quot;&gt;To conclude&lt;/h3&gt;
&lt;p&gt;Telling people they’re wasting their time is the wrong approach. If
you wish to help, perhaps suggest ideas to nudge the individual into a
less weighted time expenditure.&lt;/p&gt;
&lt;p&gt;There’s quite an irony in writing this post, but not knowing where it
will lead is the exact reason it should exist.&lt;/p&gt;
</description></item>
<item><title>Lambda Power Tuning Case Study</title><link>https://www.ashgrennan.com/post/lambda-power-tuning-case-study/</link><guid>https://www.ashgrennan.com/post/lambda-power-tuning-case-study/</guid><pubDate>Sat, 18 Jun 2022 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;intro&quot;&gt;Intro&lt;/h3&gt;
&lt;p&gt;There are two metrics for costing an AWS Lambda invocation, the
memory size and running duration. Since we’re charged by GB-seconds in
Lambda, the &lt;strong&gt;faster our lambda executes, the less we’ll be
charged&lt;/strong&gt;. Because of this, we &lt;em&gt;could&lt;/em&gt; increase memory
(and therefore more vCPU), yielding faster computation and therefore a
decrease in execution time, &lt;em&gt;and&lt;/em&gt; pay &lt;strong&gt;less&lt;/strong&gt;.
Interesting, right?&lt;/p&gt;
&lt;h3 id=&quot;measuring&quot;&gt;Measuring&lt;/h3&gt;
&lt;p&gt;Whilst an interesting problem, this is difficult to measure since
we’ll need many invocations to produce something statistically
significant, and then repeat this for many memory values. Fortunately
for us, the &lt;a
href=&quot;https://github.com/alexcasalboni/aws-lambda-power-tuning&quot;&gt;lambda
power tuning&lt;/a&gt; tool exists which invokes your AWS Lambda against
different memory settings and fires n invocations and records the
execution time using a state machine run as Step Functions.&lt;/p&gt;
&lt;p&gt;From this data a visualization is generated displaying execution
times and average cost of the configuration. Neat. An example can be
seen below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/lambda-power-tuning-case-study/img-1.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;👉 How to interpret the chart above: execution time goes from 35s
with 128MB to less than 3s with 1.5GB, while being 14% cheaper to
run.&lt;/p&gt;
&lt;h3 id=&quot;scenario&quot;&gt;Scenario&lt;/h3&gt;
&lt;p&gt;We’ll test this against a &lt;strong&gt;real&lt;/strong&gt; Lambda service used
over at &lt;a href=&quot;https://www.moonpig.com&quot;&gt;Moonpig&lt;/a&gt;. From digging
around in logs I observed a specific non-I/O operation appeared quite
slow and likely CPU-bound, a great case for the power tuning tool!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/lambda-power-tuning-case-study/img-2.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Note: I got too aggressive with cropping the above, the Y-axis is
response times, you’ll see this on additional graphs, sorry!&lt;/p&gt;
&lt;p&gt;Against &lt;strong&gt;30 invocations&lt;/strong&gt; per memory setting, the
initial results were disappointing since increasing memory should yield
something &lt;em&gt;fairly&lt;/em&gt; linear, however, that’s not the case. After
some digging, a third-party service had varied response times for each
call, therefore, skewing the results.&lt;/p&gt;
&lt;p&gt;Since the goal of this is to determine optimal CPU usage and I/O
operations are not a factor, &lt;strong&gt;all I/O calls were removed&lt;/strong&gt;
to create a more deterministic computation, allowing us to focus on the
specific code computation mentioned above. Any HTTP calls were
mocked.&lt;/p&gt;
&lt;h3 id=&quot;round-2&quot;&gt;Round 2&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/lambda-power-tuning-case-study/img-3.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This produced the desired result, with a much more linear
progression, costs remaining similar throughout each configuration due
to consistent time reductions.&lt;/p&gt;
&lt;p&gt;Since the higher value memory performed best, a macro-level approach
was taken, starting from the best result of 1536MB in the previous test
and incrementally scaling up to 3072MB. The x-axis shows all values
used.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/lambda-power-tuning-case-study/img-4.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Depending on optimization preference on cost vs performance, we can
see 1792MB being the lowest cost. At 2240MB we save &amp;lt; 10ms, but the
additional cost isn’t worth it for a service that isn’t
latency-sensitive in this case.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;From our deterministic setup, we’ve improved from 900ms to less than
100ms whilst fractionally reducing cost.&lt;/p&gt;
&lt;p&gt;This is a time reduction of 88.8% 🔥 for the computation without much
work. Given how easy it is to do this, I would encourage you to give it
a try.&lt;/p&gt;
</description></item>
<item><title>Setup Local Serverless Environment with npm packages</title><link>https://www.ashgrennan.com/post/local-serverless-environment/</link><guid>https://www.ashgrennan.com/post/local-serverless-environment/</guid><pubDate>Tue, 04 Jan 2022 00:00:00 +0000</pubDate><description>&lt;p&gt;You have written some Serverless IaC and have created several Lambdas
which need to either read or write to DynamoDB, the Lambdas are proxied
through an API gateway. You want the ability to test your code locally
whilst developing functionality and easily give other developers the
same ability just by cloning and running the project.&lt;/p&gt;
&lt;p&gt;You have different environments (beta/staging/production) and would
like an env for local development to easily use specific configs.&lt;/p&gt;
&lt;p&gt;An example of this all working can be found here 👉 &lt;a
href=&quot;https://github.com/ash-grennan/serverless-offline-with-dynamodb&quot;
class=&quot;uri&quot;&gt;https://github.com/ash-grennan/serverless-offline-with-dynamodb&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;plugins&quot;&gt;Plugins&lt;/h3&gt;
&lt;p&gt;For this we’re going to install several Serverless plugins, these
provide additional functionality and cater for many common scenarios
that may arise during development or simply enhancing IaC.&lt;/p&gt;
&lt;p&gt;&lt;a
href=&quot;https://www.serverless.com/plugins/serverless-offline&quot;&gt;Serverless
Offline&lt;/a&gt; — Used to locally run Lambdas and API Gateway, will provide
an endpoint per Lambda and supports hot reloading during
development.&lt;/p&gt;
&lt;p&gt;&lt;a
href=&quot;https://www.serverless.com/plugins/serverless-dynamodb-local&quot;&gt;Serverless
DynamoDB Local&lt;/a&gt; — This will create an emulation of DynamoDB locally
which is required for the Lambdas in this scenario.&lt;/p&gt;
&lt;p&gt;It’s also worth mentioning that DynamoDB local will require JRE which
can be found &lt;a href=&quot;https://www.java.com/en/download/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h3&gt;
&lt;p&gt;First, you’ll want to install those plugins as dev dependencies,
so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i --save-dev serverless-offline serverless-dynamodb-local&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, you’ll want to locate the plugins section (this is present in
most Serverless templates) and add those plugins, it should look like
this in TypeScript:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  service: &amp;quot;serverless-offline-with-dynamodb&amp;quot;,
  frameworkVersion: &amp;quot;2&amp;quot;,
  plugins: [
    &amp;quot;serverless-esbuild&amp;quot;,
    &amp;quot;serverless-offline&amp;quot;,
    &amp;quot;serverless-dynamodb-local&amp;quot;,
  ],
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Awesome, now we’ll run our Serverless offline plugin, we’ll pass in a
stage environment since some of our IaC expects a stage variable such as
our &lt;a
href=&quot;https://github.com/ash-grennan/serverless-offline-with-dynamodb/blob/821939ef4ec7b6b02779dc67a0a126e7cf9b0a80/serverless.ts#L39&quot;&gt;table&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;serverless offline --stage=local&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the offline plugin has run the Serverless successfully, you’ll get
a list of API GW endpoints similar to the below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET  | http://localhost:3000/local/api/customer/{id}
POST  | http://localhost:3000/local/api/customer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, if the Lambda invoked requires access to DynamoDB, you’ll
receive an error. This is because we need to create our local db
instance &lt;strong&gt;and&lt;/strong&gt; point our calling code to the local
address of this emulation, let’s do that now.&lt;/p&gt;
&lt;p&gt;To do this, we’ll wrap DocumentClient in an abstraction and simply
use this in any calling code, within this facade we’ll simply use a
condition to change the DynamoDB location depending on whether it’s
local.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const getClient = (stage: string) : DocumentClient =&amp;gt; {
    if (stage == &amp;quot;local&amp;quot;) {
        return new DocumentClient({
            region: &amp;#39;localhost&amp;#39;,
            endpoint: &amp;#39;http://localhost:8000&amp;#39;
          })
    }
    return new DocumentClient({ region: &amp;quot;eu-west-2&amp;quot; });
}

export default getClient; &lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Side note: region should be from your config, we’re skipping some
steps since it’s out of scope for this article&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And then our calling code would look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const documentClient = createClient(process.env.ENVIRONMENT);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;serverless dynamodb start --stage=local&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything has been run correctly, your Lambda will now be able to
speak to your DynamoDB. We can look at our emulation via installing &lt;a
href=&quot;https://docs.aws.amazon.com/amazonDynamoDB/latest/developerguide/workbench.settingup.html&quot;&gt;NoSQL
Workbench for DynamoDB&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once installed, go Operation builder &amp;gt; + Add Connection and select
DynamoDB local tab, it should look like the below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/local-serverless-environment/img-2.png&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;seed-data&quot;&gt;Seed Data&lt;/h3&gt;
&lt;p&gt;Fortunately, it’s trivial to add seed data as part of our start
command for DynamoDB, create a JSON file and add the JSON objects to an
array:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
    {
        &amp;quot;customerId&amp;quot;: &amp;quot;ec3808fb-ecb7-48b6-8d82-67529ad0c6ef&amp;quot;,
        &amp;quot;firstName&amp;quot;: &amp;quot;Ash&amp;quot;,
        &amp;quot;lastName&amp;quot;: &amp;quot;Grennan&amp;quot;
    },
    ...
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we’ll want to define a &lt;a
href=&quot;https://github.com/ash-grennan/serverless-offline-with-dynamodb/blob/main/serverless/configs/dynamodbConfig.ts&quot;&gt;DynamoDB
config&lt;/a&gt;, in our scenario this will only be used for our local
environment but could be extended to include additional configuration
across many environments.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    seed: {
      local : {
        sources: {
          table: &amp;quot;local-customers&amp;quot;,
          sources: [&amp;quot;./seed-data.json&amp;quot;]
        }
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run the below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;serverless dynamodb start --migrate --stage=local&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The table will now contain JSON objects which can be verified by
NoSQL Workbench.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;One of the strengths of this approach is that another developer can
simply run 3 terminal commands and be running immediately whilst
leveraging the existing IaC.&lt;/p&gt;
&lt;p&gt;Naturally, there are drawbacks, for instance, Serverless offline
cannot emulate &lt;em&gt;everything&lt;/em&gt;, an example of this would be &lt;a
href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html&quot;&gt;schema
definitions for API GW models&lt;/a&gt;, in which case you’d want something
more robust such as &lt;a
href=&quot;https://github.com/localstack/localstack&quot;&gt;LocalStack&lt;/a&gt;, which
has licensed versions providing even more functionality, such as
IAM.&lt;/p&gt;
&lt;p&gt;In case you missed above, here’s the &lt;a
href=&quot;https://github.com/ash-grennan/serverless-offline-with-dynamodb&quot;&gt;GitHub
link&lt;/a&gt; to the example 👊.&lt;/p&gt;
</description></item>
<item><title>AWS Serverless FTP File Processing Service 👊</title><link>https://www.ashgrennan.com/post/serverless-ftp-file-processing-service/</link><guid>https://www.ashgrennan.com/post/serverless-ftp-file-processing-service/</guid><pubDate>Tue, 28 Dec 2021 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;In this article, we aim to tackle a common business task which is
&lt;strong&gt;file processing&lt;/strong&gt; 📂. If you’ve been in development for
some time, it’s quite probable you’ve needed to grab a file from a
resource (such as an FTP server), perform a read/write operation, and
store the results in some form of managed data structure such as a
DBMS.&lt;/p&gt;
&lt;p&gt;From batch scripts to Windows Services 💻, there are &lt;em&gt;dozens&lt;/em&gt;
of ways to accomplish this. Let’s see how we can take a modern approach
using AWS resources.&lt;/p&gt;
&lt;h3 id=&quot;the-scenario&quot;&gt;The Scenario&lt;/h3&gt;
&lt;p&gt;Acme Corporation 💥 need a &lt;u&gt;file processing system&lt;/u&gt;. This needs
to pick up CSV files from an FTP server and read the data, and each file
requires an external API check on some fields. It’s been known for the
CSV files to have issues in the past regarding their formatting, the
system should flag files it cannot process for manual inspection. The
system should continue to process other files regardless of individual
file issues.&lt;/p&gt;
&lt;p&gt;During the Christmas period 🎄 the company have an influx of files,
being able to process more files than the average &lt;strong&gt;whilst
maintaining the same time period&lt;/strong&gt; is important.&lt;/p&gt;
&lt;h3 id=&quot;polling-transfer-service&quot;&gt;Polling &amp;amp; Transfer Service&lt;/h3&gt;
&lt;p&gt;Let’s briefly go over the process flows available in this diagram as
a ‘best effort’ goal of defining their purpose.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/serverless-ftp-file-processing-service/ftp-arch.jpg&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;lambda-file-fetch&quot;&gt;1. Lambda (File Fetch)&lt;/h3&gt;
&lt;p&gt;This is our poll service, its sole purpose is to connect to the FTP
server on an interval, check a directory for files, download and send
them to an S3 bucket.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;File Processing&lt;/strong&gt; - There’s no processing of this
file in here, the reason is our Lambda can only run for 15 minutes, we
don’t want our Lambda stuck processing and downloading each file.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Security&lt;/strong&gt; - The above gives greater separation of
concerns, which in turn enables us to have a refined execution policy
with specific resource access.&lt;/p&gt;
&lt;h3 id=&quot;cloudwatch&quot;&gt;2. CloudWatch&lt;/h3&gt;
&lt;p&gt;As the Lambda is used for file polling and downloading, the
CloudWatch log data becomes more granular via just logging those I/O
events. We can create some alarms which will alert users to issues via
SNS or direct e-mail depending upon requirements.&lt;/p&gt;
&lt;p&gt;There isn’t much to discuss, other than this is highly useful for
event tracking and debugging.&lt;/p&gt;
&lt;h3 id=&quot;s3-file-bucket&quot;&gt;3. S3 (File Bucket)&lt;/h3&gt;
&lt;p&gt;This acts as our temporary document store for our downloaded files
and is a key part of our distributed, resilient file processing service.
We will transfer our data over a VPC Endpoint to our Bucket, ensuring
this doesn’t travel over the public Internet. There’s a good few
advantages to storing the documents in our service, let’s first go over
features of S3 we’re leveraging.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Lifecycle Policy&lt;/strong&gt; - We’ll use this to delete our
files automatically to save cost. The retention may need to be agreed
upon by the data provider or your company to consider PII when retaining
the file. If there’s a processing issue we don’t need to re-download
from the remote server.&lt;/p&gt;
&lt;p&gt;This can be incredibly useful not only for saving time and
operational overhead, but files can be short-lived or overwritten on FTP
servers and therefore we avoid the headache of asking for files.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Event Notification&lt;/strong&gt; - We can hook into
&lt;code&gt;new object created&lt;/code&gt; events, allowing automatic integration
with AWS SQS, queueing our file for future processing without writing
any code.&lt;/p&gt;
&lt;h3 id=&quot;parsing-processing-service&quot;&gt;Parsing &amp;amp; Processing
Service&lt;/h3&gt;
&lt;h4 id=&quot;sqs&quot;&gt;4. SQS&lt;/h4&gt;
&lt;p&gt;Our SQS allows for massive flexibility and resiliency and provides a
mechanism for decoupling between our service architecture. Let’s outline
some of the benefits here:&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Automatic Retry&lt;/strong&gt; - If the Lambda fails to process
a file, the entry in the queue will re-appear due to the visibility
timeout mechanism. Processing files involves hitting an external API for
validation, which &lt;em&gt;could&lt;/em&gt; experience issues; in this case
&lt;em&gt;n&lt;/em&gt; retries is great to cope with random failure outside of the
service’s control.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;DLQ&lt;/strong&gt; - A dead-letter queue is incredibly useful
for our scenario, we want to know if a file cannot be processed and most
importantly we need to know &lt;strong&gt;which&lt;/strong&gt; file in question
needs to be inspected, it could be our code, or simply an issue with the
file. Furthermore, since our message is moved to the DLQ after a finite
number of processing attempts, we save time and money and avoid
potentially blocking other files from being processed 👍.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Elasticity&lt;/strong&gt; - Currently &lt;strong&gt;one&lt;/strong&gt;
Lambda to process files is enough, however, if the velocity of files
grows, event source scaling can horizontally scale &lt;em&gt;n&lt;/em&gt; Lambdas
based upon our queue length 👌.&lt;/p&gt;
&lt;h3 id=&quot;lambda&quot;&gt;5. Lambda&lt;/h3&gt;
&lt;p&gt;Here’s our file processor, its job is to read files, perform some
data transformation and call a third-party service to validate
attributes, once this has been completed the data is then stored in a
DynamoDB table for other services outside of the scope of this problem
to consume.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For this, we can use the S3 VPC Gateway endpoint to keep the file
stream off the public Internet and purely in the AWS private network,
eliminating S3 data transfer cost too.&lt;/li&gt;
&lt;li&gt;We could use batching to save invocation cost via batching our SQS
messages together, however, this would make debugging a file failure
more troublesome and isn’t needed for our requirements&lt;/li&gt;
&lt;li&gt;We can give our Lambda an explicit &lt;code&gt;iamRole&lt;/code&gt; to access
our specific DynamoDB table via its resource id, this allows us to
easily follow least privilege and simply grant what we need&lt;/li&gt;
&lt;li&gt;We can put a Lambda in our VPC and therefore restrict via Security
Group to access required external services&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;sns&quot;&gt;6. SNS&lt;/h3&gt;
&lt;p&gt;The SNS sends a notification after processing, this could send an
e-mail or log a successful process and if there’s a consistent baseline,
have an alarm trigger below that threshold for a manual review.&lt;/p&gt;
&lt;h3 id=&quot;worthy-mentions&quot;&gt;Worthy Mentions&lt;/h3&gt;
&lt;p&gt;Since we don’t want to get too tied down with all the options
available in regards to resource configuration, I’ve omitted what we
don’t need to save the article from being exhaustive, here are a few
things to consider for similar scenarios&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;FIFO Queue&lt;/strong&gt; - This guarantees order and prevents
possible duplication of messages in an SQS queue, highly useful if
duplication of messages cannot happen or order of messages is
crucial&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;S3 Cross Region&lt;/strong&gt; - If this was a mission-critical
system or our files could never be re-created, exploring S3 region
replication would be useful as part of a disaster recovery plan&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Batch processing/size&lt;/strong&gt; - Increasing the amount of
messages included in a single Lambda invocation, this could make
debugging individual files more difficult, but would result in a
performance increase (due to lack of warmup time) and some cost saving
from a time POV&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;AWS Transfer Family&lt;/strong&gt; - A little outside of scope,
but if the FTP server were created using AWS Transfer Family, not only
would this resource scale automatically, it would also provide
&lt;strong&gt;workflows to transfer directly to S3&lt;/strong&gt; or EFS. In our
scenario this would save work and reduce complexity.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That’s a wrap for the service, this scenario should be an
approximation of a standard file processing system and its default
expectations; i.e. it shouldn’t stop working if a single file cannot be
processed, and the culprit should be logged etc. We’ve skipped much of
the finer detail to avoid verbiage.&lt;/p&gt;
</description></item>
<item><title>Beginner - Fluent Interfaces</title><link>https://www.ashgrennan.com/post/beginner-fluent-interfaces/</link><guid>https://www.ashgrennan.com/post/beginner-fluent-interfaces/</guid><pubDate>Sun, 16 May 2021 00:00:00 +0000</pubDate><description>&lt;p&gt;So, let’s continue from our last post &lt;a
href=&quot;https://www.ashgrennan.com/post/beginner-method-chaining/&quot;&gt;method chaining with C#&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;To recap, Jim 🧔 found a cleaner, more efficient method of building
his sandwich, but he’s discovered there should be a specific order to
the ingredients.&lt;/p&gt;
&lt;p&gt;With method chaining we can’t enforce a specific order of methods
called. Let’s illustrate this problem.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; SandwichFactory&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; SandwichFactory &lt;span class=&quot;fu&quot;&gt;AddBread&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// implementation omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; SandwichFactory &lt;span class=&quot;fu&quot;&gt;AddCheese&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// implementation omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-14&quot;&gt;&lt;a href=&quot;#cb1-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-15&quot;&gt;&lt;a href=&quot;#cb1-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-16&quot;&gt;&lt;a href=&quot;#cb1-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We, as sandwich construction experts, want to enforce the following
pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🍞 Bread&lt;/li&gt;
&lt;li&gt;🧀 Cheese&lt;/li&gt;
&lt;li&gt;🍞 Bread&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, a consumer of our &lt;code&gt;SandwichFactory&lt;/code&gt; class could
do the following:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; createSandwich &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;SandwichFactory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddCheese&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddBread&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or something &lt;em&gt;worse!&lt;/em&gt; Let’s fix this problem.&lt;/p&gt;
&lt;p&gt;Coming up with a nice fluent API requires a good bit of thought -
Martin Fowler&lt;/p&gt;
&lt;p&gt;Depending on the API we want to provide, complexity can rapidly
escalate. For our example, we’re going to keep this small to illustrate
the concepts more than application. Let’s provide our solution below and
go through the reasoning behind decisions made in regards to fluent
interfaces:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; SandwichFactory &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; ISandwichBread&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; ISandwichCheese&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;SandwichFactory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;static&lt;/span&gt; ISandwichBread Create &lt;span class=&quot;op&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;SandwichFactory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-8&quot;&gt;&lt;a href=&quot;#cb3-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-9&quot;&gt;&lt;a href=&quot;#cb3-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; ISandwichCheese &lt;span class=&quot;fu&quot;&gt;AddBread&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-10&quot;&gt;&lt;a href=&quot;#cb3-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-11&quot;&gt;&lt;a href=&quot;#cb3-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;kw&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;NotImplementedException&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-12&quot;&gt;&lt;a href=&quot;#cb3-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-13&quot;&gt;&lt;a href=&quot;#cb3-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-14&quot;&gt;&lt;a href=&quot;#cb3-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; ISandwichBread &lt;span class=&quot;fu&quot;&gt;AddCheese&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-15&quot;&gt;&lt;a href=&quot;#cb3-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-16&quot;&gt;&lt;a href=&quot;#cb3-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;kw&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;NotImplementedException&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-17&quot;&gt;&lt;a href=&quot;#cb3-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-18&quot;&gt;&lt;a href=&quot;#cb3-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-19&quot;&gt;&lt;a href=&quot;#cb3-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-20&quot;&gt;&lt;a href=&quot;#cb3-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; ISandwichBread&lt;/span&gt;
&lt;span id=&quot;cb3-21&quot;&gt;&lt;a href=&quot;#cb3-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-22&quot;&gt;&lt;a href=&quot;#cb3-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            ISandwichCheese &lt;span class=&quot;fu&quot;&gt;AddBread&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-23&quot;&gt;&lt;a href=&quot;#cb3-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-24&quot;&gt;&lt;a href=&quot;#cb3-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-25&quot;&gt;&lt;a href=&quot;#cb3-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; ISandwichCheese&lt;/span&gt;
&lt;span id=&quot;cb3-26&quot;&gt;&lt;a href=&quot;#cb3-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-27&quot;&gt;&lt;a href=&quot;#cb3-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            ISandwichBread &lt;span class=&quot;fu&quot;&gt;AddCheese&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-28&quot;&gt;&lt;a href=&quot;#cb3-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The constructor needs to be &lt;strong&gt;private&lt;/strong&gt; - We don’t want
a direct instance of our class to be created, since this would provide
access to the &lt;code&gt;AddBread&lt;/code&gt; and &lt;code&gt;AddCheese&lt;/code&gt; methods
(which is exactly what we’re trying to avoid)!&lt;/li&gt;
&lt;li&gt;Static Entry point - our entry point, in this case
&lt;code&gt;Create&lt;/code&gt; should return the interface we want to start with
which is &lt;code&gt;ISandwichBread&lt;/code&gt; and an instance of our class&lt;/li&gt;
&lt;li&gt;Keep parameters low - we don’t want to use tons of parameters per
method, in our example we’re using 0, but it’s something to be mindful
of&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consuming the &lt;code&gt;SandwichFactory&lt;/code&gt; API, we now
&lt;strong&gt;can’t&lt;/strong&gt; add cheese 🧀 before bread 🍞, since our design
enforces a bread &amp;gt; cheese hierarchy.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        SandwichFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Create&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddBread&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddCheese&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddBread&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Simples! 💪&lt;/p&gt;
</description></item>
<item><title>Beginner - Method Chaining</title><link>https://www.ashgrennan.com/post/beginner-method-chaining/</link><guid>https://www.ashgrennan.com/post/beginner-method-chaining/</guid><pubDate>Sun, 18 Apr 2021 00:00:00 +0000</pubDate><description>&lt;p&gt;I was talking to a recent bootcamp grad who had not covered this
within their course syllabus, so let’s jump into an example.&lt;/p&gt;
&lt;h3 id=&quot;scenario&quot;&gt;Scenario&lt;/h3&gt;
&lt;p&gt;Jim 🧔 is making a sandwich. He already knows which of the
ingredients in his fridge he’ll be using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mayonnaise ⬜&lt;/li&gt;
&lt;li&gt;Lettuce 🍃&lt;/li&gt;
&lt;li&gt;Cheese 🧀&lt;/li&gt;
&lt;li&gt;Ham 🥓&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jim will need to visit the fridge for each ingredient added to the
sandwich. Each trip requires a reference to the fridge, and removal of
an item.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Could Jim make this sandwich creation operation better?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When we think about this scenario as code, it would be something
like:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; fridge &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;Fridge&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    fridge&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RemoveMayonnaise&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    fridge&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RemoveCheese&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    fridge&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RemoveHam&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    fridge&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RemoveLettuce&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;sub&gt;Figure 1: fridge and removal of items&lt;/sub&gt;&lt;/p&gt;
&lt;p&gt;This pattern of calling multiple methods from an object is something
very common in object-oriented programming. Whilst perfectly valid, this
suffers from some issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We’re &lt;em&gt;re-referencing&lt;/em&gt; the object, again and again, typing
the word &lt;code&gt;fridge&lt;/code&gt; and invoking a method each line&lt;/li&gt;
&lt;li&gt;We’re creating a lot of noise and &lt;ins&gt;lines&lt;/ins&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whilst clear in the example above, it lacks any form of relation,
with each call being completely independent. Think of this &lt;strong&gt;as a
series of cogs&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/beginner-method-chaining/cogs-gear-wheel-mechanisms.webp&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In our code example, the links are &lt;u&gt;placed one after another&lt;/u&gt;,
but they could become easily separated and it’s hard to keep them in
order. Now imagine the links linking together like a… chain! We have a
specific order; we can even enforce a certain order, but that’s a future
topic 😉.&lt;/p&gt;
&lt;p&gt;Let’s look at how method chaining can help us here.&lt;/p&gt;
&lt;p&gt;If you’ve used anything from the &lt;code&gt;System.Linq&lt;/code&gt; namespace,
it’s highly likely you’ve used method chaining, an example of this can
be found below:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; people &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; peoples&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;p &lt;span class=&quot;op&quot;&gt;=&amp;gt;&lt;/span&gt; p&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Age&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;OrderByDescending&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;p &lt;span class=&quot;op&quot;&gt;=&amp;gt;&lt;/span&gt; p&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Take&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ToList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;sub&gt;Note: the example above uses fluent interfaces too, but we’ll
get into that another time.&lt;/sub&gt;&lt;/p&gt;
&lt;p&gt;Some benefits of method chaining are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To form a chain of available commands which are tied to an
object&lt;/li&gt;
&lt;li&gt;Create a sequence of processing ‘steps’ that are optional and
require no particular order&lt;/li&gt;
&lt;li&gt;Simpler and cleaner calling code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sounds good? Let’s get started.&lt;/p&gt;
&lt;h3 id=&quot;improving-the-code-in-figure-1&quot;&gt;Improving the code in Figure
1&lt;/h3&gt;
&lt;p&gt;To create method chaining, there are two requirements for our
code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each method will need to have a return type of the class we’re
using&lt;/li&gt;
&lt;li&gt;At the end of each method, we’ll need to return the current object
instance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s modify the &lt;code&gt;RemoveMayonnaise&lt;/code&gt; method with these
changes:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;     &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Fridge &lt;span class=&quot;fu&quot;&gt;RemoveMayonnaise&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;     &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;         &lt;span class=&quot;co&quot;&gt;// implementation omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;         &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;     &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Line 1 - Here we’re stating the object type which will be
returned&lt;/li&gt;
&lt;li&gt;Line 4 - Here we’re returning the instance of itself
&lt;code&gt;this&lt;/code&gt; from the method&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because we’re returning the object &lt;code&gt;Fridge&lt;/code&gt; and we’re
returning an instance, we can then access any public method within the
&lt;code&gt;Fridge&lt;/code&gt; instance, welcome to method chaining. Let’s add the
remaining methods for illustration below:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;   &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Program&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[]&lt;/span&gt; args&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// &lt;/span&gt;&lt;span class=&quot;al&quot;&gt;###&lt;/span&gt;&lt;span class=&quot;co&quot;&gt; Previous code&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// var fridge = new Fridge();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-7&quot;&gt;&lt;a href=&quot;#cb4-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-8&quot;&gt;&lt;a href=&quot;#cb4-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// fridge.RemoveMayonnaise();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-9&quot;&gt;&lt;a href=&quot;#cb4-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// fridge.RemoveCheese();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-10&quot;&gt;&lt;a href=&quot;#cb4-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// fridge.RemoveHam();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-11&quot;&gt;&lt;a href=&quot;#cb4-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// fridge.RemoveLettuce();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-12&quot;&gt;&lt;a href=&quot;#cb4-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-13&quot;&gt;&lt;a href=&quot;#cb4-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// &lt;/span&gt;&lt;span class=&quot;al&quot;&gt;###&lt;/span&gt;&lt;span class=&quot;co&quot;&gt; New code&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-14&quot;&gt;&lt;a href=&quot;#cb4-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; fridge &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;Fridge&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-15&quot;&gt;&lt;a href=&quot;#cb4-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-16&quot;&gt;&lt;a href=&quot;#cb4-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            fridge&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RemoveMayonnaise&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-17&quot;&gt;&lt;a href=&quot;#cb4-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RemoveHam&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-18&quot;&gt;&lt;a href=&quot;#cb4-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RemoveCheese&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-19&quot;&gt;&lt;a href=&quot;#cb4-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RemoveLettuce&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-20&quot;&gt;&lt;a href=&quot;#cb4-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-21&quot;&gt;&lt;a href=&quot;#cb4-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-22&quot;&gt;&lt;a href=&quot;#cb4-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-23&quot;&gt;&lt;a href=&quot;#cb4-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Fridge&lt;/span&gt;
&lt;span id=&quot;cb4-24&quot;&gt;&lt;a href=&quot;#cb4-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-25&quot;&gt;&lt;a href=&quot;#cb4-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Fridge &lt;span class=&quot;fu&quot;&gt;RemoveMayonnaise&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-26&quot;&gt;&lt;a href=&quot;#cb4-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-27&quot;&gt;&lt;a href=&quot;#cb4-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// implementation omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-28&quot;&gt;&lt;a href=&quot;#cb4-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-29&quot;&gt;&lt;a href=&quot;#cb4-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-30&quot;&gt;&lt;a href=&quot;#cb4-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-31&quot;&gt;&lt;a href=&quot;#cb4-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Fridge &lt;span class=&quot;fu&quot;&gt;RemoveLettuce&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-32&quot;&gt;&lt;a href=&quot;#cb4-32&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-33&quot;&gt;&lt;a href=&quot;#cb4-33&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// implementation omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-34&quot;&gt;&lt;a href=&quot;#cb4-34&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-35&quot;&gt;&lt;a href=&quot;#cb4-35&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-36&quot;&gt;&lt;a href=&quot;#cb4-36&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-37&quot;&gt;&lt;a href=&quot;#cb4-37&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Fridge &lt;span class=&quot;fu&quot;&gt;RemoveCheese&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-38&quot;&gt;&lt;a href=&quot;#cb4-38&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-39&quot;&gt;&lt;a href=&quot;#cb4-39&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// implementation omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-40&quot;&gt;&lt;a href=&quot;#cb4-40&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-41&quot;&gt;&lt;a href=&quot;#cb4-41&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-42&quot;&gt;&lt;a href=&quot;#cb4-42&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-43&quot;&gt;&lt;a href=&quot;#cb4-43&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Fridge &lt;span class=&quot;fu&quot;&gt;RemoveHam&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-44&quot;&gt;&lt;a href=&quot;#cb4-44&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-45&quot;&gt;&lt;a href=&quot;#cb4-45&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// implementation omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-46&quot;&gt;&lt;a href=&quot;#cb4-46&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-47&quot;&gt;&lt;a href=&quot;#cb4-47&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-48&quot;&gt;&lt;a href=&quot;#cb4-48&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our calling code under the &lt;code&gt;### New code&lt;/code&gt; comment is
smaller, more fluid and looks much cleaner in comparison to
&lt;code&gt;### Previous code&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;but-wait&quot;&gt;But Wait…&lt;/h3&gt;
&lt;p&gt;Jim is back 🧔, the &lt;ins&gt;&lt;strong&gt;good news&lt;/strong&gt;&lt;/ins&gt; is that,
thanks to our method chaining, Jim can gather his ingredients in one
trip to the fridge.&lt;/p&gt;
&lt;p&gt;Now the &lt;ins&gt;&lt;strong&gt;bad news&lt;/strong&gt;&lt;/ins&gt;: from his last sandwich
creation, he learned that placing the lettuce at the bottom of the bread
makes it moist and threatens the structural integrity of the sandwich,
so we need to use the ingredients in a specific order.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But how can we enforce a sequence of events?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let’s cover that in the next blog post, fluent interfaces!&lt;/p&gt;
</description></item>
<item><title>Micro Multi Tenanted Value Resolver Library 📚</title><link>https://www.ashgrennan.com/post/asp-net-core-multitenant-resolver-library/</link><guid>https://www.ashgrennan.com/post/asp-net-core-multitenant-resolver-library/</guid><pubDate>Sun, 07 Jun 2020 00:00:00 +0000</pubDate><description>&lt;p&gt;I’ve been recently dealing with enabling multi-tenancy in a
full-stack application, the back end being ASP.NET Core. When looking
into various multi-tenant libraries I found them to have one or more
characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The APIs were quite complex, and required lots of documentation
parsing&lt;/li&gt;
&lt;li&gt;Difficult to plug in different value resolutions&lt;/li&gt;
&lt;li&gt;Require lots of boilerplate code and implementation of services&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many of these libraries are designed to use configuration files or
resolution databases in order to load credentials. I
&lt;strong&gt;don’t&lt;/strong&gt; like this, why?&lt;/p&gt;
&lt;p&gt;The reason is simple, &lt;em&gt;most&lt;/em&gt; credentials don’t belong in files
such as &lt;code&gt;appsettings.json&lt;/code&gt;. Instead, these should be stored
in a Key Vault which has a host of benefits such as permission
management, hardware-level encryption and so on. Services such as &lt;a
href=&quot;https://azure.microsoft.com/en-gb/services/key-vault/&quot;&gt;Azure Key
Vault&lt;/a&gt; have these benefits.&lt;/p&gt;
&lt;p&gt;So, more to the point, I’ve written a very, very small library which
implements a &lt;code&gt;Middleware&lt;/code&gt; pattern to essentially push
specific credentials based upon a tenant resolution pattern into the
current &lt;code&gt;HttpContext&lt;/code&gt;. The NuGet package can be found &lt;a
href=&quot;https://www.nuget.org/packages/BobNudd.MicroMultiTenant&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The nice thing about Azure Key Vault is that all secrets (depending
upon access rights) can be loaded into &lt;code&gt;IConfiguration&lt;/code&gt; on
application build.&lt;/p&gt;
&lt;p&gt;More information and the source can be found on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a
href=&quot;https://github.com/BobNudd/MicroMultiTenant&quot;&gt;https://github.com/BobNudd/MicroMultiTenant&lt;/a&gt;&lt;/p&gt;
</description></item>
<item><title>🔃 Angular Network Aware Preload Strategy</title><link>https://www.ashgrennan.com/post/angular-network-aware-preload-strategy/</link><guid>https://www.ashgrennan.com/post/angular-network-aware-preload-strategy/</guid><pubDate>Sun, 22 Dec 2019 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;lazy-loading-routes&quot;&gt;Lazy Loading Routes&lt;/h3&gt;
&lt;p&gt;As your app becomes larger, with different modules to group feature
components, a common approach is to lazy load each module in your main
routing configuration. Angular recommends this from the start, since
it’s a simple yet scalable pattern to load your project’s dependencies
once the &lt;code&gt;route&lt;/code&gt; is activated. You have likely seen or
implemented this pattern in the &lt;code&gt;app-routing.module.ts&lt;/code&gt; like
below:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode typescript&quot;&gt;&lt;code class=&quot;sourceCode typescript&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;im&quot;&gt;import&lt;/span&gt; { NgModule } &lt;span class=&quot;im&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;@angular/core&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;im&quot;&gt;import&lt;/span&gt; { RouterModule&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Routes } &lt;span class=&quot;im&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;@angular/router&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; routes&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; Routes &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; [&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            {&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                path&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;index&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                loadChildren&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;src/app/modules/home/home.module#HomeModule&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            }&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            {&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                path&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;shop&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                loadChildren&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;src/app/modules/shop/shop.module#ShopModule&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            }&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;]&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode typescript&quot;&gt;&lt;code class=&quot;sourceCode typescript&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;im&quot;&gt;import&lt;/span&gt; { NgModule } &lt;span class=&quot;im&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;@angular/core&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;im&quot;&gt;import&lt;/span&gt; { RouterModule&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Routes } &lt;span class=&quot;im&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;@angular/router&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; routes&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; Routes &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; [&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            {&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                path&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;index&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                loadChildren&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; () &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;im&quot;&gt;import&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;src/app/modules/home/home.module&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;then&lt;/span&gt;(m &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; m&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;HomeModule&lt;/span&gt;)&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            }&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-9&quot;&gt;&lt;a href=&quot;#cb2-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            {&lt;/span&gt;
&lt;span id=&quot;cb2-10&quot;&gt;&lt;a href=&quot;#cb2-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                path&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;users&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-11&quot;&gt;&lt;a href=&quot;#cb2-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                loadChildren&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; () &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;im&quot;&gt;import&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;src/app/modules/user/user.module&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;then&lt;/span&gt;(m &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; m&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;UserModule&lt;/span&gt;)&lt;/span&gt;
&lt;span id=&quot;cb2-12&quot;&gt;&lt;a href=&quot;#cb2-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            }&lt;/span&gt;
&lt;span id=&quot;cb2-13&quot;&gt;&lt;a href=&quot;#cb2-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;]&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note: the second example is new as of Angular 8, and is a &lt;a
href=&quot;https://caniuse.com/#feat=es6-module-dynamic-import&quot;&gt;widely
supported&lt;/a&gt; method of importing modules.&lt;/p&gt;
&lt;p&gt;The above examples allow the module to be loaded in when they’re
requested via accessing the &lt;code&gt;route&lt;/code&gt; tied to the module.&lt;/p&gt;
&lt;h3 id=&quot;eager-loading&quot;&gt;Eager Loading&lt;/h3&gt;
&lt;p&gt;The standard method of preloading any specified modules as above is
to use the &lt;code&gt;PreloadAllModules&lt;/code&gt; strategy. This quite simply
loads all modules that have been specified in our &lt;code&gt;routes&lt;/code&gt;
array. The advantage of this method is a better user experience since
there isn’t any time lag between an end user clicking a route
e.g. &lt;code&gt;/users&lt;/code&gt; and the module having to be downloaded.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre
class=&quot;sourceCode typescript&quot;&gt;&lt;code class=&quot;sourceCode typescript&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;imports&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; [&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;// omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  RouterModule&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;forRoot&lt;/span&gt;(routes&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    { preloadingStrategy&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; PreloadAllModules })&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;network-aware-preloading&quot;&gt;Network Aware Preloading&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PreloadAllModules&lt;/code&gt; is great on a fast connection, but not
everyone is on one. A user on a slow mobile connection, or with data
saver switched on, will have every module downloaded in the background
whether they ever visit those routes or not, competing with the
bandwidth the current page needs.&lt;/p&gt;
&lt;p&gt;Angular lets us decide this ourselves by providing a custom
&lt;code&gt;PreloadingStrategy&lt;/code&gt;. It has a single method,
&lt;code&gt;preload&lt;/code&gt;, which the router calls for each route that uses
&lt;code&gt;loadChildren&lt;/code&gt;. Returning &lt;code&gt;load()&lt;/code&gt; preloads the
module, returning &lt;code&gt;of(null)&lt;/code&gt; skips it.&lt;/p&gt;
&lt;p&gt;To decide, we can use the Network Information API.
&lt;code&gt;navigator.connection&lt;/code&gt; exposes &lt;code&gt;effectiveType&lt;/code&gt;
(&lt;code&gt;&#x27;slow-2g&#x27;&lt;/code&gt;, &lt;code&gt;&#x27;2g&#x27;&lt;/code&gt;, &lt;code&gt;&#x27;3g&#x27;&lt;/code&gt; or
&lt;code&gt;&#x27;4g&#x27;&lt;/code&gt;) and &lt;code&gt;saveData&lt;/code&gt;, which is true when the
user has asked their browser to use less data.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre
class=&quot;sourceCode typescript&quot;&gt;&lt;code class=&quot;sourceCode typescript&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;im&quot;&gt;import&lt;/span&gt; { Injectable } &lt;span class=&quot;im&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;@angular/core&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;im&quot;&gt;import&lt;/span&gt; { PreloadingStrategy&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Route } &lt;span class=&quot;im&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;@angular/router&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;im&quot;&gt;import&lt;/span&gt; { Observable&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;of&lt;/span&gt; } &lt;span class=&quot;im&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;@&lt;span class=&quot;fu&quot;&gt;Injectable&lt;/span&gt;({ providedIn&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;root&amp;#39;&lt;/span&gt; })&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;im&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; NetworkAwarePreloadStrategy &lt;span class=&quot;kw&quot;&gt;implements&lt;/span&gt; PreloadingStrategy {&lt;/span&gt;
&lt;span id=&quot;cb4-7&quot;&gt;&lt;a href=&quot;#cb4-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;fu&quot;&gt;preload&lt;/span&gt;(route&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; Route&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; load&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; () &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; Observable&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; Observable&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb4-8&quot;&gt;&lt;a href=&quot;#cb4-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hasGoodConnection&lt;/span&gt;() &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;load&lt;/span&gt;() &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;of&lt;/span&gt;(&lt;span class=&quot;dt&quot;&gt;null&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-9&quot;&gt;&lt;a href=&quot;#cb4-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  }&lt;/span&gt;
&lt;span id=&quot;cb4-10&quot;&gt;&lt;a href=&quot;#cb4-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-11&quot;&gt;&lt;a href=&quot;#cb4-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;hasGoodConnection&lt;/span&gt;()&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;boolean&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb4-12&quot;&gt;&lt;a href=&quot;#cb4-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; connection &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; (&lt;span class=&quot;bu&quot;&gt;navigator&lt;/span&gt; &lt;span class=&quot;im&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;any&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-13&quot;&gt;&lt;a href=&quot;#cb4-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-14&quot;&gt;&lt;a href=&quot;#cb4-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;op&quot;&gt;!&lt;/span&gt;connection) {&lt;/span&gt;
&lt;span id=&quot;cb4-15&quot;&gt;&lt;a href=&quot;#cb4-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;// API not supported, behave like PreloadAllModules&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-16&quot;&gt;&lt;a href=&quot;#cb4-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    }&lt;/span&gt;
&lt;span id=&quot;cb4-17&quot;&gt;&lt;a href=&quot;#cb4-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-18&quot;&gt;&lt;a href=&quot;#cb4-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; (connection&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;saveData&lt;/span&gt;) {&lt;/span&gt;
&lt;span id=&quot;cb4-19&quot;&gt;&lt;a href=&quot;#cb4-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-20&quot;&gt;&lt;a href=&quot;#cb4-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    }&lt;/span&gt;
&lt;span id=&quot;cb4-21&quot;&gt;&lt;a href=&quot;#cb4-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-22&quot;&gt;&lt;a href=&quot;#cb4-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;st&quot;&gt;&amp;#39;slow-2g&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;2g&amp;#39;&lt;/span&gt;]&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;includes&lt;/span&gt;(connection&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;effectiveType&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-23&quot;&gt;&lt;a href=&quot;#cb4-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  }&lt;/span&gt;
&lt;span id=&quot;cb4-24&quot;&gt;&lt;a href=&quot;#cb4-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then swap it in for &lt;code&gt;PreloadAllModules&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre
class=&quot;sourceCode typescript&quot;&gt;&lt;code class=&quot;sourceCode typescript&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;imports&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; [&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;// omitted for brevity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  RouterModule&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;forRoot&lt;/span&gt;(routes&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    { preloadingStrategy&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; NetworkAwarePreloadStrategy })&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Modules we skip aren’t lost, they’re simply loaded on demand when the
user visits the route, exactly as in the lazy loading examples
above.&lt;/p&gt;
&lt;h3 id=&quot;browser-support&quot;&gt;Browser Support&lt;/h3&gt;
&lt;p&gt;At the time of writing, &lt;code&gt;navigator.connection&lt;/code&gt; is only
available in Chromium based browsers. That’s why the strategy falls back
to preloading everything when the API is missing, so those users get the
same behaviour as &lt;code&gt;PreloadAllModules&lt;/code&gt;. Treat this as a
progressive enhancement rather than something to depend on.&lt;/p&gt;
</description></item>
<item><title>Visual Studio Remote Debugger Firewall Error</title><link>https://www.ashgrennan.com/post/visual-studio-remote-debugger-firewall-error/</link><guid>https://www.ashgrennan.com/post/visual-studio-remote-debugger-firewall-error/</guid><pubDate>Thu, 05 Dec 2019 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;an-error-occurred-while-launching-remote-debugging&quot;&gt;An Error
Occurred while launching remote debugging&lt;/h3&gt;
&lt;p&gt;This is a relatively common problem (in my experience).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/visual-studio-remote-debugger-firewall-error/remote-debugger-visual-studio.PNG&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Generally, ensuring that port 4024 for Visual Studio 2019 or 4022 if
you’re using 2017 isn’t blocked by your network resolves the issue.&lt;/p&gt;
&lt;p&gt;However, in my case, this didn’t work.&lt;/p&gt;
&lt;h4 id=&quot;solution&quot;&gt;Solution&lt;/h4&gt;
&lt;p&gt;The Web App in question had a custom domain, on the DNS side it was a
CNAME, since it was a subdomain.&lt;/p&gt;
&lt;p&gt;Finding the IP address of the app (via Properties in Azure) and
making two entries in the hosts file on Windows for the CNAME and
original *.azurewebsites.net subdomain fixed the issue.&lt;/p&gt;
</description></item>
<item><title>Image Mutation Pipeline - Reshaping and Overlay Images</title><link>https://www.ashgrennan.com/post/image-mutation-pipeline-reshaping-and-overlay-images/</link><guid>https://www.ashgrennan.com/post/image-mutation-pipeline-reshaping-and-overlay-images/</guid><pubDate>Mon, 04 Nov 2019 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;In this post, we’ll go over image manipulation by creating a
pipeline. The goal of this post is to create an avatar, consisting of a
user/character, then surround this picture with a frame as an
overlay.&lt;/p&gt;
&lt;h3 id=&quot;requirements&quot;&gt;Requirements&lt;/h3&gt;
&lt;p&gt;Rather than using &lt;code&gt;System.Drawing&lt;/code&gt; we’ll instead use &lt;a
href=&quot;https://github.com/SixLabors/ImageSharp&quot;&gt;ImageSharp&lt;/a&gt; which is
the most popular image processing library written using .NET Core, it’s
very fast and highly optimized. Despite being in beta, I have used it in
production for a year without problems.&lt;/p&gt;
&lt;p&gt;Using this library, we’ll create our own pipeline of image operations
to perform using &lt;code&gt;IImageProcessingContext&lt;/code&gt;, and take
advantage of some of the heavy lifting ImageSharp provides.&lt;/p&gt;
&lt;p&gt;For assets, we’ll be using two images, a base picture and an overlay
to surround:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/image-mutation-pipeline-reshaping-and-overlay-images/me.png&quot; alt=&quot;Base picture (me.png)&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;Base picture (me.png)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&quot;https://www.ashgrennan.com/post/image-mutation-pipeline-reshaping-and-overlay-images/masters-bronze.png&quot; alt=&quot;Overlay (frame.png)&quot; /&gt;
&lt;figcaption aria-hidden=&quot;true&quot;&gt;Overlay (frame.png)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;project-setup&quot;&gt;Project Setup&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Create a .NET Core Console App&lt;/li&gt;
&lt;li&gt;Add the following Package Reference to your .csproj file
&lt;code&gt;&amp;lt;PackageReference Include=&quot;SixLabors.ImageSharp.Drawing&quot; Version=&quot;1.0.0-beta0007&quot; /&amp;gt;&lt;/code&gt;.
If you install through NuGet, ensure that &lt;code&gt;.Drawing&lt;/code&gt; is
included, else you won’t be able to access the
&lt;code&gt;SixLabors.Shapes&lt;/code&gt; namespace&lt;/li&gt;
&lt;li&gt;Add the assets to your project, make sure you select
&lt;code&gt;Copy if newer&lt;/code&gt; in the Properties panel &amp;gt; Copy to Output
Directory&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;code&quot;&gt;Code&lt;/h3&gt;
&lt;p&gt;The first part of this process is to alter the shape of our user
image to something that resembles a diamond.&lt;/p&gt;
&lt;p&gt;Below is the code for this, with heavy use of comments elaborating on
the process since this is much easier than blog annotations:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[]&lt;/span&gt; args&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// directory to output results&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        System&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;CreateDirectory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;output&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// wrap operations in using statements to ensure that Dispose is called&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; img &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; Image&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Load&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;me.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// clone the original image, since we need this for future runs of the application&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Image destRound &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; img&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Clone&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;op&quot;&gt;=&amp;gt;&lt;/span&gt; x&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AvatarShaping&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;Size&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;225&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;225&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                destRound&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Save&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;output/result.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-14&quot;&gt;&lt;a href=&quot;#cb1-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-15&quot;&gt;&lt;a href=&quot;#cb1-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-16&quot;&gt;&lt;a href=&quot;#cb1-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-17&quot;&gt;&lt;a href=&quot;#cb1-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;/// &lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-18&quot;&gt;&lt;a href=&quot;#cb1-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;/// Here&amp;#39;s we&amp;#39;re implementing the IImageProcessingContext interface and extending it via an extension method,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-19&quot;&gt;&lt;a href=&quot;#cb1-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;/// This implements a full image mutating pipeline operating on IImageProcessingContext&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-20&quot;&gt;&lt;a href=&quot;#cb1-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;/// &lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-21&quot;&gt;&lt;a href=&quot;#cb1-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;/// &lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;param&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt; name=&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;quot;processingContext&amp;quot;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;co&quot;&gt;the processingContext&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-22&quot;&gt;&lt;a href=&quot;#cb1-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;/// &lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;param&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt; name=&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;co&quot;&gt;Size of the image&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-23&quot;&gt;&lt;a href=&quot;#cb1-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;/// &lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;param&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt; name=&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;quot;cornerRadius&amp;quot;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;co&quot;&gt;corner radius desired, higher will result in a smaller fill&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-24&quot;&gt;&lt;a href=&quot;#cb1-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;/// &lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;&amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-25&quot;&gt;&lt;a href=&quot;#cb1-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;static&lt;/span&gt; IImageProcessingContext &lt;span class=&quot;fu&quot;&gt;AvatarShaping&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt; IImageProcessingContext processingContext&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Size size&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;float&lt;/span&gt; cornerRadius&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-26&quot;&gt;&lt;a href=&quot;#cb1-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-27&quot;&gt;&lt;a href=&quot;#cb1-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; processingContext&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Resize&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; ResizeOptions&lt;/span&gt;
&lt;span id=&quot;cb1-28&quot;&gt;&lt;a href=&quot;#cb1-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-29&quot;&gt;&lt;a href=&quot;#cb1-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            Size &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; size&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-30&quot;&gt;&lt;a href=&quot;#cb1-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            Mode &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ResizeMode&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Crop&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-31&quot;&gt;&lt;a href=&quot;#cb1-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ApplyRoundedCorners&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cornerRadius&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;// the next part of our pipeline&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-32&quot;&gt;&lt;a href=&quot;#cb1-32&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-33&quot;&gt;&lt;a href=&quot;#cb1-33&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-34&quot;&gt;&lt;a href=&quot;#cb1-34&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// This method can be seen as an inline implementation of an `IImageProcessor`:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-35&quot;&gt;&lt;a href=&quot;#cb1-35&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// (The combination of `IImageOperations.Apply()` + this could be replaced with an `IImageProcessor`)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-36&quot;&gt;&lt;a href=&quot;#cb1-36&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;static&lt;/span&gt; IImageProcessingContext &lt;span class=&quot;fu&quot;&gt;ApplyRoundedCorners&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt; IImageProcessingContext ctx&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;float&lt;/span&gt; cornerRadius&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-37&quot;&gt;&lt;a href=&quot;#cb1-37&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-38&quot;&gt;&lt;a href=&quot;#cb1-38&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        Size size &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ctx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;GetCurrentSize&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-39&quot;&gt;&lt;a href=&quot;#cb1-39&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        IPathCollection corners &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;BuildCorners&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;size&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Width&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; size&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Height&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cornerRadius&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-40&quot;&gt;&lt;a href=&quot;#cb1-40&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-41&quot;&gt;&lt;a href=&quot;#cb1-41&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; graphicOptions &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;GraphicsOptions&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-42&quot;&gt;&lt;a href=&quot;#cb1-42&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-43&quot;&gt;&lt;a href=&quot;#cb1-43&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;           &lt;span class=&quot;co&quot;&gt;// AlphaCompositionMode = PixelAlphaCompositionMode.DestOut // enforces that any part of this shape that has color is punched out of the background&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-44&quot;&gt;&lt;a href=&quot;#cb1-44&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-45&quot;&gt;&lt;a href=&quot;#cb1-45&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// mutating in here as we already have a cloned original&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-46&quot;&gt;&lt;a href=&quot;#cb1-46&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// use any color (not Transparent), so the corners will be clipped&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-47&quot;&gt;&lt;a href=&quot;#cb1-47&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; ctx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Fill&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;graphicOptions&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Rgba32&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LimeGreen&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; corners&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-48&quot;&gt;&lt;a href=&quot;#cb1-48&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-49&quot;&gt;&lt;a href=&quot;#cb1-49&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-50&quot;&gt;&lt;a href=&quot;#cb1-50&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;static&lt;/span&gt; IPathCollection &lt;span class=&quot;fu&quot;&gt;BuildCorners&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; imageWidth&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; imageHeight&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;float&lt;/span&gt; cornerRadius&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-51&quot;&gt;&lt;a href=&quot;#cb1-51&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-52&quot;&gt;&lt;a href=&quot;#cb1-52&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// first create a square&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-53&quot;&gt;&lt;a href=&quot;#cb1-53&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; rect &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;RectangularPolygon&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(-&lt;/span&gt;&lt;span class=&quot;fl&quot;&gt;0.5f&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;fl&quot;&gt;0.5f&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cornerRadius&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cornerRadius&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-54&quot;&gt;&lt;a href=&quot;#cb1-54&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-55&quot;&gt;&lt;a href=&quot;#cb1-55&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// then cut out of the square a circle so we are left with a corner&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-56&quot;&gt;&lt;a href=&quot;#cb1-56&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// Changing the offsets will change how much of the shape is cut out&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-57&quot;&gt;&lt;a href=&quot;#cb1-57&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        IPath cornerTopLeft &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; rect&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Clip&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;EllipsePolygon&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cornerRadius &lt;span class=&quot;op&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;fl&quot;&gt;0.5f&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cornerRadius &lt;span class=&quot;op&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;fl&quot;&gt;0.5f&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cornerRadius&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-58&quot;&gt;&lt;a href=&quot;#cb1-58&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-59&quot;&gt;&lt;a href=&quot;#cb1-59&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// corner is now a corner shape positions top left&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-60&quot;&gt;&lt;a href=&quot;#cb1-60&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;//lets make 3 more positioned correctly, we can do that by translating the original around the center of the image&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-61&quot;&gt;&lt;a href=&quot;#cb1-61&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-62&quot;&gt;&lt;a href=&quot;#cb1-62&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;dt&quot;&gt;float&lt;/span&gt; rightPos &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; imageWidth &lt;span class=&quot;op&quot;&gt;-&lt;/span&gt; cornerTopLeft&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Bounds&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Width&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-63&quot;&gt;&lt;a href=&quot;#cb1-63&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;dt&quot;&gt;float&lt;/span&gt; bottomPos &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; imageHeight &lt;span class=&quot;op&quot;&gt;-&lt;/span&gt; cornerTopLeft&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Bounds&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Height&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-64&quot;&gt;&lt;a href=&quot;#cb1-64&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-65&quot;&gt;&lt;a href=&quot;#cb1-65&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// move it across the width of the image - the width of the shape&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-66&quot;&gt;&lt;a href=&quot;#cb1-66&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// cut the remaining 3 corners&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-67&quot;&gt;&lt;a href=&quot;#cb1-67&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        IPath cornerTopRight &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; cornerTopLeft&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RotateDegree&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Translate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;rightPos&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-68&quot;&gt;&lt;a href=&quot;#cb1-68&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        IPath cornerBottomLeft &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; cornerTopLeft&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RotateDegree&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(-&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Translate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; bottomPos&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-69&quot;&gt;&lt;a href=&quot;#cb1-69&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        IPath cornerBottomRight &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; cornerTopLeft&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RotateDegree&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Translate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;rightPos&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; bottomPos&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-70&quot;&gt;&lt;a href=&quot;#cb1-70&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-71&quot;&gt;&lt;a href=&quot;#cb1-71&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;PathCollection&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cornerTopLeft&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cornerBottomLeft&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cornerTopRight&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cornerBottomRight&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-72&quot;&gt;&lt;a href=&quot;#cb1-72&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above code results in the following image:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/image-mutation-pipeline-reshaping-and-overlay-images/fb-1.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We can achieve our approximate diamond shape via increasing the
&lt;code&gt;cornerRadius&lt;/code&gt; float value; however, this will result in less
of the canvas showing. Below is a value of 200:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/image-mutation-pipeline-reshaping-and-overlay-images/fb-2.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Side note, if you wanted a circle, all that would be required is to
set the &lt;code&gt;cornerRadius&lt;/code&gt; to 50% of the image size.&lt;/p&gt;
&lt;p&gt;Currently we’re filling in the areas we’ve clipped with a solid
green. Great for testing, but not for our desired result. You will want
to comment out the following line which will automatically remove our
colour fill.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AlphaCompositionMode = PixelAlphaCompositionMode.DestOut&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next, we’ll add our overlay, this is simpler than the previous code.
We’ll embed another &lt;code&gt;using&lt;/code&gt; statement since we still want the
original outputs, this makes viewing each stage of the process and
making changes much easier whilst we build the pipeline.&lt;/p&gt;
&lt;p&gt;Add the following within the &lt;code&gt;destRound&lt;/code&gt; using, after
&lt;code&gt;destRound.Save(&quot;output/fb.png&quot;);&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; overlay &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; Image&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Load&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;frame.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Image overlayClone &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; destRound&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Clone&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;op&quot;&gt;=&amp;gt;&lt;/span&gt; x&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;GenerateOverlay&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;overlay&lt;span class=&quot;op&quot;&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        overlayClone&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Save&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;output/complete.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;PngEncoder&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        ColorType &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; PngColorType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RgbWithAlpha&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;// needed to instruct the encoder transparency is required&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;op&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-9&quot;&gt;&lt;a href=&quot;#cb2-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-10&quot;&gt;&lt;a href=&quot;#cb2-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;GenerateOverlay&lt;/code&gt; is part of our pipeline for this
project, add the following:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;static&lt;/span&gt; IImageProcessingContext &lt;span class=&quot;fu&quot;&gt;GenerateOverlay&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt; IImageProcessingContext processingContext&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Image me&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; processingContext&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;DrawImage&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;me&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;GraphicsOptions&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above method will draw the image together with the previous
image, once that has completed this will be saved to the disk.&lt;/p&gt;
&lt;h3 id=&quot;the-final-result&quot;&gt;The Final Result&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/image-mutation-pipeline-reshaping-and-overlay-images/complete-1.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It should be noted that the code above is simply a proof of concept,
for instance the &lt;code&gt;Clone&lt;/code&gt; methods add overhead and shouldn’t
appear in production code.&lt;/p&gt;
</description></item>
<item><title>Angular Lazy Loading Modules with Ivy – Uncaught (in promise) error cannot find module</title><link>https://www.ashgrennan.com/post/angular-with-ivy-uncaught-in-promise-error-cannot-find-module/</link><guid>https://www.ashgrennan.com/post/angular-with-ivy-uncaught-in-promise-error-cannot-find-module/</guid><pubDate>Mon, 28 Oct 2019 00:00:00 +0000</pubDate><description>&lt;p&gt;This is a quick post on an error I encountered whilst opting into the
&lt;a
href=&quot;https://blog.angular.io/a-plan-for-version-8-0-and-ivy-b3318dfc19f7&quot;&gt;Ivy
compiler&lt;/a&gt; (which should be here Nov 2019).&lt;/p&gt;
&lt;p&gt;Once you get through the initial errors, you may see the below in
your console log:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;core.js:6014 ERROR Error: Uncaught (in promise): Error: Cannot find module &amp;#39;src/app/modules/module/your-module.module&amp;#39;
Error: Cannot find module &amp;#39;src/app/modules/module/your-module.module&amp;#39;
    at $_lazy_route_resource lazy namespace object:5
    at ZoneDelegate.invoke (zone-evergreen.js:365)
    at Object.onInvoke (core.js:39707)
    at ZoneDelegate.invoke (zone-evergreen.js:364)
    at Zone.run (zone-evergreen.js:124)
    at zone-evergreen.js:851
    at ZoneDelegate.invokeTask (zone-evergreen.js:400)
    at Object.onInvokeTask (core.js:39688)
    at ZoneDelegate.invokeTask (zone-evergreen.js:399)
    at Zone.runTask (zone-evergreen.js:168)
    at resolvePromise (zone-evergreen.js:793)
    at resolvePromise (zone-evergreen.js:752)
    at zone-evergreen.js:854
    at ZoneDelegate.invokeTask (zone-evergreen.js:400)
    at Object.onInvokeTask (core.js:39688)
    at ZoneDelegate.invokeTask (zone-evergreen.js:399)
    at Zone.runTask (zone-evergreen.js:168)
    at drainMicroTaskQueue (zone-evergreen.js:570)
    at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:485)
    at invokeTask (zone-evergreen.js:1596)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’re getting this error because Angular 8 introduced a new
recommended module loading method; previously the default method of lazy
loading modules was to specify a string path to a module:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{path: &#x27;/admin&#x27;, loadChildren: &#x27;./admin/admin.module#AdminModule&#x27;}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This was something built into the Angular toolchain, and performed
some wizardry to parse and load the module from the
&lt;code&gt;loadChildren&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;Since Angular 8, the method of importing modules has changed to the
industry recognised / standard, &lt;a
href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports&quot;&gt;dynamic
import&lt;/a&gt;. So, your import should now be changed to:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;loadChildren: () =&amp;gt; import(&#x27;./admin/admin.module&#x27;).then(m =&amp;gt; m.AdminModule)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Depending upon your IDE choice, you’ll likely get IntelliSense
too:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/angular-with-ivy-uncaught-in-promise-error-cannot-find-module/Capture.png&quot; /&gt;&lt;/p&gt;
</description></item>
<item><title>Azure Pipeline- Fixing ‘The project was restored using Microsoft.NETCore.App version x.x.x</title><link>https://www.ashgrennan.com/post/azure-pipeline-fixing-the-project-was-restored-using-microsoft-netcore-app-version-x-x-x/</link><guid>https://www.ashgrennan.com/post/azure-pipeline-fixing-the-project-was-restored-using-microsoft-netcore-app-version-x-x-x/</guid><pubDate>Wed, 29 May 2019 00:00:00 +0000</pubDate><description>&lt;p&gt;I couldn’t find an answer for this, so I figured a quick post may
help others, since people are experiencing this issue.&lt;/p&gt;
&lt;p&gt;Initially, despite best practice, explicitly setting a version for
the Microsoft.AspNetCore.App package resolved a pipeline library
conflict issue, where it could not determine the correct package to
restore on the Pipeline side.&lt;/p&gt;
&lt;p&gt;Then came the following host of errors:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Error : NETSDK1061: The project was restored using Microsoft.NETCore.App version 1.0.0, but with current settings, version 2.2.0 would be used instead. To resolve this issue, make sure the same settings are used for restore and for subsequent operations such as build or publish. Typically this issue can occur if the RuntimeIdentifier property is set during build or publish but not during restore. For more information, see https://aka.ms/dotnet-runtime-patch-selection.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Some people resolved this issue by specifying their projects to
target the latest runtime, however, in my scenario the pipeline
apparently still wanted to restore using 1.0.0.&lt;/p&gt;
&lt;h3 id=&quot;solutions&quot;&gt;Solution(s)&lt;/h3&gt;
&lt;p&gt;If you’re using the default generated YAML build script, the generic
script the pipeline builds after &lt;em&gt;detecting&lt;/em&gt; your project type
doesn’t work.&lt;/p&gt;
&lt;p&gt;You’ll want to replace&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode yaml&quot;&gt;&lt;code class=&quot;sourceCode yaml&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;- &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; NuGetCommand@2&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;restoreSolution&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;#39;$(solution)&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;with:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode yaml&quot;&gt;&lt;code class=&quot;sourceCode yaml&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;- &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; DotNetCoreCLI@2&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;#39;restore&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;#39;**/*.csproj&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now yes, if you have many projects in your solution this likely would
add a good amount of time to your build, but that’s not an issue for
me.&lt;/p&gt;
&lt;h4 id=&quot;even-easier-solution&quot;&gt;Even Easier Solution:&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/azure-pipeline-fixing-the-project-was-restored-using-microsoft-netcore-app-version-x-x-x/classic-editor.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Using the classic editor, you’ll get a visual build task pipeline
that should get you 90% of the way there by default. Without changing
anything, this almost fully built my default SPA with .NET Core.&lt;/p&gt;
&lt;p&gt;Honestly, I feel a little disappointed in the new YAML builder, yes
it’s nice to have a version controlled build script, but there are so
many people experiencing issues, especially those new to .NET who
shouldn’t have to spend hours getting their vanilla .NET Core app to
build to a branch or staging site.&lt;/p&gt;
</description></item>
<item><title>Broadcast data strategy with .NET Core, SignalR &amp; Angular</title><link>https://www.ashgrennan.com/post/broadcast-data-strategy-with-net-core-signalr-angular/</link><guid>https://www.ashgrennan.com/post/broadcast-data-strategy-with-net-core-signalr-angular/</guid><pubDate>Mon, 06 May 2019 00:00:00 +0000</pubDate><description>&lt;p&gt;In today’s post, we’re going to discuss an increasingly common
requirement of tasks running within a web application. Depending upon
your specialism you can think of these as service workers, or simply
background tasks. For this demo we’re going to focus on our web
application continuously running a task which emits I/O data for the
duration of the app’s lifetime. This data can then be consumed by
another service. Since our task needs to run continuously, it’s better
suited to an observable pattern on the client side, so we can subscribe
and handle new data accordingly. In this scenario, we’re going to use
SignalR to trigger invocations on any connected clients.&lt;/p&gt;
&lt;p&gt;First, you’ll need to do a small amount of configuration; I won’t
elaborate on this since it’s fairly straightforward:&lt;/p&gt;
&lt;h3 id=&quot;startup.cs&quot;&gt;Startup.cs&lt;/h3&gt;
&lt;p&gt;Add the following to your
&lt;code&gt;ConfigureServices(IServiceCollection services)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;services.AddSignalR();&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Note: In Core 3, you won’t need this call, as the Core Team are &lt;a
href=&quot;https://twitter.com/davidfowl/status/1123603857831890944&quot;&gt;tidying
up some of the service extensions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, you’ll want to add an endpoint for your hub in your
&lt;code&gt;Configure(IApplicationBuilder app, IHostingEnvironment env)&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;app&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;UseSignalR&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;routes &lt;span class=&quot;op&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    routes&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;MapHub&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;ChangeDataHub&lt;span class=&quot;op&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;/changes&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the addition of the &lt;code&gt;ChangeDataHub&lt;/code&gt; you’ll need to
create a class that inherits from the base class Hub, we’ll leave this
blank.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; ChangeDataHub&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; Hub&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Good so far? Next we’ll need a Listener service which will manage the
&lt;code&gt;HubManager&lt;/code&gt; allowing us to send invocation messages to
anything which has subscribed to our service.&lt;/p&gt;
&lt;p&gt;But, before this we’ll need to think about how we’re going to use our
listener service. With .NET Core 1 &amp;amp; 2, Microsoft provided an
&lt;code&gt;IWebHost&lt;/code&gt;; however, with the addition of 2.1, a new
interface, &lt;code&gt;IHost&lt;/code&gt;, was introduced. &lt;code&gt;IHost&lt;/code&gt; is
similar to &lt;code&gt;IWebHost&lt;/code&gt; allowing hosted services, DI etc,
however it’s more lightweight because it’s designed for background tasks
that aren’t used over HTTP. Since we’ll be transmitting our data via web
sockets, this is ideal and the right fit for our project.&lt;/p&gt;
&lt;p&gt;Registering a host is easy in .NET Core, within our
&lt;code&gt;Startup.cs&lt;/code&gt;, we’ll want to create a singleton instance of
our host. We’ll be implementing &lt;code&gt;IHostedService&lt;/code&gt;, which is a
great interface that provides just two methods of type &lt;code&gt;Task&lt;/code&gt;
to implement. Below is the interface:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt; Microsoft&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Extensions&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Hosting&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// Summary:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;//     Defines methods for objects that are managed by the host.&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; IHostedService&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-8&quot;&gt;&lt;a href=&quot;#cb3-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-9&quot;&gt;&lt;a href=&quot;#cb3-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// Summary:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-10&quot;&gt;&lt;a href=&quot;#cb3-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// Triggered when the application host is ready to start the service.&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-11&quot;&gt;&lt;a href=&quot;#cb3-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        Task &lt;span class=&quot;fu&quot;&gt;StartAsync&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;CancellationToken cancellationToken&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-12&quot;&gt;&lt;a href=&quot;#cb3-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-13&quot;&gt;&lt;a href=&quot;#cb3-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// Summary:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-14&quot;&gt;&lt;a href=&quot;#cb3-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// Triggered when the application host is performing a graceful shutdown.&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-15&quot;&gt;&lt;a href=&quot;#cb3-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        Task &lt;span class=&quot;fu&quot;&gt;StopAsync&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;CancellationToken cancellationToken&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-16&quot;&gt;&lt;a href=&quot;#cb3-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-17&quot;&gt;&lt;a href=&quot;#cb3-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Plugging in our implementation of the IHostedService as described
above:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;ConfigureServices&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;IServiceCollection services&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      services&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddSingleton&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Microsoft&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Extensions&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Hosting&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;IHostedService&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; ChangeDataListenerService&lt;span class=&quot;op&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now it’s time to implement our interface, like most tasks we don’t
want to simply run this once, but on an incremental basis. To achieve
this we’ll use the static method Task.Delay which will take our delay
value.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;readonly&lt;/span&gt; HubLifetimeManager&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;ChangeDataHub&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; _hubManager&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;readonly&lt;/span&gt; ILogger&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;DefaultLogger&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; _logger&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;readonly&lt;/span&gt; TimeSpan _delay &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; TimeSpan&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;ChartListenerService&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    HubLifetimeManager&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;ChangeDataHub&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; hubManager&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-6&quot;&gt;&lt;a href=&quot;#cb5-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    ILogger&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;DefaultLogger&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; logger&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-7&quot;&gt;&lt;a href=&quot;#cb5-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-8&quot;&gt;&lt;a href=&quot;#cb5-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    _hubManager &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; hubManager&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-9&quot;&gt;&lt;a href=&quot;#cb5-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    _logger &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; logger&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-10&quot;&gt;&lt;a href=&quot;#cb5-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-11&quot;&gt;&lt;a href=&quot;#cb5-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; async Task &lt;span class=&quot;fu&quot;&gt;StartAsync&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;CancellationToken cts&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-12&quot;&gt;&lt;a href=&quot;#cb5-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-13&quot;&gt;&lt;a href=&quot;#cb5-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    _logger&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Listener is starting..&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-14&quot;&gt;&lt;a href=&quot;#cb5-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(!&lt;/span&gt;cts&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;IsCancellationRequested&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-15&quot;&gt;&lt;a href=&quot;#cb5-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-16&quot;&gt;&lt;a href=&quot;#cb5-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        _logger&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Fetching updates..&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-17&quot;&gt;&lt;a href=&quot;#cb5-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; data &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; DataManager&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-18&quot;&gt;&lt;a href=&quot;#cb5-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        await _hubManager&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SendAllAsync&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;TransferData&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; data &lt;span class=&quot;op&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-19&quot;&gt;&lt;a href=&quot;#cb5-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        await Task&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;_delay&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cts&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-20&quot;&gt;&lt;a href=&quot;#cb5-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-21&quot;&gt;&lt;a href=&quot;#cb5-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    _logger&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Listener has completed..&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-22&quot;&gt;&lt;a href=&quot;#cb5-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s our basic implementation completed. It should be noted that if
you’re using a repository which for instance uses a
&lt;code&gt;DbContext&lt;/code&gt; this won’t work, since we’ll be creating a
DbContext instance that can become a singleton, which can lead to a
whole host of problems.&lt;/p&gt;
&lt;p&gt;The solution to this problem is injecting an
&lt;code&gt;IServiceScopeFactory&lt;/code&gt; instance and using it when we need a
scoped instance of a service.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;readonly&lt;/span&gt; HubLifetimeManager&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;ChangeDataHub&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; _hubManager&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;readonly&lt;/span&gt; ILogger&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;DefaultLogger&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; _logger&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;readonly&lt;/span&gt; TimeSpan _delay &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; TimeSpan&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;readonly&lt;/span&gt; IServiceScopeFactory _scopeFactory&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-5&quot;&gt;&lt;a href=&quot;#cb6-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;ChartListenerService&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-6&quot;&gt;&lt;a href=&quot;#cb6-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    HubLifetimeManager&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;ChangeDataHub&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; hubManager&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-7&quot;&gt;&lt;a href=&quot;#cb6-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    ILogger&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;DefaultLogger&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; logger&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-8&quot;&gt;&lt;a href=&quot;#cb6-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  IServiceScopeFactory scopeFactory&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-9&quot;&gt;&lt;a href=&quot;#cb6-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-10&quot;&gt;&lt;a href=&quot;#cb6-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    _hubManager &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; hubManager&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-11&quot;&gt;&lt;a href=&quot;#cb6-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    _logger &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; logger&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-12&quot;&gt;&lt;a href=&quot;#cb6-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  _scopeFactory &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; scopeFactory&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-13&quot;&gt;&lt;a href=&quot;#cb6-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-14&quot;&gt;&lt;a href=&quot;#cb6-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; async Task &lt;span class=&quot;fu&quot;&gt;StartAsync&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;CancellationToken cts&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-15&quot;&gt;&lt;a href=&quot;#cb6-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-16&quot;&gt;&lt;a href=&quot;#cb6-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    _logger&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Listener is starting..&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-17&quot;&gt;&lt;a href=&quot;#cb6-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(!&lt;/span&gt;cts&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;IsCancellationRequested&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-18&quot;&gt;&lt;a href=&quot;#cb6-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-19&quot;&gt;&lt;a href=&quot;#cb6-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;co&quot;&gt;// Scoped service generation, resolve and dependencies in here&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-20&quot;&gt;&lt;a href=&quot;#cb6-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; scope &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; _scopeFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;CreateScope&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-21&quot;&gt;&lt;a href=&quot;#cb6-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-22&quot;&gt;&lt;a href=&quot;#cb6-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;/span&gt;
&lt;span id=&quot;cb6-23&quot;&gt;&lt;a href=&quot;#cb6-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        _logger&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Fetching updates..&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-24&quot;&gt;&lt;a href=&quot;#cb6-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; dataManager &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; scope&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ServiceProvider&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;DataManager&lt;span class=&quot;op&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-25&quot;&gt;&lt;a href=&quot;#cb6-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;/span&gt;
&lt;span id=&quot;cb6-26&quot;&gt;&lt;a href=&quot;#cb6-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        await _hubManager&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SendAllAsync&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;TransferData&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; dataManager&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-27&quot;&gt;&lt;a href=&quot;#cb6-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-28&quot;&gt;&lt;a href=&quot;#cb6-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        await Task&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;_delay&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cts&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-29&quot;&gt;&lt;a href=&quot;#cb6-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-30&quot;&gt;&lt;a href=&quot;#cb6-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    _logger&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Listener has completed..&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-31&quot;&gt;&lt;a href=&quot;#cb6-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This also means we don’t have to inject all our service dependencies
through the constructor, a good thing in a larger, real-world
example.&lt;/p&gt;
&lt;p&gt;In the next post, I’ll go over how to connect and consume this
information on the client side.&lt;/p&gt;
</description></item>
<item><title>EF Core – Seed Data Strategy</title><link>https://www.ashgrennan.com/post/ef-core-seed-data-strategy/</link><guid>https://www.ashgrennan.com/post/ef-core-seed-data-strategy/</guid><pubDate>Sun, 06 Jan 2019 00:00:00 +0000</pubDate><description>&lt;p&gt;In this post, we’re going to explore the &lt;em&gt;relatively&lt;/em&gt; new
entity data seeding method which was introduced with EF Core 2.1.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s fantastic&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Unlike its predecessor EF6, seed data can be associated with
migrations and its relative entity type as part of your migration
strategy. What this means is adding or editing any seed data for any
entity will compute the correct insert(s) or updates to push those data
changes into your DB. This is incredibly powerful and leverages the
benefits of tracking that migrations provide.&lt;/p&gt;
&lt;h3 id=&quot;lets-see-some-code&quot;&gt;Let’s see some code&lt;/h3&gt;
&lt;p&gt;First, you’ll need to override the &lt;code&gt;OnModelCreating&lt;/code&gt;
method in your DbContext, chances are you’re already doing this in an
existing project.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OnModelCreating&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ModelBuilder builder&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;OnModelCreating&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;   &lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, we’ll call the &lt;code&gt;HasData&lt;/code&gt; method and pass in a
populated instance of an entity.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OnModelCreating&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ModelBuilder builder&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;OnModelCreating&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        modelBuilder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Entity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;HasData&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; Person &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; PersonId &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Ash&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Good so far? Before running our &lt;code&gt;add-migration&lt;/code&gt; command, I
want to touch on a few key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Primary keys for all entities must be specified; this is how the
seed data change detection works, by comparing whether the particular
record exists or any fields have changed&lt;/li&gt;
&lt;li&gt;Any entities with foreign keys must be explicitly stated&lt;/li&gt;
&lt;li&gt;Previously seeded data that has a primary key change will be removed
(this makes sense, since the record can no longer be referred to)&lt;/li&gt;
&lt;li&gt;You may want custom initialization logic depending on your scenario
such as temporary data for testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, running &lt;code&gt;add-migration firstseed&lt;/code&gt; will produce a
migration for entering our new records (since their primary keys don’t
exist in the table).&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;migrationBuilder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;InsertData&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    table&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Persons&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    columns&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;PersonId&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Name&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    values&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Ash&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Any changes to this data will result in a new migration with correct
behaviour (insert/update/delete) making seed data much easier to
progress as your application develops, rather than a monolithic script
of inserts.&lt;/p&gt;
&lt;h3 id=&quot;further-points&quot;&gt;Further Points&lt;/h3&gt;
&lt;h5 id=&quot;structuring-seed-data&quot;&gt;Structuring Seed Data&lt;/h5&gt;
&lt;p&gt;Adding seed data with the &lt;code&gt;OnModelCreating&lt;/code&gt; can become
difficult to manage, especially with dozens of entities; personally, I
would opt to create extension methods to keep this method clean.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OnModelCreating&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ModelBuilder builder&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;OnModelCreating&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;builder&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        builder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddContractHoursData&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        builder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddLocationData&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        builder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddJobTitleData&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-7&quot;&gt;&lt;a href=&quot;#cb4-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Additionally, creating a single contained class for each data entity
was my approach, adhering to the Single Responsibility Principle and
violating DRY.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;AddRewardCategoryData&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt; ModelBuilder model&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        model&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Entity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;HasData&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;RewardCategory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-6&quot;&gt;&lt;a href=&quot;#cb5-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-7&quot;&gt;&lt;a href=&quot;#cb5-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                RewardCategoryId &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-8&quot;&gt;&lt;a href=&quot;#cb5-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                Name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Vouchers&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-9&quot;&gt;&lt;a href=&quot;#cb5-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                Enabled &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-10&quot;&gt;&lt;a href=&quot;#cb5-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-11&quot;&gt;&lt;a href=&quot;#cb5-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;RewardCategory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-12&quot;&gt;&lt;a href=&quot;#cb5-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-13&quot;&gt;&lt;a href=&quot;#cb5-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                RewardCategoryId &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-14&quot;&gt;&lt;a href=&quot;#cb5-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                Name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Charity&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-15&quot;&gt;&lt;a href=&quot;#cb5-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;                Enabled &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-16&quot;&gt;&lt;a href=&quot;#cb5-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-17&quot;&gt;&lt;a href=&quot;#cb5-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-18&quot;&gt;&lt;a href=&quot;#cb5-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id=&quot;creating-identity-users&quot;&gt;Creating Identity Users&lt;/h5&gt;
&lt;p&gt;One question I’ve seen people ask is seeding users from Identity
(since &lt;code&gt;UserManager&lt;/code&gt; takes care of things like Password
Hash).&lt;/p&gt;
&lt;p&gt;We can use Identity’s &lt;code&gt;PasswordHasher&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, there’s a problem with this approach: it will generate a
different hash on every run, and therefore bloat every migration with
updates. Additionally, you’d rather avoid hard-coding dozens or hundreds
of &lt;code&gt;User&lt;/code&gt; models.&lt;/p&gt;
&lt;p&gt;My strategy for this approach was the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use a library such as &lt;a
href=&quot;https://www.nuget.org/packages/Faker.Net/&quot;&gt;Faker&lt;/a&gt; to generate a
list of user details&lt;/li&gt;
&lt;li&gt;Iterate through the list, assign a hard-coded GUID from a list
you’ve created&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;PasswordHasher&lt;/code&gt; to generate the hashed
password&lt;/li&gt;
&lt;li&gt;Pass the completed list to the &lt;code&gt;HasData&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;Copy the data within the AspNetUsers table in your database as JSON
(I used SQL Operations Studio for this)&lt;/li&gt;
&lt;li&gt;Use some Find and replace with regular expressions to fix formatting
errors and form into a list of Users&lt;/li&gt;
&lt;li&gt;You should end up with a list of populated entities, all with
hard-coded data, without tedious data entry&lt;/li&gt;
&lt;/ul&gt;
</description></item>
<item><title>T4 Multiple File Outputs in a .NET Core Project</title><link>https://www.ashgrennan.com/post/t4-multiple-file-outputs-in-a-net-core-project/</link><guid>https://www.ashgrennan.com/post/t4-multiple-file-outputs-in-a-net-core-project/</guid><pubDate>Sat, 03 Nov 2018 00:00:00 +0000</pubDate><description>&lt;p&gt;I recently needed to auto-generate boilerplate classes for a project
based upon reflection metadata. It had been a few years since I’d
leveraged T4 for SQL Stored Proc generation and the awesome &lt;a
href=&quot;https://github.com/T4MVC/T4MVC&quot;&gt;T4MVC&lt;/a&gt; template, so surely
there’s a shiny new method?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/daveaglick/Scripty&quot;&gt;Scripty&lt;/a&gt; comes up
as an alternative scripting approach for code generation, unfortunately
it’s having &lt;a
href=&quot;https://github.com/daveaglick/Scripty/issues/127&quot;&gt;major issues&lt;/a&gt;
with VS 2017 and the Roslyn compiler.&lt;/p&gt;
&lt;p&gt;So, it looks like T4 Templating is still the way to go. However,
whilst writing the template, it appears that splitting code into
multiple files and moving directories is quite a problem, as the
templating standard behaviour is to nest files under the parent
template.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;First, we’re going to leverage the T4 Toolbox extension. Yes, this
works in VS 2017 at the time of writing (I have update 15.8.9), you can
find this &lt;a href=&quot;https://github.com/olegsych/T4Toolbox&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h3&gt;
&lt;p&gt;So our strategy here is to have a Runner template and a code
generation template.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Runner template – This will be responsible for initialisation of the
code generation template, our method is to enumerate through a
collection and pass data to our template&lt;/li&gt;
&lt;li&gt;Code generation template – This will contain our templating
code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please note – The below represents a basic example, and is not
production code.&lt;/p&gt;
&lt;h5 id=&quot;templaterunner.tt&quot;&gt;TemplateRunner.tt&lt;/h5&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ template language&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;C#&amp;quot;&lt;/span&gt; debug&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;false&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;          &lt;span class=&quot;co&quot;&gt;//#A&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ output extension&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;cs&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;                         &lt;span class=&quot;co&quot;&gt;//#A&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ import &lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.Linq&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;                &lt;span class=&quot;co&quot;&gt;//#A&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ import &lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.Text&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;                &lt;span class=&quot;co&quot;&gt;//#A&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ import &lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.IO&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;                  &lt;span class=&quot;co&quot;&gt;//#A&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ import &lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.Collections.Generic&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#A&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ include file&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;BaseGeneratorApi.tt&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#B&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;BaseGeneratorApi baseTemplate &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;BaseGeneratorApi&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#B&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; currentFolder &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;FileInfo&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Host&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ResolvePath&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Host&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;TemplateFile&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;DirectoryName&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#C&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; webProject &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; Path&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;GetFullPath&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Path&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Combine&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;currentFolder&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; @&lt;span class=&quot;st&quot;&gt;&amp;quot;../&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;src&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#C&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; apiControllerNames &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; File&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ReadAllLines&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Path&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Combine&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;currentFolder&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;bin&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Debug&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;netcoreapp2.1&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;webapinames.txt&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#D&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-14&quot;&gt;&lt;a href=&quot;#cb1-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-15&quot;&gt;&lt;a href=&quot;#cb1-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;apiControllerNames &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apiControllerNames&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-16&quot;&gt;&lt;a href=&quot;#cb1-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;/span&gt;
&lt;span id=&quot;cb1-17&quot;&gt;&lt;a href=&quot;#cb1-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;kw&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;string&lt;/span&gt; className &lt;span class=&quot;kw&quot;&gt;in&lt;/span&gt; apiControllerNames&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-18&quot;&gt;&lt;a href=&quot;#cb1-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-19&quot;&gt;&lt;a href=&quot;#cb1-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    baseTemplate&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;controllerName&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; className&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#E&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-20&quot;&gt;&lt;a href=&quot;#cb1-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    baseTemplate&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;controllerNameTitled&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ToLower&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;className&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt; className&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Substring&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#E&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-21&quot;&gt;&lt;a href=&quot;#cb1-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-22&quot;&gt;&lt;a href=&quot;#cb1-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; controllerFileName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; $&lt;span class=&quot;st&quot;&gt;&amp;quot;{className}Controller.cs&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-23&quot;&gt;&lt;a href=&quot;#cb1-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-24&quot;&gt;&lt;a href=&quot;#cb1-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    baseTemplate&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RenderToFile&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Path&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Combine&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;Autogenerated&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; controllerFileName&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt; #&lt;span class=&quot;co&quot;&gt;//F&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-25&quot;&gt;&lt;a href=&quot;#cb1-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-26&quot;&gt;&lt;a href=&quot;#cb1-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-27&quot;&gt;&lt;a href=&quot;#cb1-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;#&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let us go through the code above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;#A&lt;/strong&gt; – We want to output .cs files, and we’ll need
those namespaces to import the required classes for enumeration/file
reading&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;#B&lt;/strong&gt; – Here we instantiate our code generation
template, in this example it’s a template which generates a blank Web
API controller&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;#C&lt;/strong&gt; – Since the templates base directory at runtime
is within a subdirectory of your VS folder, we need to do a little
configuration, for this example we want to read a text file in our bin
folder&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;#D&lt;/strong&gt; – Reading of a text file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;#E&lt;/strong&gt; – We pass a few strings to our own code
generation template, since these will be the subject of said class&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;#F&lt;/strong&gt; – We call the &lt;code&gt;RenderToFile&lt;/code&gt; method,
passing a file path for our class&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;basegeneratorapi.tt&quot;&gt;BaseGeneratorApi.tt&lt;/h5&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ output extension&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;cs&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ include file&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;T4Toolbox.tt&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#A&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ assembly name&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.Core&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ import &lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.Linq&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ import &lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.Text&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ import &lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.IO&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#@ import &lt;span class=&quot;kw&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;System.Collections.Generic&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#&lt;span class=&quot;op&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-9&quot;&gt;&lt;a href=&quot;#cb2-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BaseGeneratorRepository &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; CSharpTemplate &lt;span class=&quot;co&quot;&gt;//#B&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-10&quot;&gt;&lt;a href=&quot;#cb2-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-11&quot;&gt;&lt;a href=&quot;#cb2-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;string&lt;/span&gt; repositoryName&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-12&quot;&gt;&lt;a href=&quot;#cb2-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;string&lt;/span&gt; repositoryNameTitled&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-13&quot;&gt;&lt;a href=&quot;#cb2-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-14&quot;&gt;&lt;a href=&quot;#cb2-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;TransformText&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;//#B&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-15&quot;&gt;&lt;a href=&quot;#cb2-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-16&quot;&gt;&lt;a href=&quot;#cb2-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;TransformText&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-17&quot;&gt;&lt;a href=&quot;#cb2-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-18&quot;&gt;&lt;a href=&quot;#cb2-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;#&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-19&quot;&gt;&lt;a href=&quot;#cb2-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-20&quot;&gt;&lt;a href=&quot;#cb2-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;//All your code goes here!&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-21&quot;&gt;&lt;a href=&quot;#cb2-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;api/[controller]&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-22&quot;&gt;&lt;a href=&quot;#cb2-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt;Authorize&lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-23&quot;&gt;&lt;a href=&quot;#cb2-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; $&lt;span class=&quot;st&quot;&gt;&amp;quot;{controllerName}Controller&amp;quot;&lt;/span&gt; #&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; ControllerBase   &lt;/span&gt;
&lt;span id=&quot;cb2-24&quot;&gt;&lt;a href=&quot;#cb2-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-25&quot;&gt;&lt;a href=&quot;#cb2-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-26&quot;&gt;&lt;a href=&quot;#cb2-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-27&quot;&gt;&lt;a href=&quot;#cb2-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-28&quot;&gt;&lt;a href=&quot;#cb2-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;#&lt;span class=&quot;op&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-29&quot;&gt;&lt;a href=&quot;#cb2-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;GenerationEnvironment&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-30&quot;&gt;&lt;a href=&quot;#cb2-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-31&quot;&gt;&lt;a href=&quot;#cb2-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-32&quot;&gt;&lt;a href=&quot;#cb2-32&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;#&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;#A&lt;/strong&gt; – Here we’ll leverage the helpers which T4
Toolbox provides, therefore you’ll need to include the file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;#B&lt;/strong&gt; – We’ll want to inherit from the abstract class
&lt;code&gt;CSharpTemplate&lt;/code&gt; which is part of the &lt;a
href=&quot;https://github.com/olegsych/T4Toolbox/blob/master/src/T4Toolbox/CSharpTemplate.cs&quot;&gt;T4
Toolbox&lt;/a&gt;. In our derived class we’ll access the
&lt;code&gt;TransformText&lt;/code&gt; method in the base class to append our
additional code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can have multiple code generation templates which are used within
a single template runner, however it increases the scope of a problem
occurring in the transformations, and therefore a 1:1 ratio is
better.&lt;/p&gt;
</description></item>
<item><title>NopCommerce 3.8 - Wait Operation Timeout</title><link>https://www.ashgrennan.com/post/nopcommerce-3-8-wait-operation-timeout/</link><guid>https://www.ashgrennan.com/post/nopcommerce-3-8-wait-operation-timeout/</guid><pubDate>Sun, 17 Jun 2018 00:00:00 +0000</pubDate><description>&lt;p&gt;With NopCommerce I’ve noticed high waits on SQL queries resolving
when hitting a database in a test environment for local development.
This problem becomes more apparent when your seed / test dataset is
quite large.&lt;/p&gt;
&lt;p&gt;When logging into the Administration area, I would frequently get the
following, despite setting a high connection timeout period:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Exception Details: System.ComponentModel.Win32Exception: The wait operation timed out
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[Win32Exception (0x80004005): The wait operation timed out]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The stack trace pointed towards PageList.cs class, the method hit
returns an &lt;code&gt;IQueryable&lt;/code&gt; DbSet. The initial Administrator
dashboard performs a lot of I/O reads which has a large contribution
towards the timeout issue.&lt;/p&gt;
&lt;h3 id=&quot;solution&quot;&gt;Solution&lt;/h3&gt;
&lt;p&gt;All the dashboard statistic calls are within a single View which can
be found at:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Nop.Admin/Views/Home/Index.cshtml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To avoid running these on your machine, use the
&lt;code&gt;HttpContext.Current.IsDebuggingEnabled&lt;/code&gt; and wrap in an
&lt;code&gt;If&lt;/code&gt;, like below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;col-md-12&amp;quot;&amp;gt;

            @if (!HttpContext.Current.IsDebuggingEnabled)
            {
                @Html.Widget(&amp;quot;admin_dashboard_top&amp;quot;)
                if (!Model.IsLoggedInAsVendor)
                {
                    &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
                        &amp;lt;div class=&amp;quot;col-md-12&amp;quot;&amp;gt;
                            @Html.Action(&amp;quot;NopCommerceNews&amp;quot;, &amp;quot;Home&amp;quot;)
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                }
                @Html.Widget(&amp;quot;admin_dashboard_news_after&amp;quot;)
                if (!Model.IsLoggedInAsVendor &amp;amp;&amp;amp; canManageOrders &amp;amp;&amp;amp; canManageCustomers &amp;amp;&amp;amp; canManageProducts &amp;amp;&amp;amp; canManageReturnRequests)
                {
                    &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
                        &amp;lt;div class=&amp;quot;col-md-12&amp;quot;&amp;gt;
                            @Html.Action(&amp;quot;CommonStatistics&amp;quot;, &amp;quot;Home&amp;quot;)
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                }
                @Html.Widget(&amp;quot;admin_dashboard_commonstatistics_after&amp;quot;)
                if (!Model.IsLoggedInAsVendor &amp;amp;&amp;amp; (canManageOrders || canManageCustomers))
                {
                    &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
                        @if (!Model.IsLoggedInAsVendor &amp;amp;&amp;amp; canManageOrders)
                        {
                            &amp;lt;div class=&amp;quot;col-md-6&amp;quot;&amp;gt;
                                @Html.Action(&amp;quot;OrderStatistics&amp;quot;, &amp;quot;Order&amp;quot;)
                            &amp;lt;/div&amp;gt;
                        }
                        @if (!Model.IsLoggedInAsVendor &amp;amp;&amp;amp; canManageCustomers)
                        {
                            &amp;lt;div class=&amp;quot;col-md-6&amp;quot;&amp;gt;
                                @Html.Action(&amp;quot;CustomerStatistics&amp;quot;, &amp;quot;Customer&amp;quot;)
                            &amp;lt;/div&amp;gt;
                        }
                    &amp;lt;/div&amp;gt;
                }
                @Html.Widget(&amp;quot;admin_dashboard_customerordercharts_after&amp;quot;)
                if (!Model.IsLoggedInAsVendor &amp;amp;&amp;amp; canManageOrders)
                {
                    &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
                        &amp;lt;div class=&amp;quot;col-md-8&amp;quot;&amp;gt;
                            @Html.Action(&amp;quot;OrderAverageReport&amp;quot;, &amp;quot;Order&amp;quot;)
                        &amp;lt;/div&amp;gt;
                        &amp;lt;div class=&amp;quot;col-md-4&amp;quot;&amp;gt;
                            @Html.Action(&amp;quot;OrderIncompleteReport&amp;quot;, &amp;quot;Order&amp;quot;)
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                }
                @Html.Widget(&amp;quot;admin_dashboard_orderreports_after&amp;quot;)
                if (!Model.IsLoggedInAsVendor &amp;amp;&amp;amp; (canManageOrders || canManageProducts))
                {
                    &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
                        @if (canManageOrders)
                        {
                            &amp;lt;div class=&amp;quot;col-md-8&amp;quot;&amp;gt;
                                @Html.Action(&amp;quot;LatestOrders&amp;quot;, &amp;quot;Order&amp;quot;)
                            &amp;lt;/div&amp;gt;
                        }
                        &amp;lt;div class=&amp;quot;col-md-4&amp;quot;&amp;gt;
                            @if (canManageProducts)
                            {
                                @Html.Action(&amp;quot;PopularSearchTermsReport&amp;quot;, &amp;quot;Common&amp;quot;)
                            }
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                }
                @Html.Widget(&amp;quot;admin_dashboard_latestorders_searchterms_after&amp;quot;)
                if (canManageOrders)
                {
                    &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
                        &amp;lt;div class=&amp;quot;col-md-6&amp;quot;&amp;gt;
                            @Html.Action(&amp;quot;BestsellersBriefReportByQuantity&amp;quot;, &amp;quot;Order&amp;quot;)
                        &amp;lt;/div&amp;gt;
                        &amp;lt;div class=&amp;quot;col-md-6&amp;quot;&amp;gt;
                            @Html.Action(&amp;quot;BestsellersBriefReportByAmount&amp;quot;, &amp;quot;Order&amp;quot;)
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                }
                @Html.Widget(&amp;quot;admin_dashboard_bottom&amp;quot;)
            }

        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the context and environment of the project, you may want
to write a more robust solution, however for local debugging this is
perfectly fine.&lt;/p&gt;
</description></item>
<item><title>Fixing MSBuild CS1525, CS1003 Compiler Errors</title><link>https://www.ashgrennan.com/post/fixing-msbuild-cs103-cs012-compiler-errors/</link><guid>https://www.ashgrennan.com/post/fixing-msbuild-cs103-cs012-compiler-errors/</guid><pubDate>Fri, 01 Jun 2018 00:00:00 +0000</pubDate><description>&lt;p&gt;Recently I needed to use MSBuild to compile a solution for a build
server.&lt;/p&gt;
&lt;p&gt;The MSBuild was located at:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C:\Program Files (x86)\MSBuild\14.0\Bin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The following errors were occurring despite this working in the
past:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error CS1525: Invalid expression term &amp;#39;decimal&amp;#39;
Syntax error, &amp;#39;,&amp;#39; expected
error CS1003: Syntax error, &amp;#39;,&amp;#39; expected [Web.csproj]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was due to C# 6/7 features which aren’t supported by this
version of MSBuild.&lt;/p&gt;
&lt;h4 id=&quot;the-fix&quot;&gt;The Fix&lt;/h4&gt;
&lt;p&gt;Using this version of MSBuild resolved the issue:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin&lt;/code&gt;&lt;/p&gt;
</description></item>
<item><title>ChromeDriver for Data Scraping</title><link>https://www.ashgrennan.com/post/chromedriver-for-data-scraping/</link><guid>https://www.ashgrennan.com/post/chromedriver-for-data-scraping/</guid><pubDate>Mon, 06 Nov 2017 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;selenium&quot;&gt;Selenium&lt;/h3&gt;
&lt;p&gt;I have used &lt;a
href=&quot;https://github.com/SeleniumHQ/selenium&quot;&gt;Selenium&lt;/a&gt; on occasion
for automated frontend tests, it’s my go-to for web automation testing
and provides an interface to enable browsers to use the libraries.&lt;/p&gt;
&lt;p&gt;&lt;a
href=&quot;https://sites.google.com/a/chromium.org/chromedriver/&quot;&gt;ChromeDriver&lt;/a&gt;
is the implementation of the Chrome browser using the &lt;a
href=&quot;https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol&quot;&gt;WebDriver
protocols&lt;/a&gt; developed by Selenium. You can think of this in three
blocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chrome – your browser&lt;/li&gt;
&lt;li&gt;WebDriver and language protocols – the driver&lt;/li&gt;
&lt;li&gt;Executable by the Chromium project – the middleware that you’ll
download&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, it’s worth pointing out that there are implementations for
browsers such as Firefox and Edge, handy for cross-browser testing!&lt;/p&gt;
&lt;h3 id=&quot;but-for-data-scraping&quot;&gt;But for data scraping?&lt;/h3&gt;
&lt;p&gt;First, let’s make sure we’re on the same page. By data scraping I
mean grabbing information from web pages programmatically, i.e. parsing
the HTML markup for that sweet data. Granted, thanks to the popularity
of RESTful APIs, we don’t need to do this as much, but it’s still
required from time to time.&lt;/p&gt;
&lt;p&gt;At an abstract level, you’re making a request for a web page and
receiving the markup as a response which can then be parsed; it’s
trivial to do in many languages. A basic C# example would be:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; wc &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;WebClient&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; html &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; wc&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;DownloadString&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;http://somewebsite.com/some-page.html&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And use a library such as &lt;a
href=&quot;https://www.nuget.org/packages/HtmlAgilityPack/&quot;&gt;HtmlAgilityPack&lt;/a&gt;
for all your parsing and selector needs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt; – Many web apps now use JavaScript-based
frameworks such as Angular, React and Vue to pull information into an
application client-side. This uses the browser’s JavaScript engine
(called V8 in Chrome). Without the JavaScript being executed, we’re just
pulling in syntax without content. We’re visiting a house but no one is
home.&lt;/p&gt;
&lt;p&gt;(It’s worth pointing out that many highly popular websites such as &lt;a
href=&quot;https://amazon.co.uk/&quot;&gt;Amazon&lt;/a&gt; deliberately obfuscate their
markup and rely on JavaScript to parse it, preventing their data from
being mined.)&lt;/p&gt;
&lt;h3 id=&quot;solving-the-problem&quot;&gt;Solving the problem&lt;/h3&gt;
&lt;p&gt;Here’s where our automated testing tool comes in! As the executable
uses Chrome’s libraries, we could use this in theory to render our
target website, JavaScript included, and programmatically get the data
we want.&lt;/p&gt;
&lt;h3 id=&quot;trying-this-out&quot;&gt;Trying this out&lt;/h3&gt;
&lt;p&gt;For this example I’ll be using the amazon.co.uk website, it should be
noted that data scraping is a grey area, and is likely against their
ToS. However it serves as a good educational example for otherwise
unobtainable data.&lt;/p&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;Install Chrome if you don’t have it&lt;/li&gt;
&lt;li&gt;Download the Chromium exe found &lt;a
href=&quot;https://sites.google.com/a/chromium.org/chromedriver/downloads&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For this example, I’ll create a simple Console Application&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-for-data-scraping/01.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next we’ll need the Selenium.WebDriver package, which we can find via
the NuGet solution window&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-for-data-scraping/02.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Using the Selenium library, we’re going to load our Chromium
executable and tell Chrome to browse to a product page&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-for-data-scraping/03.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Using the FindElementById method, we’ll pass in the id of the element
which contains the information we would like. Our finished demo looks
like this&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-for-data-scraping/04.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-for-data-scraping/05-768x554.jpg&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This isn’t a better solution than a code-only approach due to the
overhead of creating an instance of Chrome and the loading and rendering
of page(s), however it definitely has use cases especially when dealing
with heavy dynamic web apps.&lt;/p&gt;
</description></item>
<item><title>ChromeDriver Performance C#</title><link>https://www.ashgrennan.com/post/chromedriver-performance-c-sharp/</link><guid>https://www.ashgrennan.com/post/chromedriver-performance-c-sharp/</guid><pubDate>Sun, 05 Nov 2017 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;scenario&quot;&gt;Scenario&lt;/h3&gt;
&lt;p&gt;So you’ve got a good test suite for automated browser testing,
however you’re testing a web app with different user roles, or case
scenarios, and that 1 minute test has now grown to 5 minutes and is
becoming a bottleneck in the development lifecycle. I ran into this
exact problem myself.&lt;/p&gt;
&lt;p&gt;In this post, we will be looking at how to reduce elapsed time
between operations, and overall reduce total test suite execution
time.&lt;/p&gt;
&lt;h3 id=&quot;the-setup&quot;&gt;The Setup&lt;/h3&gt;
&lt;h4 id=&quot;web-page&quot;&gt;Web Page&lt;/h4&gt;
&lt;p&gt;For the post I’ll be using the same website and loading the same web
page. I’ve chosen a page with multiple round trips, images, videos, and
several megabytes of content to simulate a sizable and real-world web
app. Chances are your tests will be fast on static HTML pages regardless
of how your tests are written. Below are &lt;a
href=&quot;https://developers.google.com/speed/pagespeed/insights&quot;&gt;Google
PageSpeed Insights&lt;/a&gt; results for this page.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-performance-c-sharp/google-pagespeed-insight-1.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-performance-c-sharp/google-pagespeed-insight-2.jpg&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;software&quot;&gt;Software&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a
href=&quot;https://sites.google.com/a/chromium.org/chromedriver/downloads&quot;&gt;ChromeDriver
2.33&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
href=&quot;https://www.nuget.org/packages/Selenium.WebDriver/3.6.0&quot;&gt;Selenium
WebDriver 3.6.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OS – Windows 10&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;performance-tests&quot;&gt;Performance Tests&lt;/h3&gt;
&lt;p&gt;As we’re making an HTTP request to a remote server, there are many
variables that could affect performance outside of our control. Using
explicit or implicit waits doesn’t help us here since they’re designed
to wait until a specific element appears via polling the DOM. We want to
try to eliminate server variance so we’ll use two test sets, one on a
local server, and one remote.&lt;/p&gt;
&lt;h4 id=&quot;finding-ui-elements&quot;&gt;Finding UI Elements&lt;/h4&gt;
&lt;p&gt;Our most common operation will be finding elements on a page and
checking if their respective values align with our expectations. The
&lt;code&gt;ChromeDriver&lt;/code&gt; instance exposes the &lt;code&gt;FindElement&lt;/code&gt;
and &lt;code&gt;FindElements&lt;/code&gt; methods which we’ll be using to find our
DOM objects. There are several methods to find elements and we’ll be
focusing on the three most popular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;By XPath&lt;/li&gt;
&lt;li&gt;By ID&lt;/li&gt;
&lt;li&gt;By Class Name&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;setup&quot;&gt;Setup&lt;/h4&gt;
&lt;p&gt;We’re going to keep this simple and create a blank C# Console
Application with the minimum code to test performance, I’ll be using the
&lt;a
href=&quot;https://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch(v=vs.110).aspx&quot;&gt;StopWatch&lt;/a&gt;
class to store elapsed time for each operation. An arbitrary sample size
of 1500 iterations (500 per FindElementBy call) will be used to create
an average for our results.&lt;/p&gt;
&lt;h4 id=&quot;test-results&quot;&gt;Test Results&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-performance-c-sharp/local-chromedriver-findby-speed.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-performance-c-sharp/remote-chromedriver-findby-speed.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Our results demonstrate that finding an element by ID is the most
efficient, this is also &lt;a
href=&quot;http://www.seleniumhq.org/docs/03_webdriver.jsp&quot;&gt;in line with the
documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;optimizations&quot;&gt;Optimizations&lt;/h3&gt;
&lt;p&gt;So our tests so far look good and produce the expected output, we’re
going to use the same two tests whilst trying different methods of
potential optimization and observe the output.&lt;/p&gt;
&lt;h3 id=&quot;browser-extensions&quot;&gt;Browser Extensions&lt;/h3&gt;
&lt;p&gt;With the amount of HTTP requests ever increasing, especially 3rd
party requests, we may benefit from something which filters requests and
reduces them. It makes sense that a faster page would mean faster tests.
For this test we’re going to use &lt;a
href=&quot;https://github.com/gorhill/uBlock&quot;&gt;uBlock&lt;/a&gt; Origin, available as
a &lt;a
href=&quot;https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm?hl=en&quot;&gt;Chrome
Extension&lt;/a&gt;. I’ve chosen uBlock because it has a lightweight footprint
and generally makes &lt;a href=&quot;https://github.com/gorhill/uBlock&quot;&gt;Chrome
leaner&lt;/a&gt; via less CPU &amp;amp; RAM consumed by the browser.&lt;/p&gt;
&lt;h4 id=&quot;setup-1&quot;&gt;Setup&lt;/h4&gt;
&lt;p&gt;To use the extension we’ll need to grab the .crx file, (you’ll need a
third-party tool such as &lt;a href=&quot;http://crxextractor.com/&quot;&gt;this&lt;/a&gt;)
and load it into our ChromeDriver instance. Below is the code for
this&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; cdOptions &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;ChromeOptions&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;cdOptions&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddExtension&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;@&lt;span class=&quot;st&quot;&gt;&amp;quot;C:&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;emp\uBlock-Origin_v1.14.20.crx&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;//Directory where our Chronium executable is located&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; cdFilePath &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; @&lt;span class=&quot;st&quot;&gt;&amp;quot;C:&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;empMonitor&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; cd &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;ChromeDriver&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cdFilePath&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cdOptions&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; TimeSpan&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;results&quot;&gt;Results&lt;/h4&gt;
&lt;p&gt;We’re going to compare the results with our remote test&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-performance-c-sharp/remote-chromedriver-findby-speed-ublock.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The results speak for themselves; we’re loading a remote page and
finding elements 200-300 milliseconds quicker due to uBlock. This has
another benefit of reducing the probability of a third party HTTP
request hanging and causing the page to never finish loading, which will
eventually cause a &lt;code&gt;TimeOutException&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;headless-chrome&quot;&gt;Headless Chrome&lt;/h3&gt;
&lt;p&gt;A new feature of ChromeDriver is the ability to run the browser
headless, so you won’t get a GUI, but you’ll still be able to see it in
process explorer. Headless has advantages in terms of speed as there’s
no rendering of the browser, however naturally there are drawbacks like
simulating a user’s input and not being able to see a problem on your
page. For brevity we’re going to ignore the pros and cons and simply
look at performance impact.&lt;/p&gt;
&lt;h4 id=&quot;setup-2&quot;&gt;Setup&lt;/h4&gt;
&lt;p&gt;For this setup we’ll simply be passing in the headless argument, in
headless mode the console receives a lot of warnings, so we will hide
those for now.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode csharp&quot;&gt;&lt;code class=&quot;sourceCode cs&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; cdOptions &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;ChromeOptions&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;cdOptions&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddArgument&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;@&lt;span class=&quot;st&quot;&gt;&amp;quot;--headless&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;cdOptions&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;AddArguments&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;@&lt;span class=&quot;st&quot;&gt;&amp;quot;--log-level=3&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;//Directory where our Chronium executable is located&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; cdFilePath &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; @&lt;span class=&quot;st&quot;&gt;&amp;quot;C:&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;empMonitor&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-9&quot;&gt;&lt;a href=&quot;#cb2-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;var&lt;/span&gt; cd &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;ChromeDriver&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cdFilePath&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cdOptions&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; TimeSpan&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;results-1&quot;&gt;Results&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/chromedriver-performance-c-sharp/remote-chromedriver-findby-speed-headless.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With Chrome in headless mode we’ve taken 300-400 milliseconds off our
no-extension tests; over a large test suite this would lead to a great
performance increase and the ability to run tests in parallel.&lt;/p&gt;
</description></item>
<item><title>Azure FTP 550 - Access is Denied</title><link>https://www.ashgrennan.com/post/azure-ftp-550/</link><guid>https://www.ashgrennan.com/post/azure-ftp-550/</guid><pubDate>Sat, 05 Aug 2017 00:00:00 +0000</pubDate><description>&lt;p&gt;So, I encountered this for the first time today: a colleague wanted
to quickly FTP some files up to an Azure Web App. However, no matter
what directory they tried, they always got the same error:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/azure-ftp-550/azure-550-ftp-error.PNG&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Initially I thought there must be a permission missing from the user
account; however, they had got the credentials from a .PublishSettings
file for the web app in question.&lt;/p&gt;
&lt;h3 id=&quot;solution&quot;&gt;Solution&lt;/h3&gt;
&lt;p&gt;Within the XML of the publish settings file, there’s a profile entry
for a &lt;strong&gt;[insert app name] - Readonly - FTP&lt;/strong&gt;. This contains
credentials for &lt;strong&gt;read only access&lt;/strong&gt;, an easy mistake to
make since, depending on your editor, the XML file might be one long
line. The correct FTP publish section will just have the word
&lt;strong&gt;FTP&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Hope that saves someone some time!&lt;/p&gt;
</description></item>
<item><title>HTML5 – Unknown JavaScript Web APIs</title><link>https://www.ashgrennan.com/post/html5-unknown-javascript-web-apis/</link><guid>https://www.ashgrennan.com/post/html5-unknown-javascript-web-apis/</guid><pubDate>Sat, 05 Aug 2017 00:00:00 +0000</pubDate><description>&lt;p&gt;There’s a lot of cool things that HTML5 can do, using local storage
for data saving/retrieval or acquiring a user’s geolocation for your
custom Google map. Well, it turns out there are many, many more
JavaScript APIs that I haven’t seen people mention; below are a few of
these.&lt;/p&gt;
&lt;p&gt;Before we go on, I should mention I’ll be focusing on APIs which
benefit mobile/tablet devices, since these are really cool and useful in
modern web applications.&lt;/p&gt;
&lt;h3 id=&quot;online-state&quot;&gt;Online State&lt;/h3&gt;
&lt;p&gt;This exposes network connection information to the web; quite simply,
we can now determine if a user has an Internet connection within our web
application. This is highly useful in cases where apps stream data in,
we can let the user know that there’s a connection issue rather than
simply returning nothing, creating a much better user experience.&lt;/p&gt;
&lt;p&gt;This API is being updated, things like grabbing a connection speed
and adjusting the app accordingly would be useful, for example degrading
image quality.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;offline&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; networkStatus)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;online&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; networkStatus)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;networkStatus&lt;/span&gt;(e) {&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(e)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;support&quot;&gt;Support&lt;/h4&gt;
&lt;p&gt;Browser support for this is excellent for recent browser versions,
with some of the smaller device-specific browsers such as Samsung
Internet supporting it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/html5-unknown-javascript-web-apis/online-status-web-api.png&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;device-orientation&quot;&gt;Device Orientation&lt;/h3&gt;
&lt;p&gt;From this we have an easy way to determine the orientation of a
device. This would be especially useful when dealing with media heavy
web apps and ensuring your content can use the new orientation real
estate efficiently. Also, combined with CSS we can rotate images in a 3D
space.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;deviceorientation&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; (e) &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;alpha:&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;alpha&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;beta:&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;beta&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;gamma:&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;gamma&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;})&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;support-1&quot;&gt;Support&lt;/h4&gt;
&lt;p&gt;Browser support for this isn’t great, partial support with known
issues. Fine for fun, non-essential features, but I wouldn’t rely on
it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/html5-unknown-javascript-web-apis/device-orientation-javascript-api.png&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;capture-ambient-light&quot;&gt;Capture Ambient Light&lt;/h3&gt;
&lt;p&gt;This is really, really cool. The metric returned from this is &lt;a
href=&quot;https://en.wikipedia.org/wiki/Lux&quot;&gt;lux&lt;/a&gt; which is used to
measure light. You can read about the sensor API more &lt;a
href=&quot;https://www.w3.org/TR/generic-sensor/&quot;&gt;here&lt;/a&gt;. A use case for
this would be switching to a darker or lighter theme depending on
available light, enabling the user to choose would certainly be a nice
feature and provide a better user experience.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre
class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;devicelight&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; (e) &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;${&lt;/span&gt;e&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt; lux`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;support-2&quot;&gt;Support&lt;/h4&gt;
&lt;p&gt;Browser support for this is bad: it has been available in Chrome
since version 62, however it is an experimental feature and has to be
enabled.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/html5-unknown-javascript-web-apis/ambient-light-api.png&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;battery-level&quot;&gt;Battery Level&lt;/h3&gt;
&lt;p&gt;We can now detect battery levels, which has the added benefit of
determining if a device is plugged into a power source. The &lt;a
href=&quot;https://w3c.github.io/battery/&quot;&gt;code for this&lt;/a&gt; is a little
different in that you’re using a promise. Since the W3C example has many
code samples, I’ll use one of these.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre
class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getBattery&lt;/span&gt;()&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;then&lt;/span&gt;(&lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt;(battery) {&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(battery&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;level&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  battery&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;levelchange&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt;() {&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;level&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  })&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;})&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;support-3&quot;&gt;Support&lt;/h4&gt;
&lt;p&gt;Browser support for this is pretty poor unless you’re specifically
targeting Chrome. Also, as Firefox has removed the feature, it doesn’t
look promising for the future.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.ashgrennan.com/post/html5-unknown-javascript-web-apis/battery-status-api.png&quot; /&gt;&lt;/p&gt;
</description></item>
<item><title>Writing in BEM using LESS</title><link>https://www.ashgrennan.com/post/writing-bem-using-less/</link><guid>https://www.ashgrennan.com/post/writing-bem-using-less/</guid><pubDate>Sat, 05 Aug 2017 00:00:00 +0000</pubDate><description>&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;This is something I found myself thinking about when using the Block
Element Module (BEM) methodology when coding with the LESS
preprocessor.&lt;/p&gt;
&lt;p&gt;I like this for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Less repeating code&lt;/li&gt;
&lt;li&gt;Ability to change the block name and auto cascade to the element
descendants&lt;/li&gt;
&lt;li&gt;Fewer lines and easier to read&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-code&quot;&gt;The Code&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;    block {  
      &amp;amp;__element {
        &amp;amp;--modifier-alpha {
           color: blue;
        }
        &amp;amp;--modifier--beta {
           color: green;
        }
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the compiled elements would look like this:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;    block__element--modifier-alpha {
      color: blue;
    }

    block__element--modifier-beta {
      color: green;
    }&lt;/code&gt;&lt;/pre&gt;
</description></item>
</channel></rss>
