<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ben Witt</title>
    <description>The latest articles on DEV Community by Ben Witt (@ben-witt).</description>
    <link>https://dev.to/ben-witt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1456445%2F4ae90e8b-6b3e-48e7-be98-2d43dacbd68d.jpeg</url>
      <title>DEV Community: Ben Witt</title>
      <link>https://dev.to/ben-witt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ben-witt"/>
    <language>en</language>
    <item>
      <title>Stop Loading Your Entire Instruction System Into Every Session</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Wed, 17 Jun 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/significantly-fewer-context-tokens-through-a-modular-instruction-architecture-2g70</link>
      <guid>https://dev.to/ben-witt/significantly-fewer-context-tokens-through-a-modular-instruction-architecture-2g70</guid>
      <description>&lt;p&gt;Most people talk about better prompts. Hardly anyone talks about what happens before every prompt: the instructions the assistant loads into the context before the actual work begins.&lt;/p&gt;

&lt;p&gt;Depending on the system, you pay for that in different ways: input tokens, latency, reduced available context, or simply more noise in the assistant's active instructions. Even if the financial cost is partly reduced through prompt caching, the cognitive cost remains: the assistant still has to operate inside a larger instruction environment.&lt;/p&gt;

&lt;p&gt;At some point, my setup had become one single, constantly growing instruction file. System structure, assistant personality, workflows, session rules, special cases: everything was in one file. And everything was loaded into the context on every interaction, no matter whether I was solving a complex task or just asking a quick question.&lt;/p&gt;

&lt;p&gt;That is roughly like starting every phone call by reading the entire employee handbook before getting to the actual topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Problem
&lt;/h2&gt;

&lt;p&gt;A monolithic instruction file has two costs that become unpleasant when combined:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The baseline gets expensive.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Most of the file is irrelevant to the concrete task. Still, it sits in the active context. Depending on the system, that means token cost, latency, less room for the real task, or all of them at once.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The signal-to-noise ratio drops.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The more rules and special cases you add, the more the currently relevant part gets diluted. More context does not automatically mean more competence.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both scale in the wrong direction: the more mature your setup becomes, the heavier and less precise it gets, as long as everything lives in one file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight
&lt;/h2&gt;

&lt;p&gt;Not all instructions are needed all the time.&lt;/p&gt;

&lt;p&gt;I always need the assistant's personality and basic operating principles. I only need the exact structure of my project system when I actually navigate through it. I only need the session-end rules at the end of a session, and never before that.&lt;/p&gt;

&lt;p&gt;A writing task does not need filesystem navigation rules.&lt;br&gt;&lt;br&gt;
A quick reasoning task does not need session-close workflows.&lt;br&gt;&lt;br&gt;
A debugging session does not need publishing guidelines.&lt;/p&gt;

&lt;p&gt;If that is true, it makes no sense to keep everything loaded permanently.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;I split the one large file into a lean entry point plus specialized modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.config/
├── instructions.md   -&amp;gt; compact entry point, always loaded
├── persona.md        -&amp;gt; personality, tone, behavior
├── structure.md      -&amp;gt; system structure, only relevant for navigation
└── workflows.md      -&amp;gt; session workflows, only relevant when needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main instruction file is now intentionally small. It contains the minimum that really has to be present in every session, plus clear references: which module is responsible for what, and when it should be loaded.&lt;/p&gt;

&lt;p&gt;The detail modules are not active by default. They are accessible, but they only become part of the context when the task requires them.&lt;/p&gt;

&lt;p&gt;That distinction matters. The full instruction set is not magically present for free. It is only available if the assistant knows that a module exists, recognizes that it is relevant, and loads it at the right moment.&lt;/p&gt;

&lt;p&gt;So modularization does not mean: same context, lower cost.&lt;/p&gt;

&lt;p&gt;It means: smaller baseline, with more responsibility placed on routing and loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Loading Works
&lt;/h2&gt;

&lt;p&gt;In my setup, the entry point acts as a router. It does not contain all detailed rules. It contains short loading rules such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If the task involves navigating the project system,
load structure.md before answering.

If the task involves ending or reviewing a session,
load workflows.md before making recommendations.

If the task is a quick standalone question,
do not load additional modules unless needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is simple, but it is also the fragile part of the system. If the entry point is vague, the assistant may fail to load the right module. If it is too broad, it loads too much again and the benefit disappears.&lt;/p&gt;

&lt;p&gt;The quality of the entry point determines the quality of the whole architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;In my setup, the baseline token load per session dropped by around &lt;strong&gt;60-80%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I measured this by comparing the files that were previously loaded unconditionally at session start with the files that are now loaded unconditionally. The important number is not the total size of all available instructions. It is the size of the always-loaded baseline.&lt;br&gt;
&lt;/p&gt;

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

Always loaded:
instructions.md
persona.md
structure.md
workflows.md

Baseline load:
~4,800 tokens

After modularization:

Always loaded:
instructions.md
persona.md

Baseline load:
~1,450 tokens

Reduction:
69.8%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full instruction set still exists, but it is no longer active by default. It becomes active only when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;The trick is not compressing individual instructions. The trick is separating &lt;strong&gt;baseline load&lt;/strong&gt; from &lt;strong&gt;on-demand load&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline load:&lt;/strong&gt; what is loaded in every session. This is where the savings matter most, because this cost is paid repeatedly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-demand load:&lt;/strong&gt; what is only relevant in specific situations. This can be large and detailed, as long as it is loaded only when it actually matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you are not optimizing the total size of your instructions. You are optimizing which part of them must always be present. And that is surprisingly little.&lt;/p&gt;

&lt;p&gt;Prompt caching can reduce the financial cost of repeated baseline instructions in some systems. But it does not remove the context-budget cost, the latency implications in every environment, or the signal-to-noise problem. A cached irrelevant instruction is still an irrelevant instruction in the active instruction set.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Costs
&lt;/h2&gt;

&lt;p&gt;This is not a free lunch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Indirection:&lt;/strong&gt;&lt;br&gt;
The assistant sometimes has to take an extra step to load the right module. That is slightly slower and creates the risk that the right module is not loaded.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Routing errors:&lt;/strong&gt;&lt;br&gt;
If the assistant does not recognize that a task requires a module, it may answer with incomplete instructions. This is the main operational risk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintenance:&lt;/strong&gt;&lt;br&gt;
More files mean more places that can drift apart. If the entry point promises something that no longer exists in a module, you have a silent consistency problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rule conflicts:&lt;/strong&gt;&lt;br&gt;
Modules can contradict each other or the entry point. You need a precedence rule: general instructions define the default, specialized modules override them only within their domain, and explicit user instructions still have to be handled according to the system's hierarchy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Onboarding:&lt;/strong&gt;&lt;br&gt;
An outsider first has to understand the loading logic before the system becomes readable. A single file is trivial to understand.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real trade-off is this:&lt;/p&gt;

&lt;p&gt;You reduce baseline cost, but you give up permanent availability. You move complexity from runtime context into structure, routing, and maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  When It Is Worth It
&lt;/h2&gt;

&lt;p&gt;It is worth it if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your instruction set is large and has grown over time&lt;/li&gt;
&lt;li&gt;you start sessions frequently and baseline cost is noticeable&lt;/li&gt;
&lt;li&gt;clearly separable situations exist, such as navigation, session-end handling, publishing, coding, or special workflows&lt;/li&gt;
&lt;li&gt;your environment allows the assistant to load additional instruction files reliably&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not worth it if your entire setup fits into a few hundred tokens anyway. In that case, modularization is premature optimization: you trade real simplicity for imagined efficiency.&lt;/p&gt;

&lt;p&gt;It is also not worth it if the assistant cannot reliably access the modules when needed. A small always-loaded file plus inaccessible detail files is not an architecture. It is missing context with extra steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The biggest lever for instruction cost is rarely a better prompt. It is the question of what you force the assistant to carry into every interaction, and what it should only load when needed.&lt;/p&gt;

&lt;p&gt;Separate baseline load from on-demand load. Keep the entry point small and turn it into a precise router. Leave the details where they only create cost when they are actually needed.&lt;/p&gt;

&lt;p&gt;In my case, that meant 60-80% less baseline load. But the important part is not just the savings. It is the trade-off: less permanent context, more deliberate loading.&lt;/p&gt;

&lt;p&gt;That is the architecture I actually want. Not less instruction, but less unnecessary instruction in the room at the wrong time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
      <category>performance</category>
    </item>
    <item>
      <title>The Most Dangerous Bias of Your AI Assistant Is That It Agrees With You</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Wed, 10 Jun 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/the-most-dangerous-bias-of-your-ai-assistant-is-that-it-agrees-with-you-4fhc</link>
      <guid>https://dev.to/ben-witt/the-most-dangerous-bias-of-your-ai-assistant-is-that-it-agrees-with-you-4fhc</guid>
      <description>&lt;p&gt;We talk a lot about hallucinations. But there is another failure mode we should take just as seriously: AI assistants are optimized to be helpful, polite, and cooperative. Over a longer session, that can quietly turn into agreeableness.&lt;/p&gt;

&lt;p&gt;In a system that is supposed to help you think, this is not a cosmetic problem. It is the core problem. An assistant that agrees with you simply because you are the one asking is worthless as a sparring partner. It confirms your bad ideas just as readily as your good ones.&lt;/p&gt;

&lt;p&gt;I call this &lt;strong&gt;sycophancy drift&lt;/strong&gt;, and I have added a reflective layer to my knowledge system to detect it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Sycophancy Drift Is
&lt;/h2&gt;

&lt;p&gt;At the beginning of a session, the assistant still pushes back. You make a suggestion, and it gives you three reasons against it. Good.&lt;/p&gt;

&lt;p&gt;Twenty messages later, the tone is different. You suggest something, and suddenly everything is “a good point,” “absolutely reasonable,” or “a strong idea.” Not because your ideas have necessarily become better, but because the conversation context has increasingly conditioned the assistant toward agreement.&lt;/p&gt;

&lt;p&gt;To be precise: the model is not learning in the training sense. Its weights are not changing during the session. But the transcript becomes part of the active context, and that context can reward a pattern. If agreement keeps the conversation moving, the assistant may drift toward more agreement.&lt;/p&gt;

&lt;p&gt;That is the drift: a gradual shift from honest evaluation to confirmation.&lt;/p&gt;

&lt;p&gt;This is not speculation. Anthropic’s own research (Perez et al. 2022; Sharma et al. 2023) shows that RLHF training systematically rewards agreement: both human raters and preference models prefer convincingly written sycophantic answers over correct ones a non-negligible fraction of the time. The disposition is built in at training time. A long conversation does not create it - it amplifies it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Do Not Notice It
&lt;/h2&gt;

&lt;p&gt;Because it feels good. That is the whole trick.&lt;/p&gt;

&lt;p&gt;Hallucinations stand out because they are wrong. Sycophancy does not stand out because it is pleasant. You feel confirmed, you move forward, the session feels productive, and nobody tells you that the quality of disagreement has quietly dropped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Message 5:
Assistant:
"I see three major risks."

Message 35:
Assistant:
"That's a very strong idea."

Drift warning:
Criticism frequency dropped from 4 objections
per proposal to 0.8 objections per proposal.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Schematic illustration; the actual reports are qualitative)&lt;/p&gt;

&lt;p&gt;This is exactly why the assistant cannot reliably correct this in real time by itself. It is inside the same conversational pressure. You need an outside view, or at least a retrospective one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea: A Reflective Layer at the End of the Session
&lt;/h2&gt;

&lt;p&gt;In my setup, the assistant does not rewrite its own rules automatically. Instead, I use a dedicated reflective layer after a session has ended.&lt;/p&gt;

&lt;p&gt;The process is intentionally simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At the end of a session, the analysis layer is explicitly triggered.&lt;/li&gt;
&lt;li&gt;It reads the transcript of the entire session.&lt;/li&gt;
&lt;li&gt;It compares the session against an explicit, versioned rule set.&lt;/li&gt;
&lt;li&gt;It writes a structured proposal with four sections:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New rules:&lt;/strong&gt; what this session produced as lessons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirmed rules:&lt;/strong&gt; what proved useful again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drift warnings:&lt;/strong&gt; where the assistant agreed too much or weakened its criticism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recommendation:&lt;/strong&gt; what should be added to the rule set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decisive point is this: it is only a proposal. It is written for human review. Not a single rule changes automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Layer Actually Flags
&lt;/h2&gt;

&lt;p&gt;The layer does not look for politeness. Politeness is not the problem.&lt;/p&gt;

&lt;p&gt;It looks for disappearing resistance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer objections over time&lt;/li&gt;
&lt;li&gt;softer risk language&lt;/li&gt;
&lt;li&gt;repeated validation phrases&lt;/li&gt;
&lt;li&gt;missing alternative paths&lt;/li&gt;
&lt;li&gt;ignored counterarguments&lt;/li&gt;
&lt;li&gt;praise replacing evaluation&lt;/li&gt;
&lt;li&gt;earlier rules being quietly weakened&lt;/li&gt;
&lt;li&gt;decisions accepted without checking trade-offs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to make the assistant negative. The goal is to preserve useful friction.&lt;/p&gt;

&lt;p&gt;A good thinking partner should not disagree for sport. But it should notice when the session has become too smooth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Design Decisions That Make the Difference
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Human in the loop, not out of caution, but out of logic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It would be tempting to let the analysis layer write its findings directly into the rule set. That would be the mistake. You would let a system that is prone to conversational drift rewrite its own anti-drift rules.&lt;/p&gt;

&lt;p&gt;Separating proposal from adoption is not a comfort feature. It is the safety mechanism that makes the whole concept meaningful.&lt;/p&gt;

&lt;p&gt;There is also a simple technical reality in my setup: the assistant has no autonomous background process and no unchecked memory update across sessions. Every change is conscious, triggered, and reviewable. This is not a limitation I want to bypass. It is the property that keeps the system honest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Importance and frequency are two different axes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every rule has two independent dimensions: an importance value and a frequency counter.&lt;/p&gt;

&lt;p&gt;A rule can occur rarely and still be critical. If I mix both dimensions together, the rare but important rule will eventually disappear because it looks statistically insignificant. That is why critical rules are protected from being archived, no matter how rarely they are triggered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A maximum of five new rules per session.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without a limit, the system overfits to a single conversation. One intense session could flood the rule set with special cases that may never matter again.&lt;/p&gt;

&lt;p&gt;The upper limit forces selection: what was truly a lesson, and what was just noise?&lt;/p&gt;

&lt;p&gt;Five is not a magic number. It is calibrated to my review capacity: a proposal I cannot review in five minutes is a proposal I will eventually stop reading and an unreviewed proposal pipeline is worse than none.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Part: The Recursion Problem
&lt;/h2&gt;

&lt;p&gt;The obvious weakness is this: I am using the same kind of model that drifted to detect its own drift. Can an assistant that tends to agree really expose its own agreement reliably?&lt;/p&gt;

&lt;p&gt;The honest answer is: not perfectly.&lt;/p&gt;

&lt;p&gt;A retrospective layer can itself become performative. It may learn to produce the kind of criticism the user expects, without actually identifying meaningful drift. It may become another agreeable ritual: “Here are your drift warnings,” even when the analysis is shallow.&lt;/p&gt;

&lt;p&gt;But the approach is still useful for two reasons.&lt;/p&gt;

&lt;p&gt;First, checking a finished transcript against an explicit checklist is a narrower task than generating helpful answers in the middle of a live conversation. Evaluation is not the same as participation.&lt;/p&gt;

&lt;p&gt;Second, the result is not trusted blindly. A human reviews it. The layer does not have to detect drift perfectly. It only has to make the pattern visible enough to question.&lt;/p&gt;

&lt;p&gt;Additional costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No real-time feedback.&lt;/strong&gt; Drift during the current session is detected afterwards, for the next run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review effort.&lt;/strong&gt; Reading proposals and deciding what to adopt is work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False confidence.&lt;/strong&gt; A reflective layer can look more objective than it really is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters. The layer is not a guarantee. It is a mirror.&lt;/p&gt;

&lt;p&gt;The industry attacks the same problem one level deeper: Anthropic’s persona-vector research identifies activation patterns associated with sycophancy and steers models away from them during training. That is the right fix at the model level. But it cannot reach the workflow level — no lab can review whether your assistant pushed back on your architecture decision yesterday. That layer is yours to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  When It Is Worth It
&lt;/h2&gt;

&lt;p&gt;If you use the assistant as a thinking tool, where honest disagreement is part of the value, then drift detection is not a nice extra. It is one of the conditions under which the tool works at all.&lt;/p&gt;

&lt;p&gt;If you only use the assistant for clearly defined execution tasks, where agreement does not matter much, you probably do not need this.&lt;/p&gt;

&lt;p&gt;But if the assistant helps shape your decisions, your architecture, your writing, your strategy, or your beliefs, then you should care about how much resistance disappears over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The labs are working on this at the training level, evals, steering, character training. What is largely missing is the workflow level: per-user, per-session drift detection that you control.&lt;/p&gt;

&lt;p&gt;The most pleasant bias may also be the most dangerous one: an assistant that agrees with you feels like a good assistant while it slowly stops being useful as a thinking partner.&lt;/p&gt;

&lt;p&gt;The solution is not simply a better model version. It is a layer that looks back after the work is done, names the drift, and leaves the decision with you.&lt;/p&gt;

&lt;p&gt;The assistant should not only help you move faster. It should also preserve the friction that keeps your thinking honesty.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part II will follow later&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Transitive Dependency – Challenges and Approaches in .NET</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Wed, 12 Feb 2025 09:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/transitive-dependency-challenges-and-approaches-in-net-20a6</link>
      <guid>https://dev.to/ben-witt/transitive-dependency-challenges-and-approaches-in-net-20a6</guid>
      <description>&lt;p&gt;Imagine you are developing a .NET application that runs perfectly at first. Suddenly, however, unexpected runtime errors occur—even though your main project does not explicitly include any additional packages. How can this phenomenon be explained? Often, the cause is a transitive dependency: a library such as Entity Framework Core is silently included in your project via another NuGet package. In complex application scenarios, this issue can lead to version conflicts or even hard-to-diagnose runtime errors like the &lt;code&gt;MissingMethodException&lt;/code&gt;. Without targeted management of these dependencies, the effort required for error resolution can become significant.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Basics and Problem Statement
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Transitive References vs. Transitive Dependencies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transitive References:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In a multi-layered solution structure, for example, Project A directly references Project B, which in turn uses Project C. In this way, Project A has an indirect (transitive) reference to Project C—even if it is not explicitly listed in the &lt;code&gt;.csproj&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transitive Dependencies:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
With external NuGet packages, the problem appears when they are brought in by a referenced package and do not show up in the main project’s &lt;code&gt;.csproj&lt;/code&gt; file. For instance, if Project A uses a package (e.g., &lt;code&gt;PackageX&lt;/code&gt;) that internally references EF Core in a specific version, then EF Core is included transitively—even though Project A never explicitly added it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Isn’t it remarkable how quickly such subtle dependency chains can emerge in large projects without being noticed?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 A Typical Scenario in .NET
&lt;/h3&gt;

&lt;p&gt;In many .NET applications, Entity Framework Core (EF Core) is used as the data access library. Even if the main project does not directly reference EF Core, it can be silently included as a transitive dependency via another utility or data access package. Initially, everything seems to work smoothly—EF Core provides a stable API. However, when updating the .NET framework or integrating new features, version conflicts may occur that eventually lead to hard-to-diagnose runtime errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Detailed Solution Approaches
&lt;/h2&gt;

&lt;p&gt;To prevent the unwanted propagation of packages, two main approaches are available: the targeted restriction of individual packages using &lt;code&gt;PrivateAssets="all"&lt;/code&gt; or the complete deactivation of transitive project references with &lt;code&gt;&amp;lt;DisableTransitiveProjectReferences&amp;gt;true&amp;lt;/DisableTransitiveProjectReferences&amp;gt;&lt;/code&gt;. Both approaches are critically examined below.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 &lt;code&gt;PrivateAssets="all"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;By adding the attribute &lt;code&gt;PrivateAssets="all"&lt;/code&gt; in the PackageReference, you prevent the respective library from being passed on to other projects in the dependency hierarchy. This way, the package—and its transitive dependencies—remain available only within the current project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Granularity:&lt;/strong&gt; You decide on a package-by-package basis whether a dependency should be inherited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoidance of Side Effects:&lt;/strong&gt; Problematic libraries are not automatically transferred to subordinate projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Increased Configuration Effort:&lt;/strong&gt; If another project needs the same library, it must be explicitly referenced again.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"SomeDataAccessPackage"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.2.3"&lt;/span&gt; &lt;span class="na"&gt;PrivateAssets=&lt;/span&gt;&lt;span class="s"&gt;"all"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Could it be that in complex environments the administrative effort for such package-specific configuration outweighs the benefits?&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 DisableTransitiveProjectReferences
&lt;/h3&gt;

&lt;p&gt;With the global setting true in the .csproj file, you prevent the automatic propagation of all transitive project references. Every project must then explicitly list all the required packages.&lt;/p&gt;

&lt;p&gt;Advantages:&lt;br&gt;
    • Increased Transparency: All dependencies are explicitly visible and controllable.&lt;br&gt;
    • Prevention of Unintended Inclusions: No “hidden” libraries are automatically added to the project.&lt;/p&gt;

&lt;p&gt;Disadvantages:&lt;br&gt;
    • Higher Maintenance Effort: Explicitly listing all dependencies can create a significant administrative overhead, especially in larger projects.&lt;br&gt;
    • Risk of Omissions: Essential dependencies might accidentally be overlooked, leading to compile-time or runtime errors.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;lt;PropertyGroup&amp;gt;
  &amp;lt;DisableTransitiveProjectReferences&amp;gt;true&amp;lt;/DisableTransitiveProjectReferences&amp;gt;
&amp;lt;/PropertyGroup&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Given the additional effort, is it really practical to explicitly declare all dependencies?&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Comparison of Both Approaches
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;PrivateAssets="all"&lt;/th&gt;
&lt;th&gt;DisableTransitiveProjectReferences&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Granularity&lt;/td&gt;
&lt;td&gt;Package-specific control&lt;/td&gt;
&lt;td&gt;Global deactivation – all dependencies must be explicitly referenced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance Effort&lt;/td&gt;
&lt;td&gt;Requires individual adjustments per project&lt;/td&gt;
&lt;td&gt;Higher maintenance effort due to separate management of all references&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clarity&lt;/td&gt;
&lt;td&gt;Can lead to opaque dependency chains in complex structures&lt;/td&gt;
&lt;td&gt;Clear separation, but increases the risk of overlooking essential packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&gt;High flexibility in isolated control of individual packages&lt;/td&gt;
&lt;td&gt;Strict control, but less adaptability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Practical Example: Abstraction and Interface Encapsulation
&lt;/h2&gt;

&lt;p&gt;Consider a specific application: a .NET Standard library named Common encapsulates various helper methods and tools and internally includes a data access package that brings EF Core as a transitive dependency. In this case, it is advisable not to expose EF Core-specific classes in the public API; instead, these should be abstracted through interfaces.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
// In Common
public interface IDataService
{
    IEnumerable&amp;lt;string&amp;gt; GetData();
}

internal class EfDataService : IDataService
{
    public IEnumerable&amp;lt;string&amp;gt; GetData()
    {
        // Internal use of EF Core
        return new List&amp;lt;string&amp;gt; { "Data from EF Core" };
    }
}

public static class DataServiceFactory
{
    public static IDataService CreateService()
    {
        return new EfDataService();
    }
}

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

&lt;/div&gt;



&lt;p&gt;Configuration in Common.csproj:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;lt;ItemGroup&amp;gt;
  &amp;lt;PackageReference Include="SomeDataAccessPackage" Version="1.2.3" PrivateAssets="all" /&amp;gt;
&amp;lt;/ItemGroup&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;This approach ensures that EF Core remains exclusively available internally—the main application only interacts with the interface without needing to know the underlying implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Advanced Strategies and Alternative Approaches
&lt;/h2&gt;

&lt;p&gt;In addition to the configuration options discussed above, other strategies can help minimize version conflicts and the issues of transitive dependencies:&lt;br&gt;
    • Binding Redirects:&lt;br&gt;
Especially in older .NET Framework applications, binding redirects can help resolve conflicts between different versions of the same library.&lt;br&gt;
    • Modular Architectures:&lt;br&gt;
Separating the application into clearly defined modules allows for targeted isolation of dependencies. This makes it easier to control unwanted side effects.&lt;/p&gt;

&lt;p&gt;Should we not always question whether a focus solely on .csproj configurations in highly complex systems is truly the optimal solution?&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Summary and Outlook
&lt;/h2&gt;

&lt;p&gt;Transitive dependencies represent a serious issue in complex .NET projects. Seemingly insignificant libraries like EF Core can—when included via other NuGet packages—lead to significant version conflicts and runtime errors. Consistent separation of dependencies and the targeted use of measures such as PrivateAssets="all" or  offer practical solutions. It is essential to weigh whether the administrative effort is justified by the benefits and how long-term maintainability can be ensured.&lt;/p&gt;

&lt;p&gt;By using abstractions, explicit references, and additional measures such as binding redirects, developers can not only create a more stable codebase but also reduce ongoing maintenance efforts. Regularly reviewing dependency hierarchies using tools like dotnet list package --include-transitive or NDepend is indispensable.&lt;/p&gt;

&lt;p&gt;Is it not ultimately the task of every developer to continually question whether the current architecture still meets the growing demands of modern applications?&lt;/p&gt;

&lt;p&gt;I think so….!&lt;/p&gt;

</description>
      <category>development</category>
      <category>coding</category>
      <category>csharp</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Efficient Debugging and Precise Logging in C#: Using Caller Attributes</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Wed, 29 Jan 2025 17:01:53 +0000</pubDate>
      <link>https://dev.to/ben-witt/efficient-debugging-and-precise-logging-in-c-using-caller-attributes-3m5f</link>
      <guid>https://dev.to/ben-witt/efficient-debugging-and-precise-logging-in-c-using-caller-attributes-3m5f</guid>
      <description>&lt;p&gt;How often do you wish, while debugging a complex application, to instantly see where in the code an error was triggered? Often, you are left with basic error messages that lack valuable context or exact code lines. This is where the C# attributes [CallerMemberName], [CallerFilePath], and [CallerLineNumber] come into play.&lt;/p&gt;

&lt;p&gt;These attributes automatically provide information about the calling method’s name, the physical file path, and the line number in the source code when a method is called. This enables precise error tracking and logging, which is useful both during debugging and in production logging. Especially when troubleshooting large codebases, they prove to be indispensable tools for quick and targeted analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basics of the Attributes
&lt;/h2&gt;

&lt;p&gt;The attributes [CallerMemberName], [CallerFilePath], and [CallerLineNumber] are applied directly to method parameters. The compiler automatically replaces the arguments with the corresponding values from the call context without any extra effort from the developer:&lt;br&gt;
    • [CallerMemberName] provides the name of the calling method.&lt;br&gt;
    • [CallerFilePath] gives the full path to the file where the method call is located.&lt;br&gt;
    • [CallerLineNumber] provides the line number of the call within that file.&lt;/p&gt;

&lt;p&gt;The syntax is straightforward. A classic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public void LogInfo(
    string message, 
    [CallerMemberName] string memberName = "",
    [CallerFilePath] string filePath = "", 
    [CallerLineNumber] int lineNumber = 0)
{
    Console.WriteLine($"[{memberName}] {message} (File: {filePath}, Line: {lineNumber})");
}

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

&lt;/div&gt;



&lt;p&gt;Here, the parameters must have default values (e.g., = "" or = 0) so that the compiler can insert the values automatically. When you call the method without specifying these optional parameters, they are substituted automatically.&lt;/p&gt;

&lt;p&gt;Example Method for Capturing Call Stack Information&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
public void ProcessData([CallerMemberName] string caller = "",
                        [CallerFilePath] string path = "",
                        [CallerLineNumber] int line = 0)
{
    Console.WriteLine($"Called by: {caller}, File: {path}, Line: {line}");
    // Further data processing code...
}

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

&lt;/div&gt;



&lt;p&gt;In this simple scenario, you can already gain initial insights into the call context and source code position. This meta-information is extremely valuable both for debugging and for later evaluations in operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Examples for Practical Applications
&lt;/h2&gt;

&lt;p&gt;a) &lt;strong&gt;Simple Examples&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine you are running a library or an API to manage borrowing processes. Everywhere important actions take place—such as registering a book, registering new users, or handling requests—you want to create log entries. You could use a helper class for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static class Logger
{
    public static void LogOperation(
        string operation, 
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string filePath = "",
        [CallerLineNumber] int lineNumber = 0)
    {
        // For example, log to a file or a database
        Console.WriteLine($"Operation: {operation} in {memberName}, File: {filePath}, Line: {lineNumber}");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time you call Logger.LogOperation("BookBorrowed"), the log provides much more information than if you had to add it manually.&lt;/p&gt;

&lt;p&gt;b) &lt;strong&gt;More Complex Example with a Base Class&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In larger projects, it is advisable to centralize logging and error handling routines in an abstract base class. The following example shows an abstract BaseRepository that catches errors and automatically uses the attributes for meaningful error messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
public abstract class BaseRepository
{
    protected void LogError(Exception ex, 
                            [CallerMemberName] string memberName = "",
                            [CallerFilePath] string filePath = "", 
                            [CallerLineNumber] int lineNumber = 0)
    {
        Console.WriteLine($"Error in {memberName} at {filePath} (Line {lineNumber}): {ex.Message}");
    }
}

public class BookRepository : BaseRepository
{
    public void AddBook(Book book)
    {
        try
        {
            // Simulated database operation
            throw new InvalidOperationException("Database error while adding a book.");
        }
        catch (Exception ex)
        {
            LogError(ex);
            throw;  // Re-throw the error to continue the stack
        }
    }
}

public class LibraryService
{
    private readonly BookRepository _repository = new BookRepository();

    public void Execute()
    {
        try
        {
            _repository.AddBook(new Book { Title = "C# Deep Dive" });
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error in the service layer: " + ex.Message);
            throw;
        }
    }
}

// Application
var service = new LibraryService();
service.Execute();

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

&lt;/div&gt;



&lt;p&gt;This approach allows complete tracking of the call stack. When an error is triggered in BookRepository, the LogError method precisely logs the context: the member name (AddBook), the file path, and the line number. At the same time, the error is re-thrown so that the higher layer (LibraryService) can perform further actions, such as separate logging or sending an alert.&lt;/p&gt;

&lt;p&gt;c) &lt;strong&gt;Tracking a Complete Stack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In complex multi-layered architectures—consisting of service layers, repository layers, and database connections—it is crucial to trace the source of an error without gaps. The above example demonstrates this flow:&lt;br&gt;
    1.  The service layer calls AddBook in BookRepository.&lt;br&gt;
    2.  The repository layer throws an exception due to a database error, logs details using the caller attributes, and re-throws the error.&lt;br&gt;
    3.  The service layer catches the error and can handle it accordingly.&lt;/p&gt;

&lt;p&gt;With this technique, precise logging of each relevant layer is achieved, ensuring that no information about the actual origin is lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Application with a Library Management System
&lt;/h2&gt;

&lt;p&gt;In library software, where daily borrowings, reservations, returns, or new registrations are logged, the caller attributes are used optimally:&lt;br&gt;
    1.  &lt;strong&gt;Logging API Errors&lt;/strong&gt;: If an HTTP request fails, the log immediately provides information about the error location in the code (method, file, line).&lt;br&gt;
    2.  &lt;strong&gt;Logging Database Operations&lt;/strong&gt;: In dynamic environments, faulty SQL queries or transaction conflicts can quickly lead to confusing errors. Thanks to the caller attributes, it is possible to determine exactly where these conflicts were triggered, whether in the repository or service layer.&lt;/p&gt;

&lt;p&gt;Such practical insights into the system not only shorten debugging times but also improve the overall maintainability of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and Best Practices
&lt;/h2&gt;

&lt;p&gt;Although logging file and line details is very helpful during development, it is important to consider potential drawbacks:&lt;br&gt;
    • &lt;strong&gt;Performance Considerations&lt;/strong&gt;: With frequent log calls, excessive capturing of caller information can quickly bloat logs and impact application performance. Therefore, in production environments, it should be carefully considered where detailed logs are truly necessary.&lt;br&gt;
    • &lt;strong&gt;Security Aspects&lt;/strong&gt;: Revealing file paths may be undesirable in some cases, such as in security-critical applications or externally shared logs.&lt;br&gt;
    • &lt;strong&gt;Reusability&lt;/strong&gt;: To keep maintenance effort low, it is recommended to encapsulate the attributes in base classes or utility methods, as shown in the example (BaseRepository). This avoids redundant code and ensures consistent error messages across the entire project.&lt;/p&gt;

&lt;p&gt;With a sensible logging concept and thoughtful filtering mechanisms, the level of detail can be flexibly controlled depending on the environment (development, staging, production).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The attributes [CallerMemberName], [CallerFilePath], and [CallerLineNumber] offer significant benefits for debugging, logging, and error tracing in C# projects. Their versatile applications—from simple logs in the development environment to complex error tracking in multi-layered architectures—make them essential tools in modern application development.&lt;/p&gt;

&lt;p&gt;So why not start implementing more precise error logging and more efficient debugging in your own projects right away? The implementation is straightforward, while the benefits are enormous—especially when errors in production systems need to be quickly isolated and resolved. With targeted use, thoughtful structuring, and a bit of pragmatism, a new level of transparency in error analysis is achieved, significantly easing the developer’s daily work.&lt;/p&gt;

</description>
      <category>development</category>
      <category>microsoft</category>
      <category>csharp</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Critical Look at Cancellation Management in .NET Applications</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Wed, 29 Jan 2025 17:01:36 +0000</pubDate>
      <link>https://dev.to/ben-witt/a-critical-look-at-cancellation-management-in-net-applications-1kfg</link>
      <guid>https://dev.to/ben-witt/a-critical-look-at-cancellation-management-in-net-applications-1kfg</guid>
      <description>&lt;p&gt;The CancellationToken and the related types provided in .NET offer a central and effective foundation for carefully canceling tasks, threads, or asynchronous processes. However, the question arises whether this concept truly provides sufficient flexibility and stability in all application scenarios. But how often is this feature actually used in practice, and what risks come with improper handling? This article explains how the CancellationToken works and offers concrete recommendations for its efficient use. Additionally, potential pitfalls are highlighted and critically examined.&lt;/p&gt;

&lt;p&gt;Definition and Purpose of CancellationToken&lt;/p&gt;

&lt;p&gt;The CancellationToken is a structure from the Task Parallel Library (TPL) that is used to signal an ongoing operation that a cancellation request has been made. In a software architecture increasingly based on asynchronous and parallel processes, the CancellationToken plays a central role: it allows computationally intensive processes or long-running tasks to be terminated early, conserving resources and ensuring an improved user experience.&lt;/p&gt;

&lt;p&gt;Why is this construct indispensable? In a modern .NET application, numerous operations can run in parallel or asynchronously. Without coordinated cancellation and resource release, there is a risk that certain tasks continue running and block system resources, even though they are no longer needed.&lt;/p&gt;

&lt;p&gt;How It Works and Basic Concepts&lt;/p&gt;

&lt;p&gt;Cancellation control is managed by the CancellationTokenSource class. It acts as the central control unit and provides methods such as Cancel(), which sends a cancellation signal to all linked CancellationTokens.&lt;/p&gt;

&lt;p&gt;Resource Management with IDisposable&lt;/p&gt;

&lt;p&gt;A frequently underestimated aspect is that CancellationTokenSource implements the IDisposable interface. If this is not taken into account, it can easily lead to resource leaks—especially in scenarios where a large number of CancellationTokenSource objects are created in parallel. Why should one neglect implementing clean resource management?&lt;/p&gt;

&lt;p&gt;The recommendation is to always use the using keyword to enforce automatic resource release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using (var cts = new CancellationTokenSource())
{
    // Code to execute the operation
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failing to call the Dispose() method can leave unmanaged resources in memory, ultimately affecting the application’s stability.&lt;/p&gt;

&lt;p&gt;Methods to Cancel an Operation&lt;/p&gt;

&lt;p&gt;There are several approaches to sending a cancellation request to an ongoing operation:&lt;br&gt;
    1.  Synchronous Cancellation&lt;br&gt;
The cancellation request is triggered immediately. This means the current method actively checks the CancellationToken and reacts right away.&lt;br&gt;
    2.  Asynchronous Cancellation&lt;br&gt;
The request is performed in the background without immediately blocking the calling code’s flow.&lt;br&gt;
    3.  Delayed Cancellation&lt;br&gt;
A predefined time span is used after which the operation is automatically terminated. This is especially helpful when a user does not want to wait further, such as in network or I/O-intensive tasks.&lt;/p&gt;

&lt;p&gt;A concrete example of this is setting a timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this time span elapses, a cancellation request is triggered, ensuring that an operation does not run beyond the set limits. But is it always sensible to set a rigid timeout without being able to flexibly respond to external events?&lt;/p&gt;

&lt;p&gt;Monitoring Cancellation Requests&lt;/p&gt;

&lt;p&gt;Polling&lt;/p&gt;

&lt;p&gt;A common practice is to regularly check the CancellationToken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while (!token.IsCancellationRequested)
{
    // Continue the operation
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When such loops are implemented in computationally intensive areas, the frequency of checks can significantly affect performance. The question arises: How often should the check be performed to recognize a cancellation promptly without excessively burdening execution time?&lt;/p&gt;

&lt;p&gt;Alternatively, the ThrowIfCancellationRequested() method can be used, which throws an OperationCanceledException if a cancellation request has been made:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token.ThrowIfCancellationRequested();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is usually cleaner but can also lead to unexpected side effects if the exception is not caught appropriately.&lt;/p&gt;

&lt;p&gt;Callback Registration&lt;/p&gt;

&lt;p&gt;In scenarios where regular polling is impractical or resource-intensive, a callback can be registered to execute automatically when a cancellation request arrives. For example, if a user is downloading a large file and cancels the process, not only should the download threads stop, but any temporary files created should also be deleted to free up space and avoid inconsistencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token.Register(() =&amp;gt; 
{
    Console.WriteLine("Operation canceled.");
    // Additional cleanup, e.g., deleting temporary files
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method has the advantage of calling cancellation actions specifically without the ongoing operation having to constantly check for the cancellation signal. However, callback registration can lead to messy code flow in complex scenarios when multiple cancellation cases and cleanup actions need to be considered.&lt;/p&gt;

&lt;p&gt;Linking Multiple CancellationTokens&lt;/p&gt;

&lt;p&gt;What happens when an application needs to meet several potential cancellation conditions at the same time? For example, an operation might need to respond to both a user event (like closing a window) and a timeout simultaneously. The CancellationTokenSource class provides the CreateLinkedTokenSource method for this purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token1, token2);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The created instance combines multiple CancellationTokens into a new token. Once any of the involved tokens is canceled, the combined token is also triggered. This simplifies the implementation of complex cancellation logic and improves readability. However, caution is necessary: incorrect linking can cause operations to be canceled too early or not at all if the relationships between tokens are not clearly defined.&lt;/p&gt;

&lt;p&gt;Best Practices for Handling CancellationToken&lt;/p&gt;

&lt;p&gt;To ensure smooth cancellation management, the following points should be considered:&lt;br&gt;
    1.  Respect the Passed Token&lt;br&gt;
Avoid ignoring the CancellationToken. In every relevant method, actively check it or use an appropriate mechanism (e.g., callback).&lt;br&gt;
    2.  Properly Release Resources&lt;br&gt;
Always call the Dispose() method on CancellationTokenSource, preferably using the using block to prevent memory leaks.&lt;br&gt;
    3.  Avoid Unnecessary Sources&lt;br&gt;
Only create a new CancellationTokenSource if passing an existing CancellationToken is insufficient. Otherwise, you risk an unclear codebase and unnecessary resource consumption.&lt;br&gt;
    4.  Proper Error Handling&lt;br&gt;
Catch the OperationCanceledException and handle it meaningfully. Silently swallowing the exception can lead to hard-to-trace error patterns, as cancellation operations behave “invisibly.”&lt;/p&gt;

&lt;p&gt;Additional Aspects and Critical Consideration&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Cases for Timeouts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the use of a timeout has already been mentioned, in a highly connected world, variable external factors such as fluctuating network bandwidths or different devices can quickly render a rigid timeout obsolete. Wouldn’t it make sense to take a closer look at adaptive timeout management?&lt;/p&gt;

&lt;p&gt;Consider scenarios where the system responds to environmental parameters and dynamically adjusts timeouts. For example, a microservice that reduces wait times for certain requests under high load or grants more generous timeouts with a good connection could significantly enhance user satisfaction. Load balancers and reverse proxies also play an important role here, as they can capture both the context and the load of the respective services.&lt;/p&gt;

&lt;p&gt;In short, a purely time-based approach without context can lead to misjudgments and, at worst, prematurely cancel important operations. In the age of cloud computing and highly variable environments, it’s worthwhile to consider context-dependent timeout management with circuit breakers or retry mechanisms.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Depth of Example Implementations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Although the article uses concise code snippets (such as the using statement), a more in-depth presentation of more complex application examples would be helpful. Especially in microservice architectures or when accessing databases, the layered cancellation logic often becomes apparent in practice.&lt;/p&gt;

&lt;p&gt;A realistic example could cover the following aspects:&lt;br&gt;
    • Multiple CancellationTokens originating from different sources (e.g., user inputs, timeouts, overload signals).&lt;br&gt;
    • A database-heavy operation accessing an external resource that, upon interruption, must not only reset local but also database transactions.&lt;br&gt;
    • Logical separation of cancellation handling at the service level and repository level to ensure a clear separation of concerns and minimize side effects.&lt;/p&gt;

&lt;p&gt;Such comprehensive implementations would allow developers to recognize and avoid common pitfalls—such as forgetting to call Dispose() or improperly catching OperationCanceledException—in a realistic context.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;More Comprehensive Error Handling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The article already emphasizes the importance of catching OperationCanceledException. But how specifically should the error situation be handled in complex systems where multiple components respond to the same token?&lt;/p&gt;

&lt;p&gt;A possible scenario could look like this: A user-initiated cancellation request is recognized, but a downstream sub-component does not respond in time or blocks while requesting resources. Partial failures occur, which might only appear late in the logs and are difficult to attribute to a specific cause. In such cases, structured exception handling is needed, considering the following points:&lt;br&gt;
    • Targeted Logging: Every cancellation request should appear in the logs, ideally with context information (e.g., which sub-component responded, which actions were interrupted).&lt;br&gt;
    • Separation of Regular Exceptions and Cancellation Cases: An OperationCanceledException does not indicate a classic error state but a deliberate cancellation. However, the interplay of multiple CancellationTokens can create unforeseen side effects, making a consistent and well-documented error strategy essential.&lt;br&gt;
    • Fallback Mechanisms: In safety-critical or highly available environments, a cancellation should not lead to an unstable overall system but support orderly fallback procedures (e.g., graceful shutdown).&lt;/p&gt;

&lt;p&gt;Especially in distributed architectures where multiple services and applications interact, a well-documented error handling and logging process is indispensable to make cancellation processes both traceable and controlled.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;The CancellationToken is a central component in the development of modern, reactive, and resource-efficient .NET applications. However, its correct use requires a certain level of attention: underestimating the importance of clean resource management or ignoring cancellation signals in the code can lead to errors and performance problems almost by default.&lt;/p&gt;

&lt;p&gt;Is it really acceptable to forgo this central cancellation mechanism or implement it only half-heartedly? Those who value scalability and user satisfaction will clearly answer no. Consistently following the described best practices leads to robust and controlled cancellation management, enhances application performance, and improves the user experience equally. At the same time, a critical look at advanced concepts such as adaptive timeout management, comprehensive implementation examples, and structured error handling is recommended to make the use of CancellationToken even more effective and secure.&lt;/p&gt;

</description>
      <category>development</category>
      <category>csharp</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Asynchronous HTTP Requests – Explained Simply</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Wed, 29 Jan 2025 17:01:17 +0000</pubDate>
      <link>https://dev.to/ben-witt/asynchronous-http-requests-explained-simply-2jkh</link>
      <guid>https://dev.to/ben-witt/asynchronous-http-requests-explained-simply-2jkh</guid>
      <description>&lt;p&gt;Imagine a waiter taking your order and waiting in the kitchen until your food is ready. During this time, the waiter cannot serve other guests. This is how a synchronous HTTP request works – it blocks the process.&lt;/p&gt;

&lt;p&gt;An asynchronous HTTP request, on the other hand, allows the waiter to serve other guests while your food is being prepared. Once your dish is ready, the waiter brings it to you without others being delayed. Sounds efficient, right?&lt;/p&gt;

&lt;p&gt;Why Are Asynchronous Requests So Important?&lt;/p&gt;

&lt;p&gt;In daily life, we encounter asynchronous HTTP requests all the time, for example:&lt;br&gt;
    • WhatsApp: Messages are loaded while you’re using other functions.&lt;br&gt;
    • Instagram: Images and videos are loaded in the background.&lt;br&gt;
    • Netflix: The next episode is preloaded while you’re still watching.&lt;br&gt;
    • Online Shopping: Products are filtered and sorted in the background.&lt;/p&gt;

&lt;p&gt;What Does Idempotence Mean in This Context?&lt;/p&gt;

&lt;p&gt;Idempotence is an essential concept in web development, particularly for HTTP requests. It describes the property of an operation that delivers the same result no matter how many times it is performed. This principle plays an important role in the reliability and repeatability of requests – especially with asynchronous HTTP requests, as they might be sent multiple times due to network failures or timeouts.&lt;/p&gt;

&lt;p&gt;Examples of idempotent HTTP methods:&lt;br&gt;
    • GET: Repeatedly fetching the same resource always returns the same data (unless the resource changes).&lt;br&gt;
    • PUT: Overwriting a resource with the same data does not change the final state.&lt;br&gt;
    • DELETE: Deleting a resource has no effect if the resource has already been removed.&lt;/p&gt;

&lt;p&gt;In contrast, POST is not idempotent because it often creates new resources or changes states, which can lead to inconsistent behavior when executed multiple times.&lt;/p&gt;

&lt;p&gt;A Simple Introduction in C#&lt;/p&gt;

&lt;p&gt;Here is an example of fetching weather data in C#, which also considers idempotence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class WetterService
{
    private readonly HttpClient _httpClient;

    public WetterService()
    {
        _httpClient = new HttpClient();
        _httpClient.BaseAddress = new Uri("https://api.wetter.de/");
    }

    public async Task&amp;lt;WetterDaten&amp;gt; HoleWetterAsync(string stadt)
    {
        try
        {
            var antwort = await _httpClient.GetAsync($"wetter/{stadt}");

            if (!antwort.IsSuccessStatusCode)
            {
                return new WetterDaten { Fehler = "Ups! Da ist etwas schiefgelaufen." };
            }

            var json = await antwort.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize&amp;lt;WetterDaten&amp;gt;(json);
        }
        catch (Exception)
        {
            return new WetterDaten { Fehler = "Sorry, wir konnten das Wetter nicht abrufen." };
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: The use of the HTTP-GET method ensures idempotence. Even with multiple requests, the server’s state remains unchanged.&lt;/p&gt;

&lt;p&gt;Practical Example: Online Shop&lt;/p&gt;

&lt;p&gt;A typical controller processing asynchronous and idempotent requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ApiController]
[Route("api/produkte")]
public class ProduktController : ControllerBase
{
    private readonly IProduktService _produktService;

    public ProduktController(IProduktService produktService)
    {
        _produktService = produktService;
    }

    [HttpGet]
    public async Task&amp;lt;ActionResult&amp;lt;List&amp;lt;Produkt&amp;gt;&amp;gt;&amp;gt; HoleAlleProdukte()
    {
        var produkte = await _produktService.HoleAlleProdukteAsync();
        return Ok(produkte);
    }

    [HttpGet("{id}")]
    public async Task&amp;lt;ActionResult&amp;lt;Produkt&amp;gt;&amp;gt; HoleProdukt(int id)
    {
        var produkt = await _produktService.HoleProduktAsync(id);

        if (produkt == null)
        {
            return NotFound("Produkt nicht gefunden");
        }

        return Ok(produkt);
    }

    [HttpDelete("{id}")]
    public async Task&amp;lt;IActionResult&amp;gt; LoescheProdukt(int id)
    {
        var erfolgreich = await _produktService.LoescheProduktAsync(id);

        if (!erfolgreich)
        {
            return NotFound("Produkt nicht gefunden oder bereits gelöscht.");
        }

        return NoContent();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Idempotence in detail: Both GET and DELETE are idempotent here. Even with multiple executions, the results remain consistent, enhancing the system’s stability.&lt;/p&gt;

&lt;p&gt;Benefits of Asynchronous and Idempotent HTTP Requests&lt;br&gt;
    1.  Better User Experience: The app remains responsive and does not freeze.&lt;br&gt;
    2.  More Efficient Resource Utilization: The server can handle other tasks during wait times.&lt;br&gt;
    3.  Increased Reliability: Repeated requests do not lead to inconsistent behavior thanks to idempotence.&lt;br&gt;
    4.  Scalability: Asynchronous processing reduces bottlenecks, especially with parallel requests.&lt;/p&gt;

&lt;p&gt;Advanced Use Case: Parallel Loading&lt;/p&gt;

&lt;p&gt;Sometimes it is necessary to load multiple resources simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class DashboardService
{
    private readonly IBenutzerService _benutzerService;
    private readonly IBestellungService _bestellungService;

    public async Task&amp;lt;DashboardDaten&amp;gt; HoleDashboardDatenAsync()
    {
        var benutzerTask = _benutzerService.HoleStatistikenAsync();
        var bestellungenTask = _bestellungService.HoleStatistikenAsync();

        await Task.WhenAll(benutzerTask, bestellungenTask);

        return new DashboardDaten
        {
            BenutzerStats = await benutzerTask,
            BestellungStats = await bestellungenTask
        };
    }
}

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

&lt;/div&gt;



&lt;p&gt;Common Pitfalls and Solutions&lt;br&gt;
    1.  Forgotten await: Without await, the code remains blocking and loses its asynchronous behavior.&lt;br&gt;
    2.  Neglecting Error Handling: Network issues are common, so plan for appropriate error handling.&lt;br&gt;
    3.  Not Setting Timeouts: Avoid endless wait times by implementing timeout rules.&lt;br&gt;
    4.  Ignoring Idempotence: Non-idempotent requests can lead to hard-to-trace errors.&lt;br&gt;
    5.  Lack of Tests: Asynchronous code is prone to subtle bugs and should be thoroughly tested.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Asynchronous HTTP requests are the foundation of modern, responsive applications. By adhering to idempotence principles, repeated requests do not produce unexpected results. Together, they not only enhance the user experience but also create robust and scalable systems. However, their proper implementation requires careful planning and execution.&lt;/p&gt;

</description>
      <category>devto</category>
      <category>developer</category>
      <category>opensource</category>
      <category>coding</category>
    </item>
    <item>
      <title>WPF Application with Plugin Architecture</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Tue, 15 Oct 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/wpf-application-with-plugin-architecture-113h</link>
      <guid>https://dev.to/ben-witt/wpf-application-with-plugin-architecture-113h</guid>
      <description>&lt;p&gt;In modern software architectures, plug-in systems offer a flexible and modular way of extending software without changing the core of the application. A specific WPF project will be used to show how a plug-in architecture can be implemented to create loosely coupled and extensible components. The focus is on the interaction between the main application, plug-ins and the use of a central event aggregator for communication between the components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview over the structure
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mcdb534tl5d7pezk5m8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mcdb534tl5d7pezk5m8.png" alt="Image description" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The plug-in interface
&lt;/h2&gt;

&lt;p&gt;Central to any plug-in system is the definition of a clear interface that all plug-ins must implement. This creates consistency and ensures that the main application and the plug-ins can interact seamlessly with each other. In our example, this is achieved through the IPlugin interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace MyPluginInterface
{
  public interface IPlugin
  {
    string Name { get; }
    IEnumerable&amp;lt;Type&amp;gt; ViewTypes { get; } 
    IEnumerable&amp;lt;Type&amp;gt; ViewModelTypes { get; }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This interface specifies that each plug-in provides a name, a list of views (ViewTypes) and the associated ViewModels (ViewModelTypes). This ensures that all plug-ins can be uniformly integrated into the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation of a&amp;nbsp;plug-in
&lt;/h2&gt;

&lt;p&gt;A typical plug-in in this system implements the IPlugin interface. In the following example, the plug-in provides two views and the corresponding ViewModels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using MyFirstPlugin.ViewModels;
using MyFirstPlugin.Views;
using MyPluginInterface;

namespace MyFirstPlugin
{
  public class MyFirstPlugin : IPlugin
  {
    public string Name =&amp;gt; "My First Plugin with Multiple Views";

    public IEnumerable&amp;lt;Type&amp;gt; ViewTypes =&amp;gt; new List&amp;lt;Type&amp;gt;
    {
        typeof(MyPluginView),
        typeof(MyPlugin1View)
    };

    public IEnumerable&amp;lt;Type&amp;gt; ViewModelTypes =&amp;gt; new List&amp;lt;Type&amp;gt;
    {
        typeof(MyPluginViewModel),
        typeof(MyPlugin1ViewModel)
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This plug-in defines two views (MyPluginView, MyPlugin1View) and their associated ViewModels (MyPluginViewModel, MyPlugin1ViewModel). This makes it possible to define various UI components within the plug-in that can be integrated into the main application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic loading and registration of&amp;nbsp;plug-ins
&lt;/h2&gt;

&lt;p&gt;One of the central functions of a plug-in system is the dynamic loading of plug-ins at runtime. This is done by searching for and loading assemblies that implement the plug-in interface. The following code in the main application shows how this is achieved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private void LoadPlugins(ContainerBuilder builder)
{
  var pluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
  var pluginFiles = Directory.GetFiles(pluginPath, "*.dll");

  foreach (var pluginFile in pluginFiles)
  {
    var assembly = Assembly.LoadFrom(pluginFile);
    var pluginTypes = assembly.GetTypes().Where(t =&amp;gt; typeof(IPlugin).IsAssignableFrom(t) &amp;amp;&amp;amp; !t.IsInterface &amp;amp;&amp;amp; !t.IsAbstract);

    foreach (var pluginType in pluginTypes)
    {
      var plugin = (IPlugin)Activator.CreateInstance(pluginType);

      foreach (var viewType in plugin.ViewTypes)
      {
        builder.RegisterType(viewType).AsSelf().InstancePerDependency();
      }

      foreach (var viewModelType in plugin.ViewModelTypes)
      {
        builder.RegisterType(viewModelType).AsSelf().InstancePerDependency();
      }

      builder.RegisterInstance(plugin).As&amp;lt;IPlugin&amp;gt;();

      // Registers the views and ViewModels
      builder.RegisterViewsAndViewModels(assembly);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, a directory is searched for plug-ins that are stored as DLLs. Each plug-in is loaded dynamically and the views and ViewModels defined in it are registered with Autofac in the dependency injection container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic registration of views and ViewModels with&amp;nbsp;Autofac
&lt;/h2&gt;

&lt;p&gt;We use an extended function of Autofac for efficient registration of views and ViewModels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static class AutofacExtensions
{
  public static void RegisterViewsAndViewModels(this ContainerBuilder builder, Assembly assembly)
  {
    builder.RegisterAssemblyTypes(assembly)
           .Where(t =&amp;gt; t.Name.EndsWith("View") &amp;amp;&amp;amp; typeof(FrameworkElement).IsAssignableFrom(t))
           .OnActivated(args =&amp;gt;
           {
             var viewType = args.Instance.GetType(); 
             var viewModelTypeName = viewType.Name.Replace("View", "ViewModel");
             var viewModelType = viewType.Assembly.GetType(viewType.Namespace + "." + viewModelTypeName); 

             if (viewModelType == null)
             {
               viewModelType = assembly.GetTypes().FirstOrDefault(t =&amp;gt; t.Name == viewModelTypeName);
             }

             if (viewModelType != null)
             {
               var viewModel = args.Context.Resolve(viewModelType);
               ((FrameworkElement)args.Instance).DataContext = viewModel;
             }
           })
           .AsSelf()
           .InstancePerDependency();

    builder.RegisterAssemblyTypes(assembly)
           .Where(t =&amp;gt; t.Name.EndsWith("ViewModel"))
           .AsSelf()
           .InstancePerDependency();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method registers all Views and ViewModels of a plug-in based on naming conventions. Views that end with 'View' are registered and the associated ViewModels are also identified and loaded into the DI container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communication between plug-ins via the EventAggregator
&lt;/h2&gt;

&lt;p&gt;In complex applications, the plug-ins must communicate with each other without creating direct dependencies in order to ensure modularity. In our project, this is made possible by the EventAggregator. Here is a concrete example from the MyFirstPlugin:&lt;br&gt;
&lt;strong&gt;Plugin 1: Send&amp;nbsp;message:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class MyPlugin1ViewModel
{
    private readonly IEventAggregator _eventAggregator;

    public ICommand MyCommand { get; }

    public MyPlugin1ViewModel(IEventAggregator eventAggregator)
    {
      _eventAggregator = eventAggregator;
      MyCommand = new RelayCommand(_ =&amp;gt; OnButtonClick());
    }

    private void OnButtonClick()
    {
      var msg = new SpecialEvent("Hello from the other view internal my first plugin");
      _eventAggregator.Publish(msg);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the MyPlugin1ViewModel sends a special event, SpecialEvent, with a message when the user presses a button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin 2: Receive&amp;nbsp;message&lt;/strong&gt;&lt;br&gt;
The second plug-in can subscribe to this event and process the message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class MySecondPluginViewModel : IHandle&amp;lt;SpecialEvent&amp;gt;
{
    private readonly IEventAggregator _eventAggregator;

    public MySecondPluginViewModel(IEventAggregator eventAggregator)
    {
        _eventAggregator = eventAggregator;
        _eventAggregator.Subscribe(this);
    }

    public void Handle(SpecialEvent message)
    {
        // Process message
        Console.WriteLine(message.Content);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, MySecondPluginViewModel receives the SpecialEvent and processes its content in the handle method. This ensures that the two plug-ins can communicate via the EventAggregator without being directly dependent on each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A plug-in architecture offers a flexible and scalable solution for extending an application. In our example, this architecture is realised through the combination of Autofac for dependency injection and an event aggregator for communication between the components. Such systems are ideal for scenarios in which an application needs to be dynamically extended with new functionalities without changing the core of the application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/r/?url=https%3A%2F%2Fgithub.com%2FWittBen%2FWPF-Plugins" rel="noopener noreferrer"&gt;plugIn architecture&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microsoft</category>
      <category>csharp</category>
      <category>coding</category>
      <category>development</category>
    </item>
    <item>
      <title>HealthChecks</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Tue, 08 Oct 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/healthchecks-4aie</link>
      <guid>https://dev.to/ben-witt/healthchecks-4aie</guid>
      <description>&lt;p&gt;From time to time it happens that the connection to the database fails or the services or containers crash. This can lead to a considerable investigation effort to find the reasons why these problems occur, which can be facilitated by the use of “HealthChecks”.&lt;/p&gt;

&lt;p&gt;ASP.NET provides a convenient and effective approach to implementing this functionality.&lt;/p&gt;

&lt;p&gt;To achieve this, the following nuggets need to be installed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dn3ls8cfbfu7m2rmzbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dn3ls8cfbfu7m2rmzbq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To include the following items in the Program.cs file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddHealthChecks();  
...  
...  
//Now the HealthChecks should be mapped to the current endpoint  
app.MapHealthChecks("/_health");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would provide us with the foundation to check the health of our application. Currently, no checks are performed, so our application is considered to be in a healthy state.&lt;/p&gt;

&lt;p&gt;Upon launching the app, initially, only the Swagger site is loaded and no other content is visible. Let’s simply update the address to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk2yeyvkj8a3q90hhaue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk2yeyvkj8a3q90hhaue.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
To extend our HealthCheck with a custom check, we will create a new class called “&lt;strong&gt;MyFirstHealthCheck&lt;/strong&gt;” with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class MyFirstHealthCheck : IHealthCheck  
{    
  public Task&amp;lt;HealthCheckResult&amp;gt; CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)    
  {      
    return Task.FromResult(HealthCheckResult.Healthy("This could be a service"));    
  }  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class serves the purpose of returning a “HealthCheckResult” with the status “Healthy”.&lt;/p&gt;

&lt;p&gt;We have reached a point where we need to include this check in our Program.cs file. When we call this check, the situation looks a little different. Initially, the appearance remains unchanged. However, if we set a breakpoint in the source code, it will be triggered when we refresh the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqk6r882c8q4kxm2guyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqk6r882c8q4kxm2guyv.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Instead of using the term “healthy”, which is not very meaningful, it would be more helpful to convey a specific message. For example, we could use a more descriptive message to communicate the status or result of the test.&lt;/p&gt;

&lt;p&gt;To accomplish this, we need to ensure that the NuGet package “AspNetCore.HealthChecks.UI.Client” is installed. After that, we can proceed to extend the following functionality:&lt;/p&gt;

&lt;p&gt;In the Program.cs file, we need to incorporate a ResponseWriter into the mapper of the HealthCheck. This will enable us to customize the output or response generated by the HealthCheck module.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aziko86nusp3tk82tgu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aziko86nusp3tk82tgu.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Let’s restart the program and see what comes out of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10b4nzegzs77ksqozgj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10b4nzegzs77ksqozgj1.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To enable easy retrieval from a list, we can assign a tag to this check. Implementing the tag in the service would facilitate the process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb62jwzxgvrwxnawuktae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb62jwzxgvrwxnawuktae.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Now let’s execute this request once more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz46f2r64zhegupwo8fgp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz46f2r64zhegupwo8fgp.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Let’s create another check:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprnzn7lyjd6hsum38v1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprnzn7lyjd6hsum38v1f.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
It can be a bit overwhelming at first.&lt;/p&gt;

&lt;p&gt;To create a dashboard that lists all our checks using the NuGet package “AspNetCore.HealthChecks.UI.Client," we need to make three additions to the Program.cs file:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Add a new service:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This new dashboard requires persistent storage, so we need to add the NuGet package “AspNetCore.HealthChecks.UI.InMemory.Storage."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg542dla3dsbhsli8e9dy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg542dla3dsbhsli8e9dy.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. New Mapping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below the previous AddHealthChecks() method call, we can add a new method call AddHealthChecksUI() that uses the in-memory store.&lt;/p&gt;

&lt;p&gt;To map this new user interface, we can add a route to the Configure() method in the Startup.cs file. The route should point to the Health Checks UI endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkx1jetsbzeflf0o30tsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkx1jetsbzeflf0o30tsd.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.) AppSettings.json&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To configure the dashboard to forward requests to the appropriate address, you can modify the appsettings.json file. Within the file, you will need to update the relevant settings to specify the desired address for request forwarding.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81nhu2jl98xxrz62p1fn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81nhu2jl98xxrz62p1fn.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
We start the application and change the address:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzg72m7de3834ybxjob81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzg72m7de3834ybxjob81.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Now our dashboard opens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2f8mlnvcwfukpbnjganp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2f8mlnvcwfukpbnjganp.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
By utilizing the dashboard we have created, we can now have a comprehensive overview of the checks we have implemented for our application. The dashboard not only displays the current status of the checks but also provides a history section that gives us insights into the events that have occurred from the application’s start until now. To access more detailed information, we can explore the details section within the dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv1ug9frczst5nmti33a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv1ug9frczst5nmti33a.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
What could a HealthCheck that monitors the connection to the database look like?&lt;/p&gt;

&lt;p&gt;We create a new object that checks the connection to the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class DatabaseConnectionCheck : IHealthCheck
{
  public async Task&amp;lt;HealthCheckResult&amp;gt; CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
  {
    try
    {
      using var connection = new SqlConnection("Data Source=.;Initial Catalog=MyDatabase;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False");
      await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
      using var command = connection.CreateCommand();
      command.CommandText = "Select 1";
      object result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);

      return HealthCheckResult.Healthy("The status of the connection is open");
    }
    catch (Exception ex)
    {
      return HealthCheckResult.Unhealthy("The status of the connection is close");
    }

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

&lt;/div&gt;



&lt;p&gt;Here only the connection to the database is established and by the select 1 it is checked whether the query can be sent or not. Accordingly, a result is returned which is healthy or unhealthy.&lt;/p&gt;

&lt;p&gt;Now we only have to announce the service:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffv7kcxbhglag5k7sulo8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffv7kcxbhglag5k7sulo8.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Now we have a comprehensive overview of all the checks that have been configured in our application. This provides a clear and organized display of the various checks and their respective statuses, making it easier to monitor and track the health of our application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fycnj937s57c4fkwepomp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fycnj937s57c4fkwepomp.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
these checks are queried every 5 seconds as we set, now if the connection to the database is closed or interrupted, this dashboard will react to it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe10uiyn4itpga5p78v8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe10uiyn4itpga5p78v8f.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
you will find a number of nuget dealing with some topics:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7e8arffwlz22sssalbej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7e8arffwlz22sssalbej.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/WittBen/HealthChecks?source=post_page-----4744f10b8970--------------------------------" rel="noopener noreferrer"&gt;Github: HealthChecks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microsoft</category>
      <category>aspnet</category>
      <category>development</category>
      <category>webdev</category>
    </item>
    <item>
      <title>DBConcurrency with EF Core</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Tue, 01 Oct 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/dbconcurrency-with-ef-core-e02</link>
      <guid>https://dev.to/ben-witt/dbconcurrency-with-ef-core-e02</guid>
      <description>&lt;h2&gt;
  
  
  What is concurrency in the database?
&lt;/h2&gt;

&lt;p&gt;Concurrency is the simultaneous processing of data by multiple users or processes. In a database environment, this can lead to conflicts if two transactions attempt to access the same data at the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5l1sswcfjjwtskatlpt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5l1sswcfjjwtskatlpt5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is concurrency control important?
&lt;/h2&gt;

&lt;p&gt;Concurrency control is important to ensure that multiple processes or threads can run simultaneously in a system without causing unexpected problems. This is important for efficient resource utilisation, data consistency and avoiding race conditions and deadlocks. Effective concurrency control improves the overall performance of systems, especially in multi-core environments, and contributes to a stable and user-friendly experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conflict detection in Entity Framework Core
&lt;/h2&gt;

&lt;p&gt;Entity Framework Core offers mechanisms for recognising conflicts.&lt;/p&gt;

&lt;p&gt;In Entity Framework Core, conflict detection is often activated by the “Timestamp” attribute or the “ConcurrencyCheck” attribute.&lt;/p&gt;

&lt;h2&gt;
  
  
  „Timestamp” attribute:
&lt;/h2&gt;

&lt;p&gt;Purpose: The “Timestamp” attribute is used to detect conflicts with concurrent updates in Entity Framework Core.&lt;br&gt;
How it works: When the &lt;code&gt;Timestamp&lt;/code&gt; attribute is applied to a property, Entity Framework Core automatically creates a database column of type RowVersion. This column is automatically updated with every update. If two updates occur simultaneously, the system recognises a conflict based on this column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Employee
{
  public int Id { get; set; }
  public string FirstName { get; set; } = string.Empty;
  public string LastName { get; set; } = string.Empty;

  public string Fullname =&amp;gt; $"{FirstName} {LastName}";

  public string Department { get; set; } = string.Empty;
  public int EntryYear { get; set; }

  [Timestamp]
  public byte[] Version { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  „ConcurrencyCheck“ attribute:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose:&lt;/strong&gt; The “ConcurrencyCheck” attribute is also used to detect conflicts during updates by ensuring that no changes were made to certain properties during the update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How it works:&lt;/strong&gt; When the &lt;code&gt;ConcurrencyCheck&lt;/code&gt; attribute is applied to a property, Entity Framework Core compares the value of that property during the update with the value stored in the database. If differences are detected, a conflict is recognised.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Employee
{
  public int Id { get; set; }
  public string FirstName { get; set; } = string.Empty;
  public string LastName { get; set; } = string.Empty;

  public string Fullname =&amp;gt; $"{FirstName} {LastName}";

  public string Department { get; set; } = string.Empty;
  public int EntryYear { get; set; }

  [ConcurrencyCheck]
  public byte[] RowVersion { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both approaches serve to ensure that database updates can be carried out consistently and without conflict, even if several users access the database at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategies for conflict resolution
&lt;/h2&gt;

&lt;p&gt;When resolving conflicts in an application, there are various strategies to ensure that simultaneous updates of data are consistent and without data inconsistencies. Here are some common strategies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Last writer wins (lww):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: the last update written to the database is considered the valid version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation&lt;/strong&gt;: If a conflict occurs, the update with the most recent timestamp or transaction ID is favoured.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Extended conflict resolution (Custom Conflict Resolution):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: custom logic is implemented to resolve conflicts based on application logic or user preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation&lt;/strong&gt;: Developers determine how conflicts should be handled, possibly through user interaction or specific business rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Optimistic conflict resolution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: It is assumed that conflicts rarely occur, so that the data is updated without prior blocking. Conflicts are only checked when data is written to the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation&lt;/strong&gt;: Use of mechanisms such as &lt;code&gt;Timestamp&lt;/code&gt; or &lt;code&gt;ConcurrencyCheck&lt;/code&gt; attributes. In the event of a conflict, an exception is raised and the application can react.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pessimistic conflict resolution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: data is locked throughout the update process to ensure that no other updates can be performed at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation&lt;/strong&gt;: Use of explicit locks or transaction isolation levels to ensure that no other operations can access the locked data at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Automatic merging:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: In the event of conflicts, changes are automatically merged if this is possible. This is often used in distributed version control systems or collaborative tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation&lt;/strong&gt;: Automatic merging algorithms are used to recognise and automatically resolve conflicts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The choice of the appropriate strategy depends on the requirements of the application, the type of data and the expected usage patterns. It is important to choose a strategy that ensures data consistency and at the same time does not impair the performance and user-friendliness of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation of concurrency control in a sample application
&lt;/h2&gt;

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

&lt;p&gt;Assume you have an “Employee” entity class with a “LastName” property and a timestamp attribute for concurrency control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;

public string Fullname =&amp;gt; $"{FirstName} {LastName}";

public string Department { get; set; } = string.Empty;
public int EntryYear { get; set; }
[Timestamp]
public byte[] Timestamp { get; set; } // Timestamp-Eigenschaft für Concurrency
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here’s how you can use the ConcurrencyCheck attribute for concurrency control in an entity class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Employee
{
  public int Id { get; set; }
  public string FirstName { get; set; } = string.Empty;
  public string LastName { get; set; } = string.Empty;

  public string Fullname =&amp;gt; $"{FirstName} {LastName}";

  public string Department { get; set; } = string.Empty;
  public int EntryYear { get; set; }

  [ConcurrencyCheck]
  public byte[] RowVersion { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your DbContext, you can then specify that the timestamp row acts as RowVersion:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In our example, we select the “ConcurrencyCheck”&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public void Configure(EntityTypeBuilder&amp;lt;Employee&amp;gt; builder)
 {
   builder.HasKey(c =&amp;gt; c.Id);
   builder.Property(c =&amp;gt; c.Id).ValueGeneratedOnAdd();
   builder.Property(rv =&amp;gt; rv.RowVersion).IsRowVersion();
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conflict simulation:
&lt;/h2&gt;

&lt;p&gt;To simulate a concurrency conflict, you can perform the following steps:&lt;/p&gt;

&lt;p&gt;Assume you already have an employee in your database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var loadedEmployee = dbContext1.Employees.Find(id);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we simulate a conflict by updating the lastname both in the database and in the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Changes in the database by another process

var existingEmployeeInDatabase = dbContext2.Employees.Find(id);


if (loadedEmployee != null &amp;amp;&amp;amp; existingEmployeeInDatabase != null)
 {
   // Change only loadedEmployee
   loadedEmployee.LastName = "NewLastName1";

   try
   {
     dbContext1.SaveChanges();  // Save changes in LoadedEmployee
   }
   catch (DbUpdateConcurrencyException ex)
   {
     Console.WriteLine($"Concurrency conflict on LoadedEmployee: {ex.Message}");
     // Here you can implement conflict handling, e.g. retry or report errors to the user interface.
   }

   // Change existingEmployeeInDatabase now
   existingEmployeeInDatabase.LastName = "NewLastName2";

   try
   {
     dbContext2.SaveChanges();  // Try to save changes in existingEmployeeInDatabase, which should throw a DbUpdateConcurrencyException
     Console.WriteLine("Records updated successfully.");
   }
   catch (DbUpdateConcurrencyException ex)
   {
     Console.WriteLine($"Concurrency conflict on existingEmployeeInDatabase: {ex.Message}");
     // Here you can implement conflict handling, e.g. retry or report errors to the user interface.
   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, &lt;code&gt;dbContext2.SaveChanges()&lt;/code&gt; will throw a &lt;code&gt;DbUpdateConcurrencyException&lt;/code&gt; because the database version of the product no longer matches the one in your application.&lt;/p&gt;

&lt;p&gt;You can now implement the conflict resolution logic to decide how to deal with this conflict. This can include applying strategies such as “client wins”, “database wins” or customised conflict resolution.&lt;/p&gt;

&lt;p&gt;Here is an example of the client wins strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using (var dbContext1 = Create.Context())
 using (var dbContext2 = Create.Context())
 {
   var loadedEmployee = dbContext1.Employees.Find(id);
   var existingEmployeeInDatabase = dbContext2.Employees.Find(id);

   if (loadedEmployee != null &amp;amp;&amp;amp; existingEmployeeInDatabase != null)
   {
     // Change only loadedEmployee
     loadedEmployee.LastName = "NewLastName1";

     try
     {
       dbContext1.SaveChanges();  // Save changes in LoadedEmployee
     }
     catch (DbUpdateConcurrencyException ex)
     {
       Console.WriteLine($"Concurrency conflict on LoadedEmployee: {ex.Message}");

       ex.Entries.Single().Reload();
       dbContext1.SaveChanges(); 
       Console.WriteLine("Client Wins: Records updated successfully.");
     }

     // Change existingEmployeeInDatabase now
     existingEmployeeInDatabase.LastName = "NewLastName2";

     try
     {
       dbContext2.SaveChanges();  // Try to save changes in existingEmployeeInDatabase, which should throw a DbUpdateConcurrencyException
       Console.WriteLine("Records updated successfully.");
     }
     catch (DbUpdateConcurrencyException ex)
     {
       Console.WriteLine($"Concurrency conflict on existingEmployeeInDatabase: {ex.Message}");
       // Here you can implement conflict handling, e.g. retry or report errors to the user interface.
     }
   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Concurrency proves to be essential by enabling simultaneous access to database resources. Precise handling of DBConcurrency minimises potential conflicts, ensures data consistency and guarantees integrity in dynamic environments. Clever implementation of mechanisms such as transaction control and locking procedures not only optimises synchronisation, but also increases performance. In complex applications that access shared databases, the accurate handling of concurrency is crucial for a robust system architecture and smooth functioning under intensive load.&lt;br&gt;
&lt;a href="https://github.com/WittBen/DBConcurrency?source=post_page-----3f1c0d6475d1--------------------------------" rel="noopener noreferrer"&gt;GitHub: DBConcurrency&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>microsoft</category>
      <category>developer</category>
      <category>efcore</category>
    </item>
    <item>
      <title>Basics of Clean Architecture with C#</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Tue, 24 Sep 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/basics-of-clean-architecture-with-c-218i</link>
      <guid>https://dev.to/ben-witt/basics-of-clean-architecture-with-c-218i</guid>
      <description>&lt;p&gt;This article covers the basics of Clean Architecture with C#. Readers will be empowered to develop solid, well-structured code that excels in maintainability, extensibility, and testability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to Clean Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is Clean Architecture?&lt;/strong&gt;&lt;br&gt;
Clean Architecture, proposed by Robert C. Martin or “Uncle Bob,” is an architectural pattern for software projects aimed at keeping the code clean, modular, and independent of external influences. It isolates the business logic of the application from external details such as user interfaces, databases, and frameworks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is Clean Architecture important?&lt;/strong&gt;&lt;br&gt;
Clean Architecture offers a variety of advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;High Maintainability&lt;/strong&gt;: The clear separation of business logic and technical details facilitates understanding of the code and enhances its maintainability.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2.** Extensibility**: Thanks to the well-structured architecture, new features can be added or existing ones modified easily without jeopardizing the integrity of the overall application.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testability&lt;/strong&gt;: The clean separation of components simplifies the writing of automated tests, thereby improving the quality of the software.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Independence from external influences&lt;/strong&gt;: By using abstractions and interfaces, the application remains flexible and is not tied to specific technologies or frameworks.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The benefits of Clean Architecture include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear Separation of Responsibilities&lt;/strong&gt;: The clear structure defines the responsibilities of the various components, leading to better-organized code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimization of Dependencies&lt;/strong&gt;: Clean Architecture minimizes dependencies between different parts of the application, enhancing flexibility and maintainability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facilitation of Testing&lt;/strong&gt;: The clean separation of components allows tests to focus on individual parts of the application, improving testability and software quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support for Scalability and Extensibility&lt;/strong&gt;: The modular structure of Clean Architecture makes it easier to scale the application and add new features without redesigning the entire architecture.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Principles of Clean Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Separation of Concerns:&lt;/strong&gt;&lt;br&gt;
This principle demands that various aspects of a software application, such as user interface, business logic, and data persistence, be separated from each other. This leads to better-structured and more maintainable code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency Rule:&lt;/strong&gt;&lt;br&gt;
The dependency rule states that dependencies within an application should always point towards stable layers. For example, the business logic layer should not have direct dependencies on the user interface or databases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stable Dependencies Principle:&lt;/strong&gt;&lt;br&gt;
This principle dictates that dependent components should be more stable than the components they depend on. This means that high-level modules should not depend on low-level modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stable Abstractions Principle:&lt;/strong&gt;&lt;br&gt;
Here, the abstraction should be more stable than the concrete implementation. Abstractions should be independent of concrete implementations.&lt;/p&gt;

&lt;p&gt;These principles form the backbone of Clean Architecture and contribute to creating a robust and flexible software architecture. Now, let’s take a look at the components of Clean Architecture.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Components of Clean Architecture
&lt;/h2&gt;

&lt;p&gt;Clean Architecture comprises various components designed to divide the application into clearly defined layers. Here are the main components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Dependency Inversion Principle:&lt;/strong&gt; This principle states that dependencies should depend on abstract interfaces, not concrete implementations. This decouples the layers and increases the flexibility and testability of the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Entities:&lt;/strong&gt; Entities represent the core objects of the application, representing the state and behavior of the business logic. They are independent of the database or other external systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use Cases:&lt;/strong&gt; Use Cases contain the application logic and define how user interactions are handled. They are independent of the user interface and other external systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Interface Adapters:&lt;/strong&gt; These adapters connect the application to external systems such as databases, APIs, or user interfaces. They convert data and calls into formats that can be processed by the core application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Frameworks &amp;amp; Drivers:&lt;/strong&gt; This layer contains external frameworks and drivers used by the application, such as web frameworks, database drivers, or user interface libraries.&lt;/p&gt;

&lt;p&gt;By clearly separating these components, changes can be made in one area of the application without affecting other areas. This makes the application flexible and easier to maintain.&lt;/p&gt;

&lt;p&gt;Implementation of Clean Architecture in C# (Continued)&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementing the Project Structure:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Creating the Core Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Define Entities and Use Cases.&lt;br&gt;
Create a folder named “Core.”&lt;br&gt;
Within this folder, classes for Entities such as “User.cs” or “Product.cs” are created.&lt;br&gt;
Create interfaces or abstract classes for Use Cases like “IUserService.cs” or “IProductService.cs”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Creating the Application Layer:&lt;/strong&gt;&lt;br&gt;
Implement Use Cases.&lt;br&gt;
Create a folder named “Application.”&lt;br&gt;
Implement Use Cases as classes that inherit from the interfaces defined in the Core layer.&lt;br&gt;
For example: “UserRegistrationService.cs”, “ProductManagementService.cs”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Creating the Infrastructure Layer:&lt;/strong&gt;&lt;br&gt;
Implement Interface Adapters.&lt;br&gt;
Create a folder named “Infrastructure.”&lt;br&gt;
Implement concrete classes for the interfaces defined in the Core layer.&lt;br&gt;
For example: “UserRepository.cs” for “IUserRepository”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Creating the Presentation Layer:&lt;/strong&gt;&lt;br&gt;
Implement user interface or API controllers.&lt;br&gt;
Create a folder named “Presentation.”&lt;br&gt;
Implement controllers, such as ASP.NET MVC controllers or WebAPI controllers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementing Entities:
&lt;/h2&gt;

&lt;p&gt;Entities represent the core objects of the application and include the state and behavior of the business logic. Here’s an example of a “User” Entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace CleanArchitectureExample.Core.Entities;

public class User
{
  public int Id { get; set; }
  public string Username { get; set; }
  public string Email { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this structure, we lay the foundation for a clean and modular architecture that is easily maintainable and extensible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Use Cases:
&lt;/h2&gt;

&lt;p&gt;Use Cases contain the application logic and define how user interactions are processed. Here’s an example of a “UserRegistration” Use Case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace CleanArchitectureExample.Core.UseCases;

public interface IUserRegistrationUseCase
{
  void RegisterUser(string username, string email);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace CleanArchitectureExample.Application.Services;

public class UserService : IUserRegistrationUseCase
{
  private readonly IUserRepository _userRepository;

  public UserService(IUserRepository userRepository)
  {
    _userRepository = userRepository;
  }

  public IUserRepository GetUserRepository()
  {
    return _userRepository;
  }

  public void RegisterUser(string username, string email)
  {
    // The user registration logic is implemented here
    var newUser = new User { Username = username, Email = email };

    _userRepository.AddUser(newUser);
    Console.WriteLine($"User {username} with the email {email} was successfully registered.");
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing Interface Adapters:
&lt;/h2&gt;

&lt;p&gt;Interface Adapters connect the application to external systems such as databases or APIs. Here’s an example of a UserRepository implementing the database access interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace CleanArchitectureExample.Infrastructure;

public interface IUserRepository
{
  void AddUser(User user);
}

public class UserRepository : IUserRepository
{
  public void AddUser(User user)
  {
    // This is where the logic for adding a user to the database would be implemented
    Console.WriteLine($"User {user.Username} has been added to the database.");
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we will proceed to manage dependencies between components using Dependency Injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation of Clean Architecture in C
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Setting up Dependency Injection:&lt;/strong&gt;&lt;br&gt;
In this step, we will set up Dependency Injection (DI) in our C# project to manage dependencies between different components.&lt;/p&gt;

&lt;p&gt;First, we need to configure a DI container in our project. We can use the built-in DI container library provided by .NET for this purpose. Here’s an example of how we can do this:&lt;br&gt;
&lt;/p&gt;

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

class Program
{
  static void Main(string[] args)
  {
    // Configuration of the DI container
    var services = new ServiceCollection();
    services.AddTransient&amp;lt;IUserRepository, UserRepository&amp;gt;();
    services.AddTransient&amp;lt;IUserRegistrationUseCase, UserService&amp;gt;();

    // Create the DI container
    var serviceProvider = services.BuildServiceProvider();

    // Use the dependent components
    var userService = serviceProvider.GetService&amp;lt;IUserRegistrationUseCase&amp;gt;();
    userService.RegisterUser("Max Mustermann", "max@example.com");

    Console.ReadLine(); // Wait for user input so that the console application is not closed immediately
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we use the &lt;code&gt;ServiceCollection&lt;/code&gt; class to register dependencies. With &lt;code&gt;AddTransient&lt;/code&gt;, we specify that a new instance of the dependent class should be created each time it’s requested.&lt;/p&gt;

&lt;p&gt;Once we have configured our DI container, we can use the dependent components in our classes. The DI container takes care of resolving and providing the dependent instances.&lt;/p&gt;

&lt;p&gt;For example, the &lt;code&gt;UserRegistrationService&lt;/code&gt; can access the &lt;code&gt;IUserRepository&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace CleanArchitectureExample.Application.Services;

public class UserService : IUserRegistrationUseCase
{
  private readonly IUserRepository _userRepository;

  public UserService(IUserRepository userRepository)
  {
    _userRepository = userRepository;
  }

  public IUserRepository GetUserRepository()
  {
    return _userRepository;
  }

  public void RegisterUser(string username, string email)
  {
    // The user registration logic is implemented here
    var newUser = new User { Username = username, Email = email };

    _userRepository.AddUser(newUser);
    Console.WriteLine($"User {username} with the email {email} was successfully registered.");
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using Dependency Injection, the components are loosely coupled, which improves the maintainability, extensibility, and testability of our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing in Clean Architecture
&lt;/h2&gt;

&lt;p&gt;Testing is an integral part of Clean Architecture, ensuring that our application functions correctly and that changes do not introduce unwanted side effects. In Clean Architecture, we use various types of tests:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Unit Tests:&lt;/strong&gt; These test individual components of our application in isolation. Mock objects can be used to simulate external dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Integration Tests:&lt;/strong&gt; These test the collaboration of multiple components of our application to ensure they interact properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. End-to-End Tests:&lt;/strong&gt; These test the entire application from an external perspective, such as by executing user interactions through the user interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test-Driven Development (TDD) Approach:
&lt;/h2&gt;

&lt;p&gt;In the test-driven development approach, we first write tests that define the expected behavior of the application, and then implement the code that passes these tests. This approach promotes thorough test coverage and often leads to better code quality and structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Tests for Various Components:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is an example of a unit test for the &lt;code&gt;UserRegistrationService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Fact]
  public void RegisterUser_Should_Register_User()
  {
    // Arrange
    var mockUserRepository = new Mock&amp;lt;IUserRepository&amp;gt;();
    var userService = new UserService(mockUserRepository.Object);
    var username = "John Smith";
    var email = "john@example.com";

    // Act
    userService.RegisterUser(username, email);

    // Assert
    mockUserRepository.Verify(repo =&amp;gt; repo.AddUser(It.IsAny&amp;lt;User&amp;gt;()), Times.Once);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this test, we use a mock object for &lt;code&gt;IUserRepository&lt;/code&gt; to simulate database access. We verify that the &lt;code&gt;AddUser&lt;/code&gt; method of the repository was called once after registering a user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion:
&lt;/h2&gt;

&lt;p&gt;In this article, we’ve provided a comprehensive overview of the fundamentals of Clean Architecture with C#, covering everything from basic principles to practical implementation and testing. Clean Architecture offers a multitude of benefits for software project development, including improved maintainability, extensibility, and testability. By clearly separating responsibilities and minimizing dependencies, the code becomes more structured and easier to maintain. The use of Dependency Injection allows for flexible configuration of the application and facilitates testing by isolating components. Overall, Clean Architecture provides a robust approach to developing high-quality software that has proven itself in a variety of application domains.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/WittBen/CleanArchitecture" rel="noopener noreferrer"&gt;Clean architecture&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microsoft</category>
      <category>development</category>
      <category>architecture</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>HybridCache in a console application with Redis</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Tue, 17 Sep 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/hybridcache-in-a-console-application-with-redis-4ih1</link>
      <guid>https://dev.to/ben-witt/hybridcache-in-a-console-application-with-redis-4ih1</guid>
      <description>&lt;p&gt;In modern applications, the efficient management of data and the avoidance of unnecessary database queries is essential. An effective way to achieve this is to use a HybridCache, which utilises both a MemoryCache and a Distributed Cache such as Redis. In this article, I will present an implementation of a HybridCache in a console-only application and explain when to query which cache to achieve optimal performance. In addition, I will discuss the importance of cache invalidation and explain how this can be solved in the implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a HybridCache?
&lt;/h2&gt;

&lt;p&gt;A HybridCache combines several cache types in order to increase performance. In our case, we first access the fast, memory-based MemoryCache, and if no data is available there, we fall back on a distributed cache — in this case Redis. Only if neither cache returns a hit is a query sent to the database. This approach helps to minimise database queries and significantly reduce latency times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation of the HybridCache
&lt;/h2&gt;

&lt;p&gt;I have created a console application that implements the HybridCache with Microsoft.Extensions.Caching.Memory and Redis. The following code shows the basic implementation where cache invalidation plays a role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class HybridCache : IHybridCache
{
    private readonly IMemoryCache _memoryCache;
    private readonly IDistributedCache _distributedCache;
    private static readonly object _lock = new object();  // Lock for thread-safety

    public HybridCache(IMemoryCache memoryCache, IDistributedCache distributedCache)
    {
        _memoryCache = memoryCache;
        _distributedCache = distributedCache;
    }

    public async Task&amp;lt;T&amp;gt; GetOrSetAsync&amp;lt;T&amp;gt;(string key, Func&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; factory, TimeSpan duration)
    {
        T value;

        // Check MemoryCache first
        lock (_lock)
        {
            if (_memoryCache.TryGetValue(key, out value))
            {
                string valueString = value.ToString();
                valueString = ReplaceSourceInfo(valueString, "MemoryCache");
                value = (T)Convert.ChangeType(valueString, typeof(T));
                return value;
            }
        }

        // Check Distributed Cache (Redis)
        var cachedData = await _distributedCache.GetStringAsync(key);
        if (cachedData != null)
        {
            value = System.Text.Json.JsonSerializer.Deserialize&amp;lt;T&amp;gt;(cachedData);

            string valueString = value.ToString();
            valueString = ReplaceSourceInfo(valueString, "Distributed Cache");
            value = (T)Convert.ChangeType(valueString, typeof(T));

            // Set the value in MemoryCache
            lock (_lock)
            {
                _memoryCache.Set(key, value, absoluteExpirationRelativeToNow: duration);
            }

            return value;
        }

        // Query the database if not found in any cache
        value = await factory();

        // Save the data to both MemoryCache and Redis
        string newValue = ReplaceSourceInfo(value.ToString(), "Database");
        value = (T)Convert.ChangeType(newValue, typeof(T));

        var serializedValue = System.Text.Json.JsonSerializer.Serialize(value);
        await _distributedCache.SetStringAsync(key, serializedValue, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = duration  // Set expiration time for Redis
        });

        lock (_lock)
        {
            _memoryCache.Set(key, value, duration);  // Set expiration time for MemoryCache
        }

        return value;
    }

    public void InvalidateCache(string key)
    {
        // Remove key from MemoryCache
        lock (_lock)
        {
            _memoryCache.Remove(key);
        }

        // Remove key from Distributed Cache (Redis)
        _distributedCache.Remove(key);
    }

    private string ReplaceSourceInfo(string originalValue, string source)
    {
        string patternToRemove = @"\(Source:.*?\)";
        string updatedValue = System.Text.RegularExpressions.Regex.Replace(originalValue, patternToRemove, "");
        return $"{updatedValue} (Source: {source})";
    }

    public async Task&amp;lt;T&amp;gt; GetAsync&amp;lt;T&amp;gt;(string key)
    {
        // Try getting the value from MemoryCache first
        lock (_lock)
        {
            if (_memoryCache.TryGetValue(key, out T value))
            {
                return value;
            }
        }

        // If not found, get the value from Distributed Cache
        var cachedData = await _distributedCache.GetStringAsync(key);
        return cachedData != null ? System.Text.Json.JsonSerializer.Deserialize&amp;lt;T&amp;gt;(cachedData) : default;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cache invalidation
&lt;/h2&gt;

&lt;p&gt;In the above implementation, a simple time-based expiration is used to ensure that the cache entries in both the MemoryCache and the Redis Cache become invalid after a certain time. This is controlled by the AbsoluteExpirationRelativeToNow method, which determines when an entry expires automatically.&lt;br&gt;
In addition, I have added an InvalidateCache method that enables manual invalidation of the cache entries. With this method, the cache entry can be explicitly deleted from both the MemoryCache and Redis if, for example, the underlying data in the database has been changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  When is which cache used?
&lt;/h2&gt;

&lt;p&gt;In this implementation, the HybridCache ensures that queries are made according to a clear hierarchy. MemoryCache is queried first, as it is faster. If no data is found there, the cache checks Redis. Redis is favoured in distributed systems as it can serve multiple instances of the application and therefore provides a central source for the cache data. Finally, if both caches fail, the database is queried.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extended strategies for cache invalidation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;manual/explicit invalidation&lt;/strong&gt;: As shown in the code, there is an InvalidateCache method that can be used to manually remove cache entries. This is useful when data in the database changes and the cache contains outdated entries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;event-based invalidation&lt;/strong&gt;: In distributed systems, an event-based model could be used to invalidate cache entries. If the data changes, a message could be sent (e.g. via a message queue system) that invalidates the cache entry in several instances simultaneously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sliding expiration&lt;/strong&gt;: Another possible strategy is sliding expiration, in which the expiration time of a cache entry is extended with each access. This ensures that frequently used data remains in the cache for longer, while unused data expires after a certain time.
An example of sliding expiration could look like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var cacheEntryOptions = new DistributedCacheEntryOptions
{
    SlidingExpiration = TimeSpan.FromMinutes(5)  // Expiration is reset after each access
};

await _distributedCache.SetStringAsync(key, serializedValue, cacheEntryOptions);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Redis - locally or via Docker
&lt;/h2&gt;

&lt;p&gt;Redis is used as a distributed cache for this application. Redis can be installed in various ways. An easy way to install Redis locally is to download the Redis binaries from the official website (&lt;a href="https://redis.io/download" rel="noopener noreferrer"&gt;https://redis.io/download&lt;/a&gt;). Once Redis is installed, it can be executed on the local host on the standard port 6379.&lt;br&gt;
Alternatively, Redis can also be run via Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other options - e.g. with MS SQL Server
&lt;/h2&gt;

&lt;p&gt;In addition to Redis, there are also other options for implementing a distributed cache. One widespread option is the use of an SQL-based cache, e.g. with Microsoft SQL Server. This solution is often used in environments where there is already a strong integration with SQL Server-based systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The implementation of a HybridCache in a console application offers numerous advantages, especially when it comes to reducing latency times and database queries. By combining a fast memory-based cache such as MemoryCache and a distributed cache such as Redis, application performance can be significantly increased. The introduction of thread safety and proper synchronisation ensures that parallel requests do not cause inconsistencies. Furthermore, cache invalidation is an important aspect to ensure that only up-to-date data is used and should be customised to the needs of the application.&lt;br&gt;
With Redis as a locally installed cache - or even as a Docker container - and the right cache hierarchy, scalable and high-performance applications can be implemented.&lt;/p&gt;

&lt;h2&gt;
  
  
  DEMO:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/WittBen/HybridCache?source=post_page-----71083901f99f--------------------------------" rel="noopener noreferrer"&gt;Github: HybridCache&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microsoft</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>Implementing the Cached Repository Pattern in C#</title>
      <dc:creator>Ben Witt</dc:creator>
      <pubDate>Tue, 23 Jul 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/ben-witt/cached-repository-in-c-432c</link>
      <guid>https://dev.to/ben-witt/cached-repository-in-c-432c</guid>
      <description>&lt;h4&gt;
  
  
  Introduction to the Concept of a Cached Repository
&lt;/h4&gt;

&lt;p&gt;A cached repository is a design pattern aimed at enhancing application performance by storing data in a fast-access memory area known as a cache. This reduces the number of database accesses, thereby improving response times and the application's scalability. A repository abstracts data access and provides uniform interfaces for CRUD operations (Create, Read, Update, Delete). Combining these concepts offers a powerful method for optimizing data access patterns in modern applications.&lt;/p&gt;

&lt;h4&gt;
  
  
  Importance and Benefits of Using Cached Repositories in C# for Advanced Developers
&lt;/h4&gt;

&lt;p&gt;For advanced developers, cached repositories offer several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Improvement&lt;/strong&gt;: Reducing database accesses significantly enhances response times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Lower database load facilitates better application scalability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Reduction&lt;/strong&gt;: Fewer database queries translate to lower costs, especially with cloud services billed per query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency and Abstraction&lt;/strong&gt;: Using a uniform repository ensures consistent data access and allows for easy abstraction and testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Detailed Implementation of a Cached Repository in C# Using the Decorator Pattern and EF Core
&lt;/h4&gt;

&lt;p&gt;Implementing a cached repository can be effectively achieved through the decorator pattern. This pattern allows additional functionality to be added to an object without altering its structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define the Repository Interface&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IProductRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAllProductsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;AddProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;UpdateProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;DeleteProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implement the Base Repository with EF Core&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductRepository&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ApplicationDbContext&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ProductRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ApplicationDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAllProductsAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;AddProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;UpdateProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;DeleteProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implement the Cached Repository&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CachedProductRepository&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IMemoryCache&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;_cacheDuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CachedProductRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IMemoryCache&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Product_&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProductByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_cacheDuration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAllProductsAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"AllProducts"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllProductsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_cacheDuration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;AddProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;InvalidateCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;UpdateProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdateProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;InvalidateCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;DeleteProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;InvalidateCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;InvalidateCache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AllProducts"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Best Practices and Potential Pitfalls in Using Cached Repositories in C#
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Best Practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache Invalidation&lt;/strong&gt;: Ensure the cache is invalidated after write operations (Add, Update, Delete) to maintain consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Duration&lt;/strong&gt;: Choose an appropriate cache duration to balance freshness and performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Management&lt;/strong&gt;: Avoid overloading the cache, especially in memory-intensive applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Potential Pitfalls:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stale Data&lt;/strong&gt;: Cached data can become outdated, leading to inconsistencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt;: Implementing and managing cached repositories increases codebase complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Consumption&lt;/strong&gt;: Excessive caching can lead to high memory usage and potential out-of-memory issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Comparison with Other Caching Strategies and Their Applications
&lt;/h4&gt;

&lt;p&gt;In addition to the decorator pattern for cached repositories, there are several other caching strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In-Memory Caching&lt;/strong&gt;: Direct use of in-memory data stores like &lt;code&gt;IMemoryCache&lt;/code&gt; or &lt;code&gt;ConcurrentDictionary&lt;/code&gt;. Ideal for short-term, small data sets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed Caching&lt;/strong&gt;: Use of distributed caches like Redis or Memcached. Suitable for applications with high scalability requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Caching&lt;/strong&gt;: Use of HTTP headers to cache web resources. Ideal for high-traffic web applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each strategy has specific use cases and challenges that need to be carefully evaluated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Advanced Topics: Cache Invalidation and Synchronization Between Cache and Database
&lt;/h4&gt;

&lt;p&gt;Cache invalidation and synchronization are complex topics that require special attention:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Invalidation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time-to-Live (TTL)&lt;/strong&gt;: Set a TTL for cache entries to ensure automatic invalidation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-Based Invalidation&lt;/strong&gt;: Use events or message queues to synchronize cache invalidations in distributed systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Synchronization Between Cache and Database:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write-Through Caching&lt;/strong&gt;: Write operations are performed on both the database and the cache, ensuring consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-Behind Caching&lt;/strong&gt;: Write operations are initially performed on the cache and later synchronized with the database. This can improve performance but carries the risk of data inconsistency in the event of a crash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Priming&lt;/strong&gt;: Preload frequently accessed data into the cache at application startup to avoid initial latencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A comprehensive understanding and correct implementation of these techniques are crucial for successfully leveraging cached repositories in demanding applications.&lt;/p&gt;

&lt;p&gt;In summary, cached repositories, combined with the decorator pattern and Entity Framework Core, offer an effective method for optimizing data access patterns. They provide significant performance benefits but require careful implementation and management to avoid potential pitfalls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Github:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/WittBen/CachedRepository" rel="noopener noreferrer"&gt;https://github.com/WittBen/CachedRepository&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microsoft</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>development</category>
    </item>
  </channel>
</rss>
