<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Emiliano Montesdeoca</title><link>https://emimontesdeoca.github.io/pt/</link><description>Microsoft MVP in Developer Technologies. Cloud Solutions Team Lead. Speaker, blogger, and community advocate.</description><language>pt</language><managingEditor>emimontesdeoca@outlook.es (Emiliano Montesdeoca)</managingEditor><webMaster>emimontesdeoca@outlook.es (Emiliano Montesdeoca)</webMaster><lastBuildDate>Mon, 01 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://emimontesdeoca.github.io/pt/index.xml" rel="self" type="application/rss+xml"/><item><title>Blazor do zero: Capítulo 3 — Componentes que escalam</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-from-scratch-chapter-3/</link><pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-from-scratch-chapter-3/</guid><description>Capítulo 3 de Blazor do zero. Vamos a fundo em componentes: parâmetros, composição, slots com RenderFragment e organização de pastas para manter a UI limpa conforme o app cresce.</description><content:encoded>&lt;p>Bem-vindo ao Capítulo 3 de &lt;strong>Blazor do zero&lt;/strong>. Se você não leu o &lt;a href="../blazor-from-scratch-chapter-2">Capítulo 2&lt;/a>, leia primeiro para já ter o projeto base pronto.&lt;/p>
&lt;p>No Capítulo 2 fizemos a aplicação rodar. Neste capítulo vamos deixá-la &lt;strong>sustentável&lt;/strong>.&lt;/p>
&lt;p>Projetos Blazor ficam bagunçados rápido quando cada página vira um &lt;code>.razor&lt;/code> gigante. Componentes são a forma de evitar isso: trazem consistência, reutilização e limites claros entre partes da interface.&lt;/p>
&lt;hr>
&lt;h2 id="o-que-um-componente-blazor-realmente-é">O que um componente Blazor realmente é&lt;/h2>
&lt;p>Um componente é um arquivo &lt;code>.razor&lt;/code> que pode:&lt;/p>
&lt;ul>
&lt;li>Renderizar markup&lt;/li>
&lt;li>Manter estado local&lt;/li>
&lt;li>Receber dados via parâmetros&lt;/li>
&lt;li>Emitir eventos para o componente pai&lt;/li>
&lt;li>Renderizar conteúdo filho&lt;/li>
&lt;/ul>
&lt;p>Em execução, o Blazor trata cada componente como uma pequena máquina de estado. Quando o estado muda, ele renderiza novamente e aplica um diff no DOM.&lt;/p>
&lt;p>Por isso você deve desenhar entradas limpas e comportamento previsível.&lt;/p>
&lt;hr>
&lt;h2 id="passo-1-comece-com-um-componente-focado">Passo 1: Comece com um componente focado&lt;/h2>
&lt;p>Crie &lt;code>Components/Common/SectionHeader.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;header class=&amp;#34;section-header&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h2&amp;gt;@Title&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @if (!string.IsNullOrWhiteSpace(Subtitle))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;@Subtitle&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/header&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string Title { get; set; } = string.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string? Subtitle { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Use em &lt;code>Components/Pages/Home.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;PageTitle&amp;gt;Blazor do zero&amp;lt;/PageTitle&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;SectionHeader
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Title=&amp;#34;Blazor do zero&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Subtitle=&amp;#34;O capítulo 3 é sobre componentes.&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É um exemplo simples, mas introduz a ideia central: os parâmetros devem explicar o componente.&lt;/p>
&lt;hr>
&lt;h2 id="passo-2-use-parâmetros-para-deixar-o-comportamento-explícito">Passo 2: Use parâmetros para deixar o comportamento explícito&lt;/h2>
&lt;p>Vamos criar um botão reutilizável.&lt;/p>
&lt;p>&lt;code>Components/Common/AppButton.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;app-button @VariantCssClass&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;OnClick&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>Text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string Text { get; set; } &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Button&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string Variant { get; set; } &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;primary&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public EventCallback OnClick { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private string VariantCssClass &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> Variant&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToLowerInvariant() &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;secondary&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;app-button--secondary&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;danger&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;app-button--danger&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;app-button--primary&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Uso:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private int _savedCount;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void Save()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _savedCount++;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;AppButton Text=&amp;#34;Save&amp;#34; Variant=&amp;#34;primary&amp;#34; OnClick=&amp;#34;Save&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;Salvo @_savedCount vezes.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O ponto principal é o contrato:&lt;/p>
&lt;ul>
&lt;li>Parâmetros pequenos e claros&lt;/li>
&lt;li>Nomes explícitos, sem comportamento &amp;ldquo;mágico&amp;rdquo;&lt;/li>
&lt;li>Defaults seguros&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="passo-3-use-renderfragment-para-composição">Passo 3: Use &lt;code>RenderFragment&lt;/code> para composição&lt;/h2>
&lt;p>&lt;code>RenderFragment&lt;/code> permite que o componente pai injete blocos de UI no filho.&lt;/p>
&lt;p>Crie &lt;code>Components/Common/Card.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;article class=&amp;#34;card&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;header class=&amp;#34;card__header&amp;#34;&amp;gt;@Title&amp;lt;/header&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;section class=&amp;#34;card__body&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @ChildContent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/article&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string Title { get; set; } = string.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public RenderFragment? ChildContent { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Uso:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;Card Title=&amp;#34;Roadmap&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ul&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;li&amp;gt;Componentes&amp;lt;/li&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;li&amp;gt;Data binding&amp;lt;/li&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;li&amp;gt;Routing&amp;lt;/li&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/ul&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/Card&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esse padrão evita repetir o mesmo markup de estrutura em várias páginas.&lt;/p>
&lt;hr>
&lt;h2 id="passo-4-prefira-composição-em-vez-de-páginas-gigantes">Passo 4: Prefira composição em vez de páginas gigantes&lt;/h2>
&lt;p>Se a página crescer demais, separe por responsabilidade:&lt;/p>
&lt;ul>
&lt;li>&lt;code>ProfileSummary&lt;/code> para o bloco principal&lt;/li>
&lt;li>&lt;code>ProfileStats&lt;/code> para métricas&lt;/li>
&lt;li>&lt;code>ProfileActivityList&lt;/code> para atividades recentes&lt;/li>
&lt;/ul>
&lt;p>Assim a página vira orquestração e não um bloco monolítico.&lt;/p>
&lt;hr>
&lt;h2 id="passo-5-equilibre-markup-e-lógica">Passo 5: Equilibre markup e lógica&lt;/h2>
&lt;p>Para componentes simples, &lt;code>@code&lt;/code> inline funciona bem.&lt;/p>
&lt;p>Para componentes maiores, mova lógica para code-behind:&lt;/p>
&lt;ul>
&lt;li>&lt;code>UserCard.razor&lt;/code>&lt;/li>
&lt;li>&lt;code>UserCard.razor.cs&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Isso melhora a leitura do markup e da lógica C#.&lt;/p>
&lt;hr>
&lt;h2 id="passo-6-estrutura-de-pastas-prática">Passo 6: Estrutura de pastas prática&lt;/h2>
&lt;p>Uma organização que costuma escalar bem:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Components/Pages/&lt;/code> -&amp;gt; páginas roteáveis&lt;/li>
&lt;li>&lt;code>Components/Layout/&lt;/code> -&amp;gt; shell e navegação&lt;/li>
&lt;li>&lt;code>Components/Common/&lt;/code> -&amp;gt; blocos genéricos compartilhados&lt;/li>
&lt;li>&lt;code>Components/Features/&amp;lt;FeatureName&amp;gt;/&lt;/code> -&amp;gt; componentes de cada feature&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="erros-comuns-no-início-com-blazor">Erros comuns no início com Blazor&lt;/h2>
&lt;ul>
&lt;li>Passar parâmetros demais em vez de usar um view model dedicado&lt;/li>
&lt;li>Colocar regra de negócio dentro de componentes de página&lt;/li>
&lt;li>Criar um &amp;ldquo;componente Deus&amp;rdquo; com centenas de linhas&lt;/li>
&lt;li>Repetir padrões de markup em vez de extrair peças reutilizáveis&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="próximo-capítulo">Próximo capítulo&lt;/h2>
&lt;p>No Capítulo 4 vamos focar em &lt;strong>data binding e eventos&lt;/strong>: &lt;code>@bind&lt;/code>, tratamento de eventos, tradeoffs do two-way binding e padrões para manter estado previsível.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category><category>Series</category></item><item><title>Blazor do Zero: Capítulo 2 — A tua primeira app Blazor</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-from-scratch-chapter-2/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-from-scratch-chapter-2/</guid><description>Capítulo 2 da série Blazor do Zero. Criamos a primeira app, executamos localmente e percorremos os ficheiros importantes para entender a estrutura do projeto desde o início.</description><content:encoded>&lt;p>Bem-vindo ao Capítulo 2 de &lt;strong>Blazor do Zero&lt;/strong>. Se perdeste o &lt;a href="../blazor-from-scratch-chapter-1">Capítulo 1&lt;/a>, lê primeiro para manter o contexto dos modelos de renderização.&lt;/p>
&lt;p>Neste capítulo vamos criar uma nova app Blazor, executá-la localmente e perceber o papel dos ficheiros principais.&lt;/p>
&lt;hr>
&lt;h2 id="passo-1-criar-o-projeto">Passo 1: Criar o projeto&lt;/h2>
&lt;p>No terminal:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new blazor -o BlazorFromScratch
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd BlazorFromScratch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isto cria uma Blazor Web App em .NET 9 com a estrutura padrão.&lt;/p>
&lt;p>Se a tua ferramenta pedir um modelo de interatividade, começa por &lt;strong>Interactive Server&lt;/strong>. É o modo mais simples para aprender.&lt;/p>
&lt;hr>
&lt;h2 id="passo-2-executar-localmente">Passo 2: Executar localmente&lt;/h2>
&lt;p>Inicia a app:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet watch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois abre o URL mostrado no terminal (normalmente &lt;code>https://localhost:5xxx&lt;/code>). Deves ver a app padrão com as páginas Home, Counter e Weather.&lt;/p>
&lt;p>Se isto funcionar, o ambiente está pronto.&lt;/p>
&lt;hr>
&lt;h2 id="passo-3-entender-a-estrutura-do-projeto">Passo 3: Entender a estrutura do projeto&lt;/h2>
&lt;p>Estes são os ficheiros e pastas mais importantes no início:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Program.cs&lt;/code> — regista serviços e configura o pipeline HTTP.&lt;/li>
&lt;li>&lt;code>Components/App.razor&lt;/code> — componente raiz da aplicação.&lt;/li>
&lt;li>&lt;code>Components/Routes.razor&lt;/code> — mapeamento de rotas.&lt;/li>
&lt;li>&lt;code>Components/Pages/&lt;/code> — páginas com rota como Home e Counter.&lt;/li>
&lt;li>&lt;code>Components/Layout/&lt;/code> — layout partilhado (&lt;code>MainLayout.razor&lt;/code>, menu).&lt;/li>
&lt;li>&lt;code>wwwroot/&lt;/code> — recursos estáticos (CSS, imagens, favicon).&lt;/li>
&lt;li>&lt;code>appsettings.json&lt;/code> — valores de configuração.&lt;/li>
&lt;/ul>
&lt;p>Não te preocupes em dominar tudo já. Nesta fase, basta saber onde vive a UI e onde o arranque é configurado.&lt;/p>
&lt;hr>
&lt;h2 id="passo-4-fazer-a-primeira-alteração">Passo 4: Fazer a primeira alteração&lt;/h2>
&lt;p>Abre &lt;code>Components/Pages/Home.razor&lt;/code> e substitui o conteúdo por:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;PageTitle&amp;gt;Blazor do Zero&amp;lt;/PageTitle&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Blazor do Zero&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;O Capítulo 2 está a correr localmente.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Guarda e atualiza o browser. Com &lt;code>dotnet watch&lt;/code>, a app deve atualizar de imediato.&lt;/p>
&lt;p>Esta alteração simples confirma o ciclo completo: editar -&amp;gt; compilar -&amp;gt; renderizar.&lt;/p>
&lt;hr>
&lt;h2 id="passo-5-ler-o-programcs-uma-vez">Passo 5: Ler o Program.cs uma vez&lt;/h2>
&lt;p>Não precisas memorizar, mas deves reconhecer estas linhas:&lt;/p>
&lt;ul>
&lt;li>&lt;code>AddRazorComponents()&lt;/code> ativa componentes Razor.&lt;/li>
&lt;li>&lt;code>AddInteractiveServerComponents()&lt;/code> ativa componentes interativos no servidor.&lt;/li>
&lt;li>&lt;code>MapRazorComponents&amp;lt;App&amp;gt;()&lt;/code> mapeia o componente raiz para endpoints.&lt;/li>
&lt;/ul>
&lt;p>Estas linhas mostram como a app arranca e quais capacidades de renderização estão ativas.&lt;/p>
&lt;hr>
&lt;h2 id="problemas-comuns">Problemas comuns&lt;/h2>
&lt;p>Se algo falhar, normalmente é um destes pontos:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>SDK antigo&lt;/strong>: corre &lt;code>dotnet --version&lt;/code> e confirma .NET 9.&lt;/li>
&lt;li>&lt;strong>Certificado HTTPS&lt;/strong>: corre &lt;code>dotnet dev-certs https --trust&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Conflito de portas&lt;/strong>: termina processos &lt;code>dotnet&lt;/code> antigos e volta a correr.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="próximo-capítulo">Próximo capítulo&lt;/h2>
&lt;p>No Capítulo 3 vamos aprofundar &lt;strong>componentes&lt;/strong>: parâmetros, composição e organização de UI reutilizável.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category><category>Series</category></item><item><title>Blazor do Zero: Capítulo 1 — O que é Blazor?</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-from-scratch-chapter-1/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-from-scratch-chapter-1/</guid><description>Capítulo 1 da série Blazor do Zero. Vemos o que é realmente o Blazor, de onde veio, os diferentes modelos de renderização disponíveis hoje e como se compara com frameworks JavaScript.</description><content:encoded>&lt;p>Bem-vindo ao Capítulo 1 de &lt;strong>Blazor do Zero&lt;/strong>. Se perdeu o &lt;a href="../blazor-from-scratch-intro">post de introdução&lt;/a> onde explico o propósito desta série e a quem se destina, comece por lá — é curto.&lt;/p>
&lt;p>Neste capítulo vamos responder à pergunta fundamental: &lt;em>o que é Blazor?&lt;/em> Parece simples, mas a resposta tem várias camadas — especialmente porque &amp;ldquo;Blazor&amp;rdquo; passou a ter significados ligeiramente diferentes ao longo dos anos. No final deste post saberá o que é Blazor, como se encaixa no ecossistema .NET, quais são os diferentes modelos de renderização e por que razão poderá — ou não — escolhê-lo em vez de um framework JavaScript.&lt;/p>
&lt;hr>
&lt;h2 id="um-breve-historial">Um breve historial&lt;/h2>
&lt;p>O Blazor começou como um projeto experimental de Steve Sanderson na Microsoft por volta de 2017. A ideia era provocadora: executar C# no navegador usando WebAssembly, eliminando completamente a necessidade de JavaScript. Era uma prova de conceito, e o nome era uma mistura deliberada de &lt;strong>Bla&lt;/strong>zer e Ra&lt;strong>zor&lt;/strong> — o motor de templates sobre o qual o Blazor viria a ser construído.&lt;/p>
&lt;p>O experimento gerou entusiasmo suficiente para que a Microsoft o levasse a sério. O Blazor foi lançado como parte do ASP.NET Core 3.0 em setembro de 2019, primeiro como &lt;strong>Blazor Server&lt;/strong> — um modelo que executa o seu código C# no servidor e usa uma conexão SignalR em tempo real para enviar atualizações de interface ao navegador. &lt;strong>Blazor WebAssembly&lt;/strong> seguiu em maio de 2020 como parte do ASP.NET Core 3.1, trazendo execução verdadeiramente do lado do cliente ao framework.&lt;/p>
&lt;p>O .NET 6 e 7 refinaram a experiência do desenvolvedor. Depois, o .NET 8, lançado em novembro de 2023, repensou fundamentalmente o modelo de renderização com o que a Microsoft chamou de &lt;em>UI web full-stack&lt;/em>. Renderização estática do lado do servidor, streaming rendering, servidor interativo, WebAssembly interativo e um novo modo Auto viviam todos sob o mesmo teto num único projeto. O .NET 9 construiu sobre esta base e eliminou as arestas.&lt;/p>
&lt;p>É aqui que estamos hoje.&lt;/p>
&lt;hr>
&lt;h2 id="o-que-o-blazor-realmente-é">O que o Blazor realmente é&lt;/h2>
&lt;p>No seu núcleo, o Blazor é um &lt;strong>framework de UI baseado em componentes para .NET&lt;/strong>. Constrói a sua interface a partir de componentes — peças autocontidas de C# e marcação HTML que podem manter estado, responder a eventos e compor-se em estruturas maiores. Se trabalhou com React ou Vue, esse modelo mental vai parecer familiar. A diferença fundamental é que em vez de JavaScript, escreve C#.&lt;/p>
&lt;p>Os componentes em Blazor são escritos em ficheiros &lt;code>.razor&lt;/code>. Um ficheiro &lt;code>.razor&lt;/code> mistura marcação HTML com código C# usando a sintaxe Razor que pode já conhecer das views do ASP.NET MVC. Aqui está como fica um componente simples:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/counter&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Contador&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;Contagem atual: @currentCount&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button @onclick=&amp;#34;IncrementCount&amp;#34;&amp;gt;Clique aqui&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private int currentCount = 0;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void IncrementCount()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentCount++;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Não há JavaScript à vista. A diretiva &lt;code>@onclick&lt;/code> associa o clique do botão a um método C#. A expressão &lt;code>@currentCount&lt;/code> renderiza o valor atual. Quando o estado muda, o Blazor determina o que atualizar no DOM.&lt;/p>
&lt;p>Esse é o núcleo. Tudo o resto — routing, injeção de dependências, formulários, chamadas HTTP — é construído sobre este modelo de componentes.&lt;/p>
&lt;hr>
&lt;h2 id="os-modelos-de-renderização">Os modelos de renderização&lt;/h2>
&lt;p>É aqui que reside muita confusão, por isso sejamos precisos. &amp;ldquo;Blazor&amp;rdquo; hoje refere-se a uma família de modos de renderização, não a um único modelo de implantação. Compreender a diferença importa porque afeta a performance, os requisitos de infraestrutura e o que os seus componentes podem ou não fazer.&lt;/p>
&lt;h3 id="ssr-estático">SSR Estático&lt;/h3>
&lt;p>O modo mais simples. Os seus componentes Razor renderizam no servidor para HTML e esse HTML é enviado ao navegador. Não há conexão persistente, sem WebAssembly e sem interatividade do lado do cliente por padrão. É essencialmente o que o Razor Pages faz, mas usando o modelo de componentes.&lt;/p>
&lt;p>Use para páginas com muito conteúdo, landing pages, qualquer coisa que não precise de interatividade em tempo real.&lt;/p>
&lt;h3 id="servidor-interativo">Servidor Interativo&lt;/h3>
&lt;p>O código do seu componente corre no servidor. Uma conexão WebSocket SignalR é estabelecida entre o navegador e o servidor. Quando clica num botão ou escreve num campo, o evento viaja pelo WebSocket até ao servidor, o seu C# executa, o diff é calculado e as atualizações do DOM são enviadas de volta ao navegador.&lt;/p>
&lt;p>&lt;strong>Vantagens:&lt;/strong> Acesso total aos recursos do servidor (bases de dados, sistema de ficheiros, segredos). Carregamento inicial rápido. Tamanho de download reduzido. Funciona em navegadores sem suporte a WebAssembly.&lt;/p>
&lt;p>&lt;strong>Desvantagens:&lt;/strong> Cada interação do utilizador requer uma viagem de ida e volta ao servidor, o que adiciona latência. Os recursos do servidor escalam com o número de conexões ativas. A app degrada se a conexão cair.&lt;/p>
&lt;p>Use para aplicações empresariais, ferramentas internas, apps onde o acesso a dados do servidor importa mais do que a capacidade offline.&lt;/p>
&lt;h3 id="webassembly-interativo">WebAssembly Interativo&lt;/h3>
&lt;p>O runtime .NET é descarregado para o navegador e a sua app corre completamente no cliente. Após o download inicial, a app funciona completamente offline e cada interação é imediata — sem viagens ao servidor para lógica de interface.&lt;/p>
&lt;p>&lt;strong>Vantagens:&lt;/strong> Execução verdadeiramente do lado do cliente. Funciona offline. Carga do servidor reduzida após a app estar carregada.&lt;/p>
&lt;p>&lt;strong>Desvantagens:&lt;/strong> Download inicial maior (runtime .NET + a sua app). Primeiro carregamento mais lento. Precisa de uma API para aceder a dados do servidor.&lt;/p>
&lt;p>Use para apps com capacidade offline, ferramentas que precisam de reatividade imediata, cenários onde o processamento do servidor não é necessário para a interface.&lt;/p>
&lt;h3 id="modo-auto">Modo Auto&lt;/h3>
&lt;p>Introduzido no .NET 8. A app começa no modo Servidor Interativo (carregamento inicial rápido, sem esperar pelo download do WebAssembly). Assim que os ficheiros WebAssembly tiverem sido descarregados em segundo plano, as visitas subsequentes mudam automaticamente para o modo WebAssembly.&lt;/p>
&lt;p>Isto dá-lhe o carregamento inicial rápido do modo Servidor e eventualmente a execução completa do lado do cliente do WebAssembly. É o modelo mais complexo de compreender, mas é um padrão prático para muitas apps.&lt;/p>
&lt;h3 id="misturar-modos-na-mesma-app">Misturar modos na mesma app&lt;/h3>
&lt;p>No .NET 8 e posteriores, não está limitado a um único modo de renderização para toda a app. Uma landing page pode ser SSR Estático; o dashboard autenticado pode ser Servidor Interativo; um widget de visualização de dados pode ser WebAssembly Interativo. O framework gere as transições.&lt;/p>
&lt;p>Nesta série começaremos com o Servidor Interativo por ser o mais simples de colocar a funcionar e ter o modelo mental mais direto. Exploraremos os outros modos à medida que avançamos.&lt;/p>
&lt;hr>
&lt;h2 id="como-o-blazor-se-compara-com-frameworks-javascript">Como o Blazor se compara com frameworks JavaScript&lt;/h2>
&lt;p>Se desenvolveu com React, Angular ou Vue, esta é provavelmente a comparação que lhe interessa.&lt;/p>
&lt;p>&lt;strong>As semelhanças são reais.&lt;/strong> O modelo de componentes do Blazor é deliberadamente semelhante ao do React. Tem props (Parâmetros no Blazor), estado local (campos no bloco &lt;code>@code&lt;/code>), hooks de ciclo de vida e o mesmo padrão de comunicação dados para baixo / eventos para cima. Se conhece React, vai sentir-se em casa em poucas horas.&lt;/p>
&lt;p>&lt;strong>A diferença fundamental é a linguagem.&lt;/strong> No Blazor escreve C#. A sua lógica de negócio, regras de validação e modelos de dados podem todos ser partilhados entre o seu frontend Blazor e o seu backend ASP.NET Core. Sem duplicar uma classe &lt;code>User&lt;/code> em TypeScript quando já a tem em C#.&lt;/p>
&lt;p>&lt;strong>A lacuna do ecossistema é real mas está a fechar.&lt;/strong> O ecossistema npm é enorme. O ecossistema NuGet para componentes de UI é menor, embora tenha crescido substancialmente. Para uma biblioteca de gráficos específica ou um widget de drag-and-drop, o JavaScript ainda tem mais opções. Mas para a maioria das aplicações empresariais, o que está disponível no mundo .NET é mais do que suficiente.&lt;/p>
&lt;p>&lt;strong>A interoperabilidade JavaScript existe quando precisa dela.&lt;/strong> O Blazor permite chamar JavaScript a partir de C# e C# a partir de JavaScript. Para APIs do navegador sem wrapper .NET, ou quando quer usar uma biblioteca JS existente, o interop está disponível. Adiciona uma camada mas não é doloroso.&lt;/p>
&lt;p>&lt;strong>A resposta honesta:&lt;/strong> se a sua equipa é totalmente de developers JavaScript a construir um produto público onde SEO, performance e o ecossistema npm são críticos, fique com JavaScript. Se a sua equipa é de developers .NET, se está a construir aplicações internas ou empresariais, ou se a partilha de código entre frontend e backend lhe importa, o Blazor é uma escolha convincente.&lt;/p>
&lt;hr>
&lt;h2 id="o-que-precisa-para-acompanhar-a-série">O que precisa para acompanhar a série&lt;/h2>
&lt;p>Para o resto desta série vai precisar de:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>.NET 9 SDK&lt;/strong> — descarregue em &lt;a href="https://dotnet.microsoft.com/download">dot.net&lt;/a>&lt;/li>
&lt;li>&lt;strong>Um IDE&lt;/strong> — Visual Studio 2022 (a edição Community é gratuita), VS Code com a extensão C# Dev Kit, ou JetBrains Rider&lt;/li>
&lt;li>Conforto básico com C# — classes, propriedades, interfaces, async/await&lt;/li>
&lt;/ul>
&lt;p>Só isso. Sem Node, sem npm, sem webpack.&lt;/p>
&lt;hr>
&lt;h2 id="a-seguir">A seguir&lt;/h2>
&lt;p>No Capítulo 2 vamos montar a sua primeira app Blazor, percorrer a estrutura do projeto e garantir que consegue executá-la localmente. No final terá uma app a funcionar e uma imagem clara do que cada ficheiro está a fazer.&lt;/p>
&lt;p>Até lá.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category><category>Series</category></item><item><title>Blazor do Zero: Uma Nova Série</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-from-scratch-intro/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-from-scratch-intro/</guid><description>Início de uma longa série onde percorremos o Blazor desde os fundamentos — sem atalhos, sem rodeios, apenas explicações claras e código real.</description><content:encoded>&lt;p>Há algum tempo que escrevo sobre Blazor — ciclos de vida de componentes, CSS isolado, modelos de interatividade, autenticação. Esses posts foram úteis individualmente, mas sempre senti que faltava uma base. Eles assumem que você já sabe o que é Blazor, por que ele existe e como se encaixa no ecossistema .NET mais amplo. Nem todo mundo sabe isso, e isso é completamente normal.&lt;/p>
&lt;p>Por isso, estou começando algo novo: &lt;strong>Blazor do Zero&lt;/strong>. Uma série de verdade, construída desde os fundamentos, pensada para desenvolvedores que querem realmente entender o que estão construindo — não apenas copiar e colar até funcionar.&lt;/p>
&lt;h2 id="para-quem-é-esta-série">Para Quem É Esta Série&lt;/h2>
&lt;p>Esta série é para você se:&lt;/p>
&lt;ul>
&lt;li>Você é um desenvolvedor .NET que já ouviu falar em Blazor mas nunca teve o tempo ou o ponto de partida certo para se aprofundar.&lt;/li>
&lt;li>Você já experimentou Blazor, fez funcionar, mas sente que está adivinhando &lt;em>por que&lt;/em> as coisas funcionam.&lt;/li>
&lt;li>Você vem do mundo JavaScript/React/Angular e quer entender como é a resposta da Microsoft para o frontend moderno.&lt;/li>
&lt;li>Você quer um recurso único e coerente em vez de documentação espalhada e posts de blog dispersos.&lt;/li>
&lt;/ul>
&lt;p>Você não precisa ser um desenvolvedor sênior. Você precisa, sim, se sentir confortável com os fundamentos do C# — classes, interfaces, async/await. Se você consegue escrever uma API CRUD simples em ASP.NET Core, você está pronto.&lt;/p>
&lt;h2 id="o-que-vamos-abordar">O Que Vamos Abordar&lt;/h2>
&lt;p>Aqui está um roteiro aproximado do que tenho planejado. Alguns tópicos se expandirão em vários posts conforme necessário:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>O que é Blazor?&lt;/strong> — Modelos de hospedagem, histórico, comparação com o desenvolvimento web tradicional&lt;/li>
&lt;li>&lt;strong>Seu primeiro app Blazor&lt;/strong> — Scaffolding, estrutura do projeto, execução local&lt;/li>
&lt;li>&lt;strong>Componentes&lt;/strong> — O bloco fundamental de qualquer interface em Blazor&lt;/li>
&lt;li>&lt;strong>Data binding e eventos&lt;/strong> — Tornando sua interface reativa&lt;/li>
&lt;li>&lt;strong>Comunicação entre componentes&lt;/strong> — Parâmetros, EventCallbacks, valores em cascata&lt;/li>
&lt;li>&lt;strong>Roteamento e navegação&lt;/strong> — Como o Blazor lida com URLs e transições de página&lt;/li>
&lt;li>&lt;strong>Injeção de dependência&lt;/strong> — Serviços, escopos e o contêiner DI no Blazor&lt;/li>
&lt;li>&lt;strong>Formulários e validação&lt;/strong> — EditForm, DataAnnotations, validadores personalizados&lt;/li>
&lt;li>&lt;strong>HTTP e dados externos&lt;/strong> — Chamando APIs a partir do seu app Blazor&lt;/li>
&lt;li>&lt;strong>Autenticação e autorização&lt;/strong> — Protegendo seu app da forma correta&lt;/li>
&lt;li>&lt;strong>Interop com JavaScript&lt;/strong> — Quando você precisa acessar o navegador diretamente&lt;/li>
&lt;li>&lt;strong>Desempenho e otimização&lt;/strong> — Virtualização, lazy loading, estratégias de renderização&lt;/li>
&lt;li>&lt;strong>Testando componentes Blazor&lt;/strong> — bUnit e como é um bom teste&lt;/li>
&lt;li>&lt;strong>Deploy&lt;/strong> — Publicando no Azure, IIS e hosts estáticos&lt;/li>
&lt;/ol>
&lt;p>Esta lista vai evoluir. Alguns tópicos se dividirão em vários posts; outros podem ser fundidos. Vou atualizar este post à medida que a série avança e adicionar links para cada entrada conforme forem publicadas.&lt;/p>
&lt;h2 id="por-que-uma-série-por-que-agora">Por Que Uma Série, Por Que Agora&lt;/h2>
&lt;p>O Blazor amadureceu muito. Com o .NET 8 e 9, o modelo de renderização foi significativamente reformulado — SSR estático, streaming rendering, Server interativo, WebAssembly interativo e o modo Auto convivem agora sob o mesmo teto. É um framework genuinamente interessante e capaz, mas o salto em complexidade torna a experiência inicial bastante desorientadora.&lt;/p>
&lt;p>Quero construir um recurso que te encontre onde você está e te guie por tudo de forma sistemática. Não é um substituto para a documentação oficial — ela é boa e você deve lê-la — mas um companheiro que explica o &lt;em>por quê&lt;/em> por trás do &lt;em>o quê&lt;/em>.&lt;/p>
&lt;h2 id="como-acompanhar-a-série">Como Acompanhar a Série&lt;/h2>
&lt;p>Cada post da série será suficientemente independente para ser lido sozinho, mas eles também vão se apoiar mutuamente. Se você está começando do zero, recomendo seguir a ordem. Se você está entrando para preencher uma lacuna específica, tudo bem também — vou linkar posts de pré-requisito onde for relevante.&lt;/p>
&lt;p>O código de cada post estará disponível no GitHub. Vou compartilhar os links ao longo do caminho.&lt;/p>
&lt;p>Até o próximo post.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category><category>Series</category></item><item><title>Tipos de união C#: Uniões discriminadas estão finalmente chegando</title><link>https://emimontesdeoca.github.io/pt/posts/csharp-union-types/</link><pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/csharp-union-types/</guid><description>Um mergulho profundo nos próximos tipos de união discriminados em C# — o que são, como funcionam e por que mudarão a forma como você modela seus domínios.</description><content:encoded>&lt;p>Se você escreve C# há algum tempo, há uma boa chance de você ter se deparado com um obstáculo ao tentar modelar algo que pode ser &amp;ldquo;uma entre várias coisas&amp;rdquo;. Talvez você precisasse de um método para retornar um valor de sucesso ou um erro. Talvez você estivesse construindo um sistema de pagamento que lida com cartões de crédito, transferências bancárias e carteiras digitais – cada uma com dados totalmente diferentes. Ou talvez você apenas olhou para F# ou Rust e pensou: “Por que não posso ter isso em C#?”&lt;/p>
&lt;p>A espera está quase no fim. &lt;strong>Sindicatos discriminados estão chegando ao C#.&lt;/strong>&lt;/p>
&lt;p>Este tem sido um dos recursos linguísticos mais solicitados há anos, com discussões na comunidade que remontam a 2017 e antes. A equipe de design da linguagem C# está trabalhando em uma proposta que introduz uma palavra-chave &lt;code>union&lt;/code> para definir hierarquias de tipo fechado com correspondência exaustiva de padrões. Neste post, quero explicar o que são os sindicatos discriminados, por que eles são tão importantes, como os falsificamos até agora e como realmente é a sintaxe proposta - com exemplos de código reais para cada um.&lt;/p>
&lt;p>Uma observação rápida antes de começarmos: no momento em que este livro foi escrito, o recurso de tipos de união ainda estava em fase de proposta e visualização. A sintaxe e o comportamento que descrevo aqui baseiam-se nos documentos de design mais recentes disponíveis publicamente e nas discussões da equipe de design da linguagem C#. As coisas podem mudar antes do lançamento final. Serei claro sobre o que está confirmado versus o que ainda está em discussão.&lt;/p>
&lt;h2 id="o-que-são-sindicatos-discriminados">O que são sindicatos discriminados?&lt;/h2>
&lt;p>Em sua essência, uma união discriminada (às vezes chamada de &amp;ldquo;união marcada&amp;rdquo; ou &amp;ldquo;tipo de soma&amp;rdquo;) é um tipo que pode conter um de um conjunto fixo de valores possíveis, onde cada variante pode transportar dados diferentes. A parte “discriminada” significa que o tempo de execução sempre sabe qual variante é – há uma tag que a identifica.&lt;/p>
&lt;p>Pense nisso como um &lt;code>enum&lt;/code>, mas onde cada membro pode carregar sua própria carga de dados.&lt;/p>
&lt;p>Se você já usou outras linguagens, provavelmente já viu esse conceito antes:&lt;/p>
&lt;p>&lt;strong>F#&lt;/strong> teve sindicatos discriminados desde o primeiro dia:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fsharp" data-lang="fsharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">type&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Shape&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> Circle &lt;span style="color:#ff7b72">of&lt;/span> radius&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> Rectangle &lt;span style="color:#ff7b72">of&lt;/span> width&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">*&lt;/span> height&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> Triangle &lt;span style="color:#ff7b72">of&lt;/span> &lt;span style="color:#ff7b72">base&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">*&lt;/span> height&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Rust&lt;/strong> usa &lt;code>enum&lt;/code> para a mesma ideia:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-rust" data-lang="rust">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">enum&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Shape&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>Circle&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681"> &lt;/span>radius: &lt;span style="color:#ff7b72">f64&lt;/span> },&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>Rectangle&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681"> &lt;/span>width: &lt;span style="color:#ff7b72">f64&lt;/span>,&lt;span style="color:#6e7681"> &lt;/span>height: &lt;span style="color:#ff7b72">f64&lt;/span> },&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>Triangle&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681"> &lt;/span>base: &lt;span style="color:#ff7b72">f64&lt;/span>,&lt;span style="color:#6e7681"> &lt;/span>height: &lt;span style="color:#ff7b72">f64&lt;/span> },&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>}&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>TypeScript&lt;/strong> consegue algo semelhante com uniões marcadas:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-typescript" data-lang="typescript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">type&lt;/span> Shape &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> { kind&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;circle&amp;#34;&lt;/span>; radius: &lt;span style="color:#ff7b72">number&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> { kind&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;rectangle&amp;#34;&lt;/span>; width: &lt;span style="color:#ff7b72">number&lt;/span>; height: &lt;span style="color:#ff7b72">number&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> { kind&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;triangle&amp;#34;&lt;/span>; base: &lt;span style="color:#ff7b72">number&lt;/span>; height: &lt;span style="color:#ff7b72">number&lt;/span> };
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Em todos esses casos, o compilador conhece todas as variantes possíveis e pode garantir que você lide com todas elas. Esse é o superpoder: &lt;strong>verificação de exaustividade&lt;/strong>. Se você adicionar uma nova variante, o compilador informará onde você esqueceu de manipulá-la.&lt;/p>
&lt;p>C# nunca teve uma maneira de primeira classe de expressar isso. Até agora.&lt;/p>
&lt;h2 id="como-simulamos-sindicatos-hoje">Como simulamos sindicatos hoje&lt;/h2>
&lt;p>Ao longo dos anos, a comunidade C# apresentou diversas soluções alternativas, cada uma com suas próprias vantagens e desvantagens. Deixe-me examinar as abordagens mais comuns.&lt;/p>
&lt;h3 id="registros-abstratos-com-herança">Registros abstratos com herança&lt;/h3>
&lt;p>A solução alternativa mais idiomática no C# moderno é usar registros abstratos com tipos derivados selados:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Shape&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">sealed&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Circle&lt;/span>(&lt;span style="color:#ff7b72">double&lt;/span> Radius) : Shape;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">sealed&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Rectangle&lt;/span>(&lt;span style="color:#ff7b72">double&lt;/span> Width, &lt;span style="color:#ff7b72">double&lt;/span> Height) : Shape;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">sealed&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Triangle&lt;/span>(&lt;span style="color:#ff7b72">double&lt;/span> Base, &lt;span style="color:#ff7b72">double&lt;/span> Height) : Shape;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> Shape() { } &lt;span style="color:#8b949e;font-style:italic">// Prevent external inheritance&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso funciona razoavelmente bem. Você obtém imutabilidade, igualdade de valores e pode usar correspondência de padrões:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">double&lt;/span> Area(Shape shape) =&amp;gt; shape &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Shape.Circle c =&amp;gt; Math.PI * c.Radius * c.Radius,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Shape.Rectangle r =&amp;gt; r.Width * r.Height,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Shape.Triangle t =&amp;gt; &lt;span style="color:#a5d6ff">0.5&lt;/span> * t.Base * t.Height,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> InvalidOperationException(&lt;span style="color:#a5d6ff">&amp;#34;Unknown shape&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Mas existem desvantagens significativas. O compilador não sabe que a hierarquia está fechada, então você sempre precisa daquele braço de descarte &lt;span style="color:#f85149">`&lt;/span>_&lt;span style="color:#f85149">`&lt;/span> ou receberá um aviso. Se você adicionar uma nova variante, o compilador não informará sobre todos os lugares em que você se esqueceu de manipulá-la &lt;span style="color:#f85149">—&lt;/span> o descarte a engole silenciosamente. Isso anula completamente o propósito.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> A Biblioteca OneOf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Outra abordagem popular &lt;span style="color:#f85149">é&lt;/span> o pacote [OneOf](https:&lt;span style="color:#8b949e;font-style:italic">//github.com/mcintyre321/OneOf) NuGet:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> OneOf&amp;lt;Success&amp;lt;Order&amp;gt;, NotFound, ValidationError&amp;gt; ProcessOrder(OrderRequest request)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Usage&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = ProcessOrder(request);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>result.Switch(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> success =&amp;gt; Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Order {success.Value.Id} processed&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> notFound =&amp;gt; Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;Order not found&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> error =&amp;gt; Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Validation failed: {error.Message}&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>OneOf fornece verificação exaustiva em tempo de compilação por meio de seus parâmetros de tipo genérico, o que é ótimo. Mas depende da correspondência posicional (primeiro tipo, segundo tipo, etc.), as assinaturas genéricas tornam-se difíceis de manejar rapidamente e não se integram à correspondência de padrões da linguagem. É um hack inteligente, mas ainda é um hack.&lt;/p>
&lt;h3 id="enum-manual--padrão-de-dados">Enum manual + padrão de dados&lt;/h3>
&lt;p>Alguns desenvolvedores seguem o caminho clássico com uma tag enum e um contêiner de dados:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">enum&lt;/span> PaymentType { CreditCard, BankTransfer, DigitalWallet }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Payment&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> PaymentType Type { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">init&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CreditCardInfo? CreditCard { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">init&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> BankTransferInfo? BankTransfer { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">init&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DigitalWalletInfo? DigitalWallet { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">init&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isto é frágil. Nada impede você de definir &lt;code>Type&lt;/code> como &lt;code>CreditCard&lt;/code>, mas preencher a propriedade &lt;code>BankTransfer&lt;/code>. O compilador não pode ajudá-lo e você acaba com erros de tempo de execução e verificações de nulos em todos os lugares. É a abordagem &amp;ldquo;stringly-typed&amp;rdquo; para modelagem de tipos e não é escalonável.&lt;/p>
&lt;p>Todas essas abordagens compartilham um problema fundamental: &lt;strong>elas estão lutando contra a linguagem em vez de trabalhar com ela.&lt;/strong> O compilador não consegue raciocinar sobre o conjunto fechado de possibilidades, então você perde a propriedade mais valiosa das uniões discriminadas — a verificação exaustiva.&lt;/p>
&lt;h2 id="a-proposta-c-a-palavra-chave-union">A proposta C#: a palavra-chave &lt;code>union&lt;/code>&lt;/h2>
&lt;p>A proposta da equipe da linguagem C# introduz uma palavra-chave &lt;code>union&lt;/code> dedicada que define um conjunto fechado de membros nomeados, cada um carregando dados opcionalmente. Aqui está a sintaxe básica conforme proposta:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Shape
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Circle(&lt;span style="color:#ff7b72">double&lt;/span> Radius),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Rectangle(&lt;span style="color:#ff7b72">double&lt;/span> Width, &lt;span style="color:#ff7b72">double&lt;/span> Height),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Triangle(&lt;span style="color:#ff7b72">double&lt;/span> Base, &lt;span style="color:#ff7b72">double&lt;/span> Height)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É isso. Limpo, conciso e imediatamente legível. Cada membro dentro da união define uma variante distinta com seus próprios dados. O compilador sabe que &lt;code>Shape&lt;/code> só pode ser uma dessas três coisas.&lt;/p>
&lt;p>Nos bastidores, o compilador gera uma hierarquia de tipos selada — semelhante ao que você escreveria à mão com registros abstratos, mas com total consciência do compilador sobre a natureza fechada do tipo. Isso significa que o compilador pode impor exaustividade na correspondência de padrões, que é o principal benefício.&lt;/p>
&lt;h3 id="membros-somente-de-valor">Membros somente de valor&lt;/h3>
&lt;p>Os membros do sindicato não precisam transportar dados. Você pode misturar membros que transportam dados com membros de valor simples:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Option&amp;lt;T&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Some(T Value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> None
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Este é o tipo clássico &lt;code>Option&lt;/code>/&lt;code>Maybe&lt;/code> que os programadores funcionais vêm pedindo em C# há anos. &lt;code>None&lt;/code> não carrega dados — é apenas uma tag.&lt;/p>
&lt;h3 id="uniões-genéricas">Uniões Genéricas&lt;/h3>
&lt;p>Como você pode ver no exemplo &lt;code>Option&amp;lt;T&amp;gt;&lt;/code> acima, os sindicatos apoiam os genéricos. Aqui está um exemplo mais envolvente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Result&amp;lt;T, E&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Ok(T Value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(E Err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso abre todo um estilo de tratamento de erros que não depende de exceções para casos de falha esperados – algo que tem sido uma prática padrão em Rust e em linguagens funcionais há anos.&lt;/p>
&lt;h3 id="uniões-com-métodos">Uniões com Métodos&lt;/h3>
&lt;p>A proposta também permite que os sindicatos tenham métodos, propriedades computadas e implementem interfaces, assim como qualquer outro tipo:```csharp
union Shape
{
Circle(double Radius),
Rectangle(double Width, double Height),
Triangle(double Base, double Height);&lt;/p>
&lt;pre>&lt;code>public double Area =&amp;gt; this switch
{
Circle(var r) =&amp;gt; Math.PI * r * r,
Rectangle(var w, var h) =&amp;gt; w * h,
Triangle(var b, var h) =&amp;gt; 0.5 * b * h
};
public double Perimeter =&amp;gt; this switch
{
Circle(var r) =&amp;gt; 2 * Math.PI * r,
Rectangle(var w, var h) =&amp;gt; 2 * (w + h),
Triangle(var b, var h) =&amp;gt; b + h + Math.Sqrt(b * b + h * h)
};
&lt;/code>&lt;/pre>
&lt;p>}&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>Observe&lt;span style="color:#6e7681"> &lt;/span>como&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>expressão&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>switch&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>dentro&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>Area&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>e&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>Perimeter&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>não&lt;span style="color:#6e7681"> &lt;/span>precisa&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>um&lt;span style="color:#6e7681"> &lt;/span>braço&lt;span style="color:#6e7681"> &lt;/span>padrão.&lt;span style="color:#6e7681"> &lt;/span>O&lt;span style="color:#6e7681"> &lt;/span>compilador&lt;span style="color:#6e7681"> &lt;/span>sabe&lt;span style="color:#6e7681"> &lt;/span>que&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>união&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">é&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>exaustiva&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">—&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>existem&lt;span style="color:#6e7681"> &lt;/span>apenas&lt;span style="color:#6e7681"> &lt;/span>três&lt;span style="color:#6e7681"> &lt;/span>variantes&lt;span style="color:#6e7681"> &lt;/span>e&lt;span style="color:#6e7681"> &lt;/span>todas&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">as&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>três&lt;span style="color:#6e7681"> &lt;/span>são&lt;span style="color:#6e7681"> &lt;/span>tratadas.&lt;span style="color:#6e7681"> &lt;/span>Se&lt;span style="color:#6e7681"> &lt;/span>você&lt;span style="color:#6e7681"> &lt;/span>adicionar&lt;span style="color:#6e7681"> &lt;/span>uma&lt;span style="color:#6e7681"> &lt;/span>quarta&lt;span style="color:#6e7681"> &lt;/span>variante&lt;span style="color:#6e7681"> &lt;/span>posteriormente,&lt;span style="color:#6e7681"> &lt;/span>o&lt;span style="color:#6e7681"> &lt;/span>compilador&lt;span style="color:#6e7681"> &lt;/span>sinalizará&lt;span style="color:#6e7681"> &lt;/span>todos&lt;span style="color:#6e7681"> &lt;/span>os&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>switch&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>que&lt;span style="color:#6e7681"> &lt;/span>não&lt;span style="color:#6e7681"> &lt;/span>lidam&lt;span style="color:#6e7681"> &lt;/span>com&lt;span style="color:#6e7681"> &lt;/span>ela.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">## Integração de correspondência de padrões
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>A&lt;span style="color:#6e7681"> &lt;/span>correspondência&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>padrões&lt;span style="color:#6e7681"> &lt;/span>tem&lt;span style="color:#6e7681"> &lt;/span>evoluído&lt;span style="color:#6e7681"> &lt;/span>em&lt;span style="color:#6e7681"> &lt;/span>C&lt;span style="color:#8b949e;font-style:italic"># desde a versão 7.0, e os tipos de união são projetados para serem cidadãos de primeira classe desse sistema.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">### Expressões de troca exaustivas
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>O&lt;span style="color:#6e7681"> &lt;/span>recurso&lt;span style="color:#6e7681"> &lt;/span>mais&lt;span style="color:#6e7681"> &lt;/span>impactante&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">é&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>verificação&lt;span style="color:#6e7681"> &lt;/span>exaustiva&lt;span style="color:#6e7681"> &lt;/span>dos&lt;span style="color:#6e7681"> &lt;/span>switches.&lt;span style="color:#6e7681"> &lt;/span>Com&lt;span style="color:#6e7681"> &lt;/span>uniões,&lt;span style="color:#6e7681"> &lt;/span>o&lt;span style="color:#6e7681"> &lt;/span>compilador&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>conhece&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>todos&lt;span style="color:#6e7681"> &lt;/span>os&lt;span style="color:#6e7681"> &lt;/span>casos&lt;span style="color:#6e7681"> &lt;/span>possíveis:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">```&lt;/span>csharp&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>string&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">Describe&lt;/span>(Shape&lt;span style="color:#6e7681"> &lt;/span>shape)&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>shape&lt;span style="color:#6e7681"> &lt;/span>switch&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">{&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#d2a8ff;font-weight:bold">Circle&lt;/span>(var&lt;span style="color:#6e7681"> &lt;/span>r)&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">$&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;A circle with radius {r}&amp;#34;&lt;/span>,&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#d2a8ff;font-weight:bold">Rectangle&lt;/span>(var&lt;span style="color:#6e7681"> &lt;/span>w,&lt;span style="color:#6e7681"> &lt;/span>var&lt;span style="color:#6e7681"> &lt;/span>h)&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">$&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;A {w}x{h} rectangle&amp;#34;&lt;/span>,&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#d2a8ff;font-weight:bold">Triangle&lt;/span>(var&lt;span style="color:#6e7681"> &lt;/span>b,&lt;span style="color:#6e7681"> &lt;/span>var&lt;span style="color:#6e7681"> &lt;/span>h)&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">$&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;A triangle with base {b} and height {h}&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">}&lt;/span>;&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Sem braço de descarte. Não &lt;code>_ =&amp;gt; throw new NotImplementedException()&lt;/code>. Se você esquecer um caso, o compilador emitirá um erro, não um aviso. Esta é uma melhoria fundamental na segurança.&lt;/p>
&lt;h3 id="correspondência-de-padrões-aninhados">Correspondência de padrões aninhados&lt;/h3>
&lt;p>As uniões são compostas naturalmente com padrões aninhados:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Expr
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Literal(&lt;span style="color:#ff7b72">double&lt;/span> Value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Add(Expr Left, Expr Right),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Multiply(Expr Left, Expr Right),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Negate(Expr Inner)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">double&lt;/span> Evaluate(Expr expr) =&amp;gt; expr &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Literal(&lt;span style="color:#ff7b72">var&lt;/span> v) =&amp;gt; v,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Add(&lt;span style="color:#ff7b72">var&lt;/span> left, &lt;span style="color:#ff7b72">var&lt;/span> right) =&amp;gt; Evaluate(left) + Evaluate(right),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Multiply(&lt;span style="color:#ff7b72">var&lt;/span> left, &lt;span style="color:#ff7b72">var&lt;/span> right) =&amp;gt; Evaluate(left) * Evaluate(right),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Negate(&lt;span style="color:#ff7b72">var&lt;/span> inner) =&amp;gt; -Evaluate(inner)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esse tipo de estrutura de dados recursiva é extremamente comum em compiladores, interpretadores, mecanismos de regras e modelagem matemática. Hoje, em C#, você precisaria de uma hierarquia de classes profunda e do padrão de visitante. Com os sindicatos, o código é dramaticamente mais simples.&lt;/p>
&lt;h3 id="cláusulas-de-proteção">Cláusulas de proteção&lt;/h3>
&lt;p>A correspondência de padrões com uniões oferece suporte a guardas &lt;code>when&lt;/code> exatamente como seria de esperar:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> Classify(Shape shape) =&amp;gt; shape &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Circle(&lt;span style="color:#ff7b72">var&lt;/span> r) when r &amp;gt; &lt;span style="color:#a5d6ff">100&lt;/span> =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Large circle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Circle(&lt;span style="color:#ff7b72">var&lt;/span> r) when r &amp;gt; &lt;span style="color:#a5d6ff">10&lt;/span> =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Medium circle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Circle(_) =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Small circle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Rectangle(&lt;span style="color:#ff7b72">var&lt;/span> w, &lt;span style="color:#ff7b72">var&lt;/span> h) when w == h =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Square&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Rectangle _ =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Rectangle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Triangle _ =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Triangle&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="exemplos-práticos">Exemplos práticos&lt;/h2>
&lt;p>Deixe-me examinar alguns cenários do mundo real onde os tipos de união melhoram drasticamente o código.&lt;/p>
&lt;h3 id="o-padrão-de-resultado-substituindo-exceções-por-erros-esperados">O padrão de resultado: substituindo exceções por erros esperados&lt;/h3>
&lt;p>Um dos padrões mais comuns no desenvolvimento de aplicativos modernos é a representação de operações que podem ter êxito ou falhar sem usar exceções para o fluxo de controle. As exceções devem ser excepcionais – coisas como falhas de rede ou condições de falta de memória. Um erro de validação ou um resultado “não encontrado” é um resultado esperado, não uma exceção.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Result&amp;lt;T, E&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Ok(T Value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(E Err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>union OrderError
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NotFound(Guid OrderId),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> InsufficientStock(&lt;span style="color:#ff7b72">string&lt;/span> ProductId, &lt;span style="color:#ff7b72">int&lt;/span> Requested, &lt;span style="color:#ff7b72">int&lt;/span> Available),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PaymentDeclined(&lt;span style="color:#ff7b72">string&lt;/span> Reason),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ValidationFailed(IReadOnlyList&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; Errors)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Result&amp;lt;Order, OrderError&amp;gt; ProcessOrder(OrderRequest request)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!Validate(request, &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> errors))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ValidationFailed(errors);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> product = catalog.Find(request.ProductId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (product &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> NotFound(request.OrderId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (product.Stock &amp;lt; request.Quantity)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> InsufficientStock(request.ProductId, request.Quantity, product.Stock);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> paymentResult = paymentGateway.Charge(request.Payment);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!paymentResult.Success)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> PaymentDeclined(paymentResult.Message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> order = CreateOrder(request, product);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Ok(order);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O chamador é então forçado a lidar com todos os resultados possíveis:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = ProcessOrder(request);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> response = result &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Ok(&lt;span style="color:#ff7b72">var&lt;/span> order) =&amp;gt; Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/orders/{order.Id}&amp;#34;&lt;/span>, order),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(NotFound(&lt;span style="color:#ff7b72">var&lt;/span> id)) =&amp;gt; Results.NotFound(&lt;span style="color:#a5d6ff">$&amp;#34;Order {id} not found&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(InsufficientStock(&lt;span style="color:#ff7b72">var&lt;/span> pid, &lt;span style="color:#ff7b72">var&lt;/span> req, &lt;span style="color:#ff7b72">var&lt;/span> avail)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Results.Conflict(&lt;span style="color:#a5d6ff">$&amp;#34;Product {pid}: requested {req}, only {avail} available&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(PaymentDeclined(&lt;span style="color:#ff7b72">var&lt;/span> reason)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Results.UnprocessableEntity(&lt;span style="color:#a5d6ff">$&amp;#34;Payment declined: {reason}&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(ValidationFailed(&lt;span style="color:#ff7b72">var&lt;/span> errors)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Errors = errors })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Sem blocos try-catch, sem tipos de exceção esquecidos, sem surpresas em tempo de execução. Cada modo de falha é visível na assinatura de tipo e aplicado pelo compilador. Esta é uma grande melhoria para a confiabilidade da API.&lt;/p>
&lt;h3 id="modelagem-de-domínio-tipos-de-pagamento">Modelagem de Domínio: Tipos de Pagamento&lt;/h3>
&lt;p>Aqui está um exemplo de modelagem de domínio do mundo real – lidando com diferentes métodos de pagamento em um sistema de comércio eletrônico:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union PaymentMethod
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CreditCard(&lt;span style="color:#ff7b72">string&lt;/span> CardNumber, &lt;span style="color:#ff7b72">string&lt;/span> Expiry, &lt;span style="color:#ff7b72">string&lt;/span> Cvv),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BankTransfer(&lt;span style="color:#ff7b72">string&lt;/span> Iban, &lt;span style="color:#ff7b72">string&lt;/span> Bic),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DigitalWallet(&lt;span style="color:#ff7b72">string&lt;/span> Provider, &lt;span style="color:#ff7b72">string&lt;/span> Token),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CashOnDelivery
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">decimal&lt;/span> CalculateProcessingFee(PaymentMethod method, &lt;span style="color:#ff7b72">decimal&lt;/span> amount) =&amp;gt; method &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CreditCard _ =&amp;gt; amount * &lt;span style="color:#a5d6ff">0.029&lt;/span>m + &lt;span style="color:#a5d6ff">0.30&lt;/span>m,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BankTransfer _ =&amp;gt; &lt;span style="color:#a5d6ff">1.50&lt;/span>m,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DigitalWallet(provider: &lt;span style="color:#a5d6ff">&amp;#34;PayPal&amp;#34;&lt;/span>, _) =&amp;gt; amount * &lt;span style="color:#a5d6ff">0.034&lt;/span>m + &lt;span style="color:#a5d6ff">0.35&lt;/span>m,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DigitalWallet _ =&amp;gt; amount * &lt;span style="color:#a5d6ff">0.025&lt;/span>m,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CashOnDelivery =&amp;gt; &lt;span style="color:#a5d6ff">4.99&lt;/span>m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> FormatForReceipt(PaymentMethod method) =&amp;gt; method &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CreditCard(&lt;span style="color:#ff7b72">var&lt;/span> num, _, _) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Credit Card ending in {num[^4..]}&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BankTransfer(&lt;span style="color:#ff7b72">var&lt;/span> iban, _) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Bank Transfer ({iban[..4]}****)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DigitalWallet(&lt;span style="color:#ff7b72">var&lt;/span> provider, _) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Digital Wallet ({provider})&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CashOnDelivery =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Cash on Delivery&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Compare isso com a abordagem atual, onde você teria uma interface ou classe abstrata com cinco implementações diferentes espalhadas por cinco arquivos, possivelmente com um padrão de visitante no topo. A abordagem sindical mantém a definição dos dados e as operações juntas, legíveis e verificadas exaustivamente.&lt;/p>
&lt;h3 id="máquinas-de-estado">Máquinas de Estado&lt;/h3>
&lt;p>As máquinas de estado estão por toda parte no software – processamento de pedidos, mecanismos de fluxo de trabalho, gerenciamento de conexões, estado da interface do usuário. Os sindicatos os tornam explícitos e seguros:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union ConnectionState
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Disconnected,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Connecting(&lt;span style="color:#ff7b72">string&lt;/span> Host, &lt;span style="color:#ff7b72">int&lt;/span> Port, DateTime StartedAt),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Connected(&lt;span style="color:#ff7b72">string&lt;/span> Host, &lt;span style="color:#ff7b72">int&lt;/span> Port, TcpClient Client),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Reconnecting(&lt;span style="color:#ff7b72">string&lt;/span> Host, &lt;span style="color:#ff7b72">int&lt;/span> Port, &lt;span style="color:#ff7b72">int&lt;/span> Attempt, TimeSpan Delay),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Failed(&lt;span style="color:#ff7b72">string&lt;/span> Host, Exception Error)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ConnectionState HandleEvent(ConnectionState state, ConnectionEvent evt) =&amp;gt; (state, evt) &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Disconnected, Connect(&lt;span style="color:#ff7b72">var&lt;/span> host, &lt;span style="color:#ff7b72">var&lt;/span> port)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Connecting(host, port, DateTime.UtcNow),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Connecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, _), ConnectionSucceeded(&lt;span style="color:#ff7b72">var&lt;/span> client)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Connected(h, p, client),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Connecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, _), ConnectionFailed(&lt;span style="color:#ff7b72">var&lt;/span> ex)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Reconnecting(h, p, Attempt: &lt;span style="color:#a5d6ff">1&lt;/span>, Delay: TimeSpan.FromSeconds(&lt;span style="color:#a5d6ff">1&lt;/span>)),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Reconnecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, &lt;span style="color:#ff7b72">var&lt;/span> attempt, _), ConnectionSucceeded(&lt;span style="color:#ff7b72">var&lt;/span> client)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Connected(h, p, client),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Reconnecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, &lt;span style="color:#ff7b72">var&lt;/span> attempt, _), ConnectionFailed(&lt;span style="color:#ff7b72">var&lt;/span> ex)) when attempt &amp;gt;= &lt;span style="color:#a5d6ff">5&lt;/span> =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Failed(h, ex),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Reconnecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, &lt;span style="color:#ff7b72">var&lt;/span> attempt, _), ConnectionFailed(_)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Reconnecting(h, p, attempt + &lt;span style="color:#a5d6ff">1&lt;/span>, TimeSpan.FromSeconds(Math.Pow(&lt;span style="color:#a5d6ff">2&lt;/span>, attempt))),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Connected(_, _, &lt;span style="color:#ff7b72">var&lt;/span> client), Disconnect) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Disconnected,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; state &lt;span style="color:#8b949e;font-style:italic">// Ignore events that don&amp;#39;t apply to current state&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Cada estado carrega exatamente os dados relevantes para esse estado. Você não pode acessar acidentalmente um &lt;code>TcpClient&lt;/code> quando estiver no estado &lt;code>Connecting&lt;/code> porque ele não existe nessa variante. O sistema de tipos impõe os invariantes da máquina de estados.&lt;/p>
&lt;h2 id="considerações-sobre-serialização-e-interoperabilidadeuma-das-questões-práticas-que-surgem-imediatamente-com-os-tipos-de-união-é-como-serializá-los-se-você-estiver-criando-apis-ou-armazenando-dados-precisará-da-serialização-json-para-funcionar-corretamente">Considerações sobre serialização e interoperabilidadeUma das questões práticas que surgem imediatamente com os tipos de união é: como serializá-los? Se você estiver criando APIs ou armazenando dados, precisará da serialização JSON para funcionar corretamente.&lt;/h2>
&lt;p>A equipe de design tem discutido o suporte integrado &lt;code>System.Text.Json&lt;/code> para tipos de união. A abordagem esperada envolve a serialização com uma propriedade discriminadora:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;$type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Circle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;radius&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">5.0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;$type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Rectangle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;width&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">10.0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;height&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">20.0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso é consistente com o suporte de serialização polimórfica existente introduzido no .NET 7 com atributos &lt;code>JsonDerivedType&lt;/code>. A expectativa é que os sindicatos funcionem com &lt;code>System.Text.Json&lt;/code> prontos para uso, usando o nome da variante como discriminador de tipo por padrão.&lt;/p>
&lt;p>Para o Entity Framework Core, a abordagem provável é armazenar valores de união usando uma coluna discriminadora — semelhante a como o mapeamento de herança de tabela por hierarquia (TPH) já funciona. A integração exata do EF Core ainda está sendo projetada, mas a infraestrutura para lidar com hierarquias de tipo fechado já existe.&lt;/p>
&lt;p>Vale a pena notar que a interoperabilidade com outras linguagens .NET deve ser tranquila, já que as uniões serão compiladas em hierarquias de classe IL padrão nos bastidores. O código F# que consome uma união C# o veria como uma hierarquia de tipos padrão e vice-versa.&lt;/p>
&lt;h2 id="como-experimentar">Como experimentar&lt;/h2>
&lt;p>No momento em que este artigo foi escrito, os tipos de união estavam disponíveis como um recurso de visualização nas visualizações mais recentes do SDK do .NET. Para experimentar a sintaxe proposta, você precisará:&lt;/p>
&lt;ol>
&lt;li>Instale o SDK de visualização .NET mais recente&lt;/li>
&lt;li>Habilite a versão do idioma de visualização em seu arquivo de projeto:&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;Project&lt;/span> Sdk=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;TargetFramework&amp;gt;&lt;/span>net10.0&lt;span style="color:#7ee787">&amp;lt;/TargetFramework&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;LangVersion&amp;gt;&lt;/span>preview&lt;span style="color:#7ee787">&amp;lt;/LangVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/Project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lembre-se de que os recursos de visualização estão sujeitos a alterações. A sintaxe, o comportamento e o diagnóstico do compilador podem evoluir significativamente antes do lançamento final. Não envie código de produção dependendo de recursos de linguagem de visualização, mas experimente-os e forneça feedback. A equipe C# monitora ativamente as discussões do repositório &lt;a href="https://github.com/dotnet/csharplang">csharplang&lt;/a>.&lt;/p>
&lt;p>Se você quiser acompanhar o andamento da proposta, os principais locais a serem observados são:&lt;/p>
&lt;ul>
&lt;li>O repositório &lt;a href="https://github.com/dotnet/csharplang">dotnet/csharplang&lt;/a> para discussões sobre design de linguagem&lt;/li>
&lt;li>O repositório &lt;a href="https://github.com/dotnet/roslyn">dotnet/roslyn&lt;/a> para o progresso da implementação do compilador&lt;/li>
&lt;li>O blog .NET para anúncios oficiais&lt;/li>
&lt;/ul>
&lt;h2 id="comparação-com-abordagens-existentes">Comparação com abordagens existentes&lt;/h2>
&lt;p>Deixe-me fazer uma comparação rápida para que você possa ver como as diferentes abordagens se comparam:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Recurso&lt;/th>
&lt;th>Registros Abstratos&lt;/th>
&lt;th>UmDe&amp;lt;T1,T2&amp;gt;&lt;/th>
&lt;th>Enum + Dados&lt;/th>
&lt;th>Tipos de União&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Verificação de exaustividade&lt;/td>
&lt;td>❌ Não&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;td>❌ Não&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Correspondência de padrões&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;td>❌ Limitado&lt;/td>
&lt;td>❌ Manual&lt;/td>
&lt;td>✅ Nativo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Fechamento imposto pelo compilador&lt;/td>
&lt;td>❌Não&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;td>❌Não&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Dados por variante&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;td>⚠️Frágil&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Legibilidade&lt;/td>
&lt;td>⚠️ Verboso&lt;/td>
&lt;td>⚠️ Posicional&lt;/td>
&lt;td>❌ Pobre&lt;/td>
&lt;td>✅ Excelente&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Serialização&lt;/td>
&lt;td>✅ Manual&lt;/td>
&lt;td>⚠️ Complexo&lt;/td>
&lt;td>✅ Manual&lt;/td>
&lt;td>✅ Integrado&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Padrão&lt;/td>
&lt;td>⚠️ Moderado&lt;/td>
&lt;td>✅ Baixo&lt;/td>
&lt;td>⚠️ Alto&lt;/td>
&lt;td>✅ Mínimo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Nenhuma dependência externa&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;td>❌ NuGet&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;td>✅ Sim&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="o-que-isso-significa-para-o-ecossistema-net">O que isso significa para o ecossistema .NET&lt;/h2>
&lt;p>A introdução de sindicatos discriminados terá repercussões em todo o ecossistema .NET. Aqui está o que espero ver:&lt;/p>
&lt;p>&lt;strong>O design da biblioteca será melhorado.&lt;/strong> APIs que atualmente retornam &lt;code>null&lt;/code> para indicar &amp;ldquo;não encontrado&amp;rdquo; ou lançam exceções para falhas de validação poderão retornar tipos &lt;code>Result&amp;lt;T, E&amp;gt;&lt;/code> em vez disso. Isso torna os modos de falha explícitos na assinatura de tipo — você pode ver o que pode dar errado observando a assinatura do método, não lendo a documentação ou o código-fonte.&lt;/p>
&lt;p>&lt;strong>A modelagem de domínio se torna mais expressiva.&lt;/strong> A lacuna entre o domínio do problema e a representação do código diminui drasticamente. Quando seu especialista em domínio diz “um pagamento pode ser um cartão de crédito, uma transferência bancária ou dinheiro na entrega”, você pode modelar isso diretamente como uma união, em vez de traduzi-lo em uma hierarquia de herança.&lt;/p>
&lt;p>&lt;strong>As ideias de F# tornam-se acessíveis aos desenvolvedores de C#.&lt;/strong> Muitos desenvolvedores de C# admiraram o sistema de tipos de F# à distância, mas não conseguiram adotá-lo em suas organizações. Os tipos de união trazem um dos recursos mais poderosos do F# para o C#, o que é uma vitória para todo o ecossistema .NET.&lt;/p>
&lt;p>&lt;strong>Menos erros de tempo de execução.&lt;/strong> A verificação exaustiva por si só evitará categorias inteiras de bugs. Cada vez que você adiciona uma nova variante a uma união, o compilador irá guiá-lo para cada lugar na base de código que precisa de atualização. Chega de casos &lt;code>switch&lt;/code> esquecidos, chega de &lt;code>NotImplementedException&lt;/code> em ramificações padrão que só surgem na produção.&lt;/p>
&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>Os sindicatos discriminados estão no topo da lista de desejos da comunidade C# há quase uma década, e por boas razões. Eles resolvem uma lacuna fundamental no sistema de tipos – a capacidade de modelar dados que podem ser “uma entre várias coisas” com segurança imposta pelo compilador.&lt;/p>
&lt;p>A palavra-chave &lt;code>union&lt;/code> proposta traz uma sintaxe limpa e concisa que se integra profundamente à correspondência de padrões do C#, funciona com genéricos, oferece suporte a métodos e interfaces e permite verificação exaustiva que detecta bugs em tempo de compilação em vez de tempo de execução.&lt;/p>
&lt;p>Esteja você construindo modelos de domínio, projetando APIs com tipos de erros explícitos, implementando máquinas de estado ou apenas tentando substituir hierarquias de herança desajeitadas por algo mais natural – os tipos de união mudarão a forma como você escreve C#.&lt;/p>
&lt;p>Estamos esperando há muito tempo por isso. A sintaxe é elegante, a integração com os recursos de linguagem existentes é cuidadosa e o impacto prático será sentido em todo o ecossistema .NET.&lt;/p>
&lt;p>Fique de olho nas versões prévias, experimente o recurso e forneça feedback à equipe de design de linguagem. Este é um daqueles recursos que, depois de usá-lo, você se perguntará como conseguiu viver sem ele.&lt;/p></content:encoded><category>.NET</category><category>C#</category></item><item><title>Construindo um sistema RAG em C# com Kernel Semântico</title><link>https://emimontesdeoca.github.io/pt/posts/rag-csharp-semantic-kernel/</link><pubDate>Wed, 18 Mar 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/rag-csharp-semantic-kernel/</guid><description>Implemente a geração aumentada de recuperação em C# usando kernel semântico, embeddings e pesquisa vetorial.</description><content:encoded>&lt;h2 id="introdução">Introdução&lt;/h2>
&lt;p>Se você tentou usar um LLM para responder perguntas sobre seus próprios dados – documentos da empresa, especificações de produtos, bases de conhecimento internas – você provavelmente notou que ele alucina ou apenas diz “Não tenho informações sobre isso”. Isso porque o modelo só sabe no que foi treinado.&lt;/p>
&lt;p>RAG (Geração Aumentada de Recuperação) corrige isso. Em vez de ajustar um modelo em seus dados, você recupera partes relevantes de seus documentos no momento da consulta e os transmite ao LLM como contexto. O modelo então gera respostas baseadas em seus dados reais.&lt;/p>
&lt;p>Nesta postagem, orientarei você na construção de um pipeline RAG completo em C# usando Kernel Semântico.&lt;/p>
&lt;h2 id="como-funciona-o-rag">Como funciona o RAG&lt;/h2>
&lt;p>O fluxo é direto:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Ingestão&lt;/strong>: divida seus documentos em partes, gere embeddings para cada parte e armazene-os em um banco de dados vetorial&lt;/li>
&lt;li>&lt;strong>Consulta&lt;/strong>: Quando um usuário faz uma pergunta, gere uma incorporação para a consulta, pesquise no banco de dados de vetores por partes semelhantes&lt;/li>
&lt;li>&lt;strong>Gerar&lt;/strong>: Passe os pedaços recuperados como contexto para o LLM junto com a pergunta do usuário&lt;/li>
&lt;/ol>
&lt;p>É isso. A mágica está nos embeddings – eles capturam o significado semântico do texto como vetores, para que você possa encontrar conteúdo relevante mesmo quando as palavras exatas não correspondem.&lt;/p>
&lt;h2 id="pré-requisitos">Pré-requisitos&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.Extensions.VectorData.Abstractions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.InMemory
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para produção, você trocaria o armazenamento na memória pelo Azure AI Search, Qdrant, Pinecone ou qualquer outro banco de dados de vetores compatível. Mas o in-memory é perfeito para aprendizado e prototipagem.&lt;/p>
&lt;h2 id="configurando-o-kernel">Configurando o kernel&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.AzureOpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.VectorData&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.InMemory&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Embeddings&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:Endpoint&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:ApiKey&amp;#34;&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAITextEmbeddingGeneration(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;text-embedding-3-small&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:Endpoint&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:ApiKey&amp;#34;&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Precisamos de dois modelos: um para completar o chat (responder perguntas) e outro para gerar embeddings (transformar texto em vetores).&lt;/p>
&lt;h2 id="definindo-o-modelo-de-dados">Definindo o modelo de dados&lt;/h2>
&lt;p>Precisamos de uma classe para representar nossos pedaços de documentos no armazenamento de vetores:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.VectorData&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">DocumentChunk&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordKey]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = Guid.NewGuid().ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordData]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Content { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordData]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Source { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordData]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> ChunkIndex { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordVector(1536)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ReadOnlyMemory&amp;lt;&lt;span style="color:#ff7b72">float&lt;/span>&amp;gt; Embedding { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O atributo &lt;code>VectorStoreRecordVector(1536)&lt;/code> informa ao armazenamento vetorial a dimensão de nossos embeddings. O modelo &lt;code>text-embedding-3-small&lt;/code> produz vetores de 1536 dimensões.&lt;/p>
&lt;h2 id="fragmentação-de-documentos">Fragmentação de documentos&lt;/h2>
&lt;p>Antes de podermos criar embeddings, precisamos dividir nossos documentos em partes gerenciáveis. Aqui está um divisor de texto simples:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TextChunker&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; SplitText(&lt;span style="color:#ff7b72">string&lt;/span> text, &lt;span style="color:#ff7b72">int&lt;/span> maxChunkSize = &lt;span style="color:#a5d6ff">500&lt;/span>, &lt;span style="color:#ff7b72">int&lt;/span> overlap = &lt;span style="color:#a5d6ff">50&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chunks = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> paragraphs = text.Split(&lt;span style="color:#a5d6ff">&amp;#34;\n\n&amp;#34;&lt;/span>, StringSplitOptions.RemoveEmptyEntries);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> currentChunk = &lt;span style="color:#ff7b72">new&lt;/span> System.Text.StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> paragraph &lt;span style="color:#ff7b72">in&lt;/span> paragraphs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (currentChunk.Length + paragraph.Length &amp;gt; maxChunkSize &amp;amp;&amp;amp; currentChunk.Length &amp;gt; &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chunks.Add(currentChunk.ToString().Trim());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Keep overlap from the end of the previous chunk&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> overlapText = currentChunk.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (overlapText.Length &amp;gt; overlap)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Append(overlapText[^overlap..]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Append(&lt;span style="color:#a5d6ff">&amp;#39; &amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Append(paragraph);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Append(&lt;span style="color:#a5d6ff">&amp;#34;\n\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (currentChunk.Length &amp;gt; &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chunks.Add(currentChunk.ToString().Trim());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> chunks;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A sobreposição é importante — ela garante que o contexto na fronteira entre os pedaços não seja perdido. Se uma frase relevante for dividida em dois blocos, a sobreposição significa que ela aparecerá integralmente em pelo menos um deles.&lt;/p>
&lt;h2 id="ingestão-de-documentos">Ingestão de documentos&lt;/h2>
&lt;p>Agora vamos juntar tudo para ingerir documentos em nosso armazenamento de vetores:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> vectorStore = &lt;span style="color:#ff7b72">new&lt;/span> InMemoryVectorStore();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> collection = vectorStore.GetCollection&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, DocumentChunk&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;documents&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> collection.CreateCollectionIfNotExistsAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> embeddingService = kernel.GetRequiredService&amp;lt;ITextEmbeddingGenerationService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task IngestDocument(&lt;span style="color:#ff7b72">string&lt;/span> content, &lt;span style="color:#ff7b72">string&lt;/span> source)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chunks = TextChunker.SplitText(content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">for&lt;/span> (&lt;span style="color:#ff7b72">int&lt;/span> i = &lt;span style="color:#a5d6ff">0&lt;/span>; i &amp;lt; chunks.Count; i++)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> embedding = &lt;span style="color:#ff7b72">await&lt;/span> embeddingService.GenerateEmbeddingAsync(chunks[i]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chunk = &lt;span style="color:#ff7b72">new&lt;/span> DocumentChunk
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Content = chunks[i],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Source = source,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ChunkIndex = i,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Embedding = embedding
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> collection.UpsertAsync(chunk);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;✅ Ingested {chunks.Count} chunks from {source}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Ingest some documents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doc1 = &lt;span style="color:#ff7b72">await&lt;/span> File.ReadAllTextAsync(&lt;span style="color:#a5d6ff">&amp;#34;docs/product-guide.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doc2 = &lt;span style="color:#ff7b72">await&lt;/span> File.ReadAllTextAsync(&lt;span style="color:#a5d6ff">&amp;#34;docs/faq.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doc3 = &lt;span style="color:#ff7b72">await&lt;/span> File.ReadAllTextAsync(&lt;span style="color:#a5d6ff">&amp;#34;docs/troubleshooting.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> IngestDocument(doc1, &lt;span style="color:#a5d6ff">&amp;#34;product-guide.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> IngestDocument(doc2, &lt;span style="color:#a5d6ff">&amp;#34;faq.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> IngestDocument(doc3, &lt;span style="color:#a5d6ff">&amp;#34;troubleshooting.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="procurando-por-pedaços-relevantes">Procurando por pedaços relevantes&lt;/h2>
&lt;p>Quando um usuário faz uma pergunta, geramos uma incorporação para sua consulta e procuramos por partes semelhantes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;List&amp;lt;DocumentChunk&amp;gt;&amp;gt; SearchAsync(&lt;span style="color:#ff7b72">string&lt;/span> query, &lt;span style="color:#ff7b72">int&lt;/span> topK = &lt;span style="color:#a5d6ff">3&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> queryEmbedding = &lt;span style="color:#ff7b72">await&lt;/span> embeddingService.GenerateEmbeddingAsync(query);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> searchResults = &lt;span style="color:#ff7b72">await&lt;/span> collection.VectorizedSearchAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> queryEmbedding,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> VectorSearchOptions { Top = topK });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> results = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;DocumentChunk&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> result &lt;span style="color:#ff7b72">in&lt;/span> searchResults.Results)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> results.Add(result.Record);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> results;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="gerando-respostas-com-contexto">Gerando respostas com contexto&lt;/h2>
&lt;p>Agora a parte RAG – pegamos os pedaços recuperados e os incluímos como contexto em nosso prompt:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> chatService = kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; AskAsync(&lt;span style="color:#ff7b72">string&lt;/span> question)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 1: Retrieve relevant chunks&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> relevantChunks = &lt;span style="color:#ff7b72">await&lt;/span> SearchAsync(question);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 2: Build context from chunks&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> context = &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34;\n\n---\n\n&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> relevantChunks.Select(c =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;[Source: {c.Source}]\n{c.Content}&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 3: Generate answer with context&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddSystemMessage(&lt;span style="color:#f85149">$&lt;/span>&lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> You are a helpful assistant that answers questions based on the provided context.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Use ONLY the information from the context to answer. If the context doesn&amp;#39;t contain
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> enough information to answer the question, say &amp;#34;&lt;/span>I don&lt;span style="color:#f85149">&amp;#39;&lt;/span>t have enough information
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> to answer that question.&lt;span style="color:#a5d6ff">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Do not make up information. Always cite the source document when possible.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Context:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {{context}}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(question);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> chatService.GetChatMessageContentAsync(history);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response.Content ?? &lt;span style="color:#a5d6ff">&amp;#34;No response generated.&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="usando">Usando&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Ask questions about your documents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> answer1 = &lt;span style="color:#ff7b72">await&lt;/span> AskAsync(&lt;span style="color:#a5d6ff">&amp;#34;How do I reset my password?&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Q: How do I reset my password?\nA: {answer1}\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> answer2 = &lt;span style="color:#ff7b72">await&lt;/span> AskAsync(&lt;span style="color:#a5d6ff">&amp;#34;What are the system requirements?&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Q: What are the system requirements?\nA: {answer2}\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> answer3 = &lt;span style="color:#ff7b72">await&lt;/span> AskAsync(&lt;span style="color:#a5d6ff">&amp;#34;What&amp;#39;s the capital of France?&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Q: What&amp;#39;s the capital of France?\nA: {answer3}\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Should respond with &amp;#34;I don&amp;#39;t have enough information&amp;#34; since it&amp;#39;s not in the docs&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="passando-para-produção">Passando para produção&lt;/h2>
&lt;p>O armazenamento de vetores na memória é ótimo para prototipagem, mas para produção você precisará de um banco de dados de vetores persistente. O Kernel Semântico possui conectores para diversas opções:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic"># Azure AI Search&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.AzureAISearch
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic"># Qdrant&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.Qdrant
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic"># Redis&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.Redis
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A troca é simples, pois todos implementam a mesma interface &lt;code>IVectorStore&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Instead of InMemoryVectorStore, use:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.AzureAISearch&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> vectorStore = &lt;span style="color:#ff7b72">new&lt;/span> AzureAISearchVectorStore(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Azure.Search.Documents.Indexes.SearchIndexClient(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Uri(config[&lt;span style="color:#a5d6ff">&amp;#34;AzureAISearch:Endpoint&amp;#34;&lt;/span>]),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> AzureKeyCredential(config[&lt;span style="color:#a5d6ff">&amp;#34;AzureAISearch:ApiKey&amp;#34;&lt;/span>])));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Todo o resto permanece igual. Essa &lt;span style="color:#f85149">é&lt;/span> a beleza da abstração.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> Dicas para construir sistemas RAG
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Algumas coisas que aprendi da maneira mais difícil:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **O tamanho &lt;span style="color:#ff7b72">do&lt;/span> pedaço &lt;span style="color:#f85149">é&lt;/span> muito importante.** Muito pequeno e você perde o contexto. Muito grande e você desperdiça tokens em conteúdo irrelevante. Comece com &lt;span style="color:#a5d6ff">500&lt;/span>-&lt;span style="color:#a5d6ff">800&lt;/span> tokens e ajuste com &lt;span style="color:#ff7b72">base&lt;/span> nos seus dados.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **A sobreposição evita problemas de limite.** Uma sobreposição de &lt;span style="color:#a5d6ff">50&lt;/span>-&lt;span style="color:#a5d6ff">100&lt;/span> tokens entre pedaços geralmente &lt;span style="color:#f85149">é&lt;/span> suficiente.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **Recupere mais &lt;span style="color:#ff7b72">do&lt;/span> que você pensa.** Comece com &lt;span style="color:#f85149">`&lt;/span>topK = &lt;span style="color:#a5d6ff">5&lt;/span>&lt;span style="color:#f85149">`&lt;/span> e reduza se estiver recebendo muito ruído. &lt;span style="color:#f85149">É&lt;/span> melhor ter contexto extra &lt;span style="color:#ff7b72">do&lt;/span> que perder a parte relevante.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **As instruções &lt;span style="color:#ff7b72">do&lt;/span> sistema são cruciais.** Seja bem explícito ao usar apenas o contexto fornecido. Sem essa instrução, o modelo terá alucinações felizes &lt;span style="color:#f85149">“&lt;/span>com &lt;span style="color:#ff7b72">base&lt;/span> em seus dados de treinamento&lt;span style="color:#f85149">”&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **Rastreie &lt;span style="color:#ff7b72">as&lt;/span> fontes.** Sempre armazene metadados com seus pedaços para que você possa citar de onde veio a resposta. Os usuários confiam mais nas respostas quando podem verificar a fonte.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **Reclassifique se necessário.** A similaridade &lt;span style="color:#ff7b72">do&lt;/span> vetor não &lt;span style="color:#f85149">é&lt;/span> perfeita. Para aplicações críticas, adicione uma etapa de reclassificação usando um modelo de codificador cruzado para melhorar a precisão.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> Conclusão
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>RAG &lt;span style="color:#f85149">é&lt;/span> um dos padrões mais práticos em IA atualmente. Ele permite que você crie sistemas de perguntas e respostas com tecnologia de IA sobre seus próprios dados sem ajuste fino, e o Kernel Semântico o torna surpreendentemente limpo em C&lt;span style="color:#f85149">#&lt;/span>. Comece com o armazenamento na memória, acerte a fragmentação e os prompts e, em seguida, troque por um banco de dados vetorial real quando estiver pronto para produção.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Boa codificação!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> Recursos
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- [Documentação &lt;span style="color:#ff7b72">do&lt;/span> armazenamento de vetores &lt;span style="color:#ff7b72">do&lt;/span> kernel semântico](https:&lt;span style="color:#8b949e;font-style:italic">//learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- [Padrão RAG com Azure AI Search](https:&lt;span style="color:#8b949e;font-style:italic">//learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- [Modelos de incorporação de texto](https:&lt;span style="color:#8b949e;font-style:italic">//learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#embeddings)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content:encoded><category>.NET</category><category>AI</category><category>Semantic Kernel</category><category>C#</category><category>Azure</category></item><item><title>Construindo fluxos de trabalho de agentes com o Microsoft Agent Framework</title><link>https://emimontesdeoca.github.io/pt/posts/agent-framework-workflows/</link><pubDate>Tue, 10 Feb 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/agent-framework-workflows/</guid><description>Projete e orquestre fluxos de trabalho de agentes sequenciais e paralelos usando o Microsoft Agent Framework em .NET.</description><content:encoded>&lt;h2 id="introdução">Introdução&lt;/h2>
&lt;p>Se você leu minhas postagens anteriores sobre o Agent Framework da Microsoft, sabe como criar agentes individuais e até mesmo bate-papos em grupo com vários agentes. Mas em cenários do mundo real, muitas vezes você precisa de algo mais estruturado: um fluxo de trabalho em que os agentes executam em uma ordem específica, passam os resultados entre si e lidam com a lógica de ramificação com base nos resultados.&lt;/p>
&lt;p>É exatamente isso que os recursos de fluxo de trabalho do Agent Framework oferecem. Em vez de colocar os agentes em um bate-papo em grupo e esperar que eles descubram, você define etapas, dependências e fluxo de dados explícitos entre os agentes.&lt;/p>
&lt;h2 id="o-que-são-fluxos-de-trabalho-de-agentes">O que são fluxos de trabalho de agentes?&lt;/h2>
&lt;p>Pense nos fluxos de trabalho dos agentes como um pipeline. Cada etapa é realizada por um agente especializado e o resultado de uma etapa alimenta a próxima. Você pode executar etapas sequencialmente, em paralelo ou condicionalmente com base nos resultados.&lt;/p>
&lt;p>Alguns exemplos:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Pipeline de conteúdo&lt;/strong>: Pesquisa → Rascunho → Revisão → Publicar&lt;/li>
&lt;li>&lt;strong>Processamento de dados&lt;/strong>: Extrair → Transformar → Validar → Carregar&lt;/li>
&lt;li>&lt;strong>Suporte ao cliente&lt;/strong>: Classificar → Encaminhar → Responder → Acompanhamento&lt;/li>
&lt;/ul>
&lt;h2 id="pré-requisitos">Pré-requisitos&lt;/h2>
&lt;p>Certifique-se de ter os pacotes mais recentes do Agent Framework:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Agents.Core
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E um ponto final Azure OpenAI ou OpenAI configurado.&lt;/p>
&lt;h2 id="construindo-um-fluxo-de-trabalho-sequencial">Construindo um fluxo de trabalho sequencial&lt;/h2>
&lt;p>Vamos construir um fluxo de trabalho de criação de conteúdo com três agentes: um pesquisador, um redator e um editor.&lt;/p>
&lt;h3 id="definindo-os-agentes">Definindo os agentes&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = Kernel.CreateBuilder()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:Endpoint&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:ApiKey&amp;#34;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> researcher = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Researcher&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a research specialist. Given a topic, find key facts, statistics,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and talking points. Return a structured research brief with bullet points.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Be thorough but concise.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> writer = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Writer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical blog writer. Given a research brief, write a clear
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and engaging blog post. Use a conversational tone, include code examples
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">where&lt;/span> relevant, and structure the post with clear headings.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> editor = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Editor&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a senior editor. Review the blog post &lt;span style="color:#ff7b72">for&lt;/span> clarity, accuracy,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> grammar, and flow. Return the corrected version with a summary of
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> changes made at the end.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="executando-sequencialmente">Executando sequencialmente&lt;/h3>
&lt;p>O fluxo de trabalho mais simples é sequencial — cada agente processa a saída do anterior:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents.Chat&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; RunSequentialWorkflow(&lt;span style="color:#ff7b72">string&lt;/span> topic)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> currentInput = &lt;span style="color:#a5d6ff">$&amp;#34;Research the following topic: {topic}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agents = &lt;span style="color:#ff7b72">new&lt;/span>[] { researcher, writer, editor };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> agent &lt;span style="color:#ff7b72">in&lt;/span> agents)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(currentInput);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">new&lt;/span> System.Text.StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> agent.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response.Append(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentInput = response.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;✅ {agent.Name} completed&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> currentInput;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> RunSequentialWorkflow(&lt;span style="color:#a5d6ff">&amp;#34;Blazor render modes in .NET 9&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(result);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Cada agente obtém o contexto acumulado, processa-o e o resultado passa para a próxima etapa.&lt;/p>
&lt;h2 id="execução-paralela">Execução paralela&lt;/h2>
&lt;p>Às vezes, os agentes podem trabalhar de forma independente. Por exemplo, você pode querer pesquisar vários subtópicos ao mesmo tempo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;&amp;gt; RunParallelResearch(&lt;span style="color:#ff7b72">string&lt;/span>[] topics)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> tasks = topics.Select(&lt;span style="color:#ff7b72">async&lt;/span> topic =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(&lt;span style="color:#a5d6ff">$&amp;#34;Research: {topic}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">new&lt;/span> System.Text.StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> researcher.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response.Append(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;✅ Research completed: {topic}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> results = &lt;span style="color:#ff7b72">await&lt;/span> Task.WhenAll(tasks);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> results.ToList();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> topics = &lt;span style="color:#ff7b72">new&lt;/span>[]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Blazor SSR streaming&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Enhanced navigation in .NET 9&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Render mode boundaries&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> briefs = &lt;span style="color:#ff7b72">await&lt;/span> RunParallelResearch(topics);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Em seguida, você pode alimentar todos os resumos de pesquisa em um único agente redator para produzir uma postagem coesa.&lt;/p>
&lt;h2 id="ramificação-condicional">Ramificação condicional&lt;/h2>
&lt;p>Fluxos de trabalho reais precisam de decisões. Talvez você queira uma etapa de verificação de qualidade que retorne ao redator se a postagem não for boa o suficiente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> qualityChecker = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;QualityChecker&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a quality assurance reviewer. Evaluate the blog post &lt;span style="color:#ff7b72">on&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">1.&lt;/span> Technical accuracy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">2.&lt;/span> Clarity and readability
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">3.&lt;/span> Completeness
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Respond with either:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#a5d6ff">&amp;#34;APPROVED&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> the post meets all criteria
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#a5d6ff">&amp;#34;REVISION NEEDED: [specific feedback]&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> changes are required
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Be strict. Only approve posts that are truly ready to publish.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; RunWithQualityLoop(&lt;span style="color:#ff7b72">string&lt;/span> topic, &lt;span style="color:#ff7b72">int&lt;/span> maxRevisions = &lt;span style="color:#a5d6ff">3&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 1: Research&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(&lt;span style="color:#a5d6ff">$&amp;#34;Research: {topic}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> research = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(researcher, history);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 2: Write&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(research);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> draft = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(writer, history);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 3: Quality loop&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">for&lt;/span> (&lt;span style="color:#ff7b72">int&lt;/span> i = &lt;span style="color:#a5d6ff">0&lt;/span>; i &amp;lt; maxRevisions; i++)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> qaHistory = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> qaHistory.AddUserMessage(&lt;span style="color:#a5d6ff">$&amp;#34;Review this post:\n\n{draft}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> qaResult = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(qualityChecker, qaHistory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (qaResult.Contains(&lt;span style="color:#a5d6ff">&amp;#34;APPROVED&amp;#34;&lt;/span>, StringComparison.OrdinalIgnoreCase))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;✅ Approved after {i + 1} review(s)&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> draft;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;🔄 Revision {i + 1}: {qaResult[..Math.Min(100, qaResult.Length)]}...&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Send back to writer with feedback&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(&lt;span style="color:#a5d6ff">$&amp;#34;Please revise based on this feedback:\n{qaResult}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> draft = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(writer, history);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;⚠️ Max revisions reached, returning latest draft&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> draft;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; InvokeAgent(ChatCompletionAgent agent, ChatHistory history)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">new&lt;/span> System.Text.StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> agent.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response.Append(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esse padrão é super útil. O verificador de qualidade atua como uma porta e o fluxo de trabalho faz um loop até que a saída seja boa o suficiente ou atinja o limite máximo de revisão.&lt;/p>
&lt;h2 id="adicionando-plug-ins-a-agentes-de-fluxo-de-trabalho">Adicionando plug-ins a agentes de fluxo de trabalho&lt;/h2>
&lt;p>Os agentes em fluxos de trabalho podem usar plug-ins da mesma forma que os agentes autônomos. É aqui que as coisas ficam realmente poderosas – os agentes podem chamar APIs, consultar bancos de dados ou realizar operações de arquivo como parte de sua etapa de fluxo de trabalho:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> researcherWithTools = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Researcher&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;Research the topic using available tools. Summarize findings.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add a web search plugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kernel.Plugins.AddFromType&amp;lt;WebSearchPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add a database plugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kernel.Plugins.AddFromObject(&lt;span style="color:#ff7b72">new&lt;/span> DatabasePlugin(connectionString), &lt;span style="color:#a5d6ff">&amp;#34;Database&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WebSearchPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction, Description(&amp;#34;Search the web for information&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; SearchAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;The search query&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> query)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Your search implementation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> httpClient.GetStringAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$&amp;#34;https://api.search.example.com?q={Uri.EscapeDataString(query)}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="tratamento-de-erros-em-fluxos-de-trabalho">Tratamento de erros em fluxos de trabalho&lt;/h2>
&lt;p>Ao encadear vários agentes, o tratamento de erros se torna crítico. Você não quer que uma etapa com falha trave todo o fluxo de trabalho silenciosamente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;WorkflowResult&amp;gt; RunResilientWorkflow(&lt;span style="color:#ff7b72">string&lt;/span> input)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">new&lt;/span> WorkflowResult();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Research = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(researcher, &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory(input));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Errors.Add(&lt;span style="color:#a5d6ff">$&amp;#34;Research failed: {ex.Message}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> writeHistory = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory(result.Research);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Draft = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(writer, writeHistory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Errors.Add(&lt;span style="color:#a5d6ff">$&amp;#34;Writing failed: {ex.Message}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Draft = result.Research; &lt;span style="color:#8b949e;font-style:italic">// Fallback to research output&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Success = result.Errors.Count == &lt;span style="color:#a5d6ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WorkflowResult&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Research { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Draft { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; Errors { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> Success { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="melhores-práticas">Melhores práticas&lt;/h2>
&lt;p>Depois de criar vários fluxos de trabalho de agentes, eis o que aprendi:- &lt;strong>Mantenha o foco nas instruções do agente&lt;/strong> — cada agente deve fazer uma coisa bem feita. Não tente ser um agente canivete suíço.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Limite o contexto&lt;/strong> — não passe todo o histórico da conversa para todos os agentes. Dê a cada passo apenas o que ele precisa.&lt;/li>
&lt;li>&lt;strong>Defina limites de revisão&lt;/strong> — loops de qualidade são ótimos, mas podem funcionar para sempre se você não tomar cuidado. Sempre tenha um limite máximo de iterações.&lt;/li>
&lt;li>&lt;strong>Registre tudo&lt;/strong> — as saídas do agente podem ser imprevisíveis. Registre a entrada e a saída de cada etapa para depuração.&lt;/li>
&lt;li>&lt;strong>Use a execução paralela com sabedoria&lt;/strong> — isso acelera as coisas, mas observe os limites de taxa da API e os custos de token.&lt;/li>
&lt;li>&lt;strong>Teste primeiro com modelos menores&lt;/strong> — desenvolva e teste sua lógica de fluxo de trabalho com GPT-3.5 antes de mudar para GPT-4o para produção.&lt;/li>
&lt;/ul>
&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>Os fluxos de trabalho dos agentes permitem criar pipelines de IA complexos e de várias etapas, nos quais cada agente é um especialista. Comece com fluxos de trabalho sequenciais, adicione execução paralela onde as etapas são independentes e use ramificação condicional para portas de qualidade. Os padrões são combináveis ​​– depois de pegar o jeito, você pode construir uma automação bastante sofisticada.&lt;/p>
&lt;p>Boa codificação!&lt;/p>
&lt;h2 id="recursos">Recursos&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent">Documentação do Microsoft Agent Framework&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/GettingStartedWithAgents">Amostras de agentes de kernel semântico&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>AI</category><category>Agent Framework</category><category>Semantic Kernel</category></item><item><title>Ciclo de vida do componente Blazor: o guia completo</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-component-lifecycle/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-component-lifecycle/</guid><description>Entenda cada método de ciclo de vida dos componentes do Blazor, desde a inicialização até o descarte, e quando usar cada um deles.</description><content:encoded>&lt;p>Já uso o Blazor há algum tempo e, honestamente, os métodos de ciclo de vida me confundiram no início. &lt;code>OnInitialized&lt;/code> versus &lt;code>OnInitializedAsync&lt;/code>? &lt;code>OnParametersSet&lt;/code> versus &lt;code>OnAfterRender&lt;/code>? Quando &lt;code>StateHasChanged&lt;/code> realmente aciona uma nova renderização? Depois de muitas tentativas e erros, finalmente tenho um modelo mental sólido para tudo isso.&lt;/p>
&lt;h1 id="o-ciclo-de-vida-em-resumo">O ciclo de vida em resumo&lt;/h1>
&lt;p>Quando um componente Blazor é renderizado, ele passa por estes métodos em ordem:&lt;/p>
&lt;ol>
&lt;li>&lt;code>SetParametersAsync&lt;/code>&lt;/li>
&lt;li>&lt;code>OnInitialized&lt;/code> / &lt;code>OnInitializedAsync&lt;/code>&lt;/li>
&lt;li>&lt;code>OnParametersSet&lt;/code> / &lt;code>OnParametersSetAsync&lt;/code>&lt;/li>
&lt;li>&lt;code>OnAfterRender&lt;/code> / &lt;code>OnAfterRenderAsync&lt;/code>&lt;/li>
&lt;li>&lt;code>Dispose&lt;/code> / &lt;code>DisposeAsync&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>Vamos examinar cada um deles.&lt;/p>
&lt;p>#SetParametersAsync&lt;/p>
&lt;p>Este é o primeiro método chamado. Ele recebe o &lt;code>ParameterView&lt;/code> bruto do componente pai. Na maioria das vezes você não substitui isso - o Blazor lida com os parâmetros de mapeamento automaticamente. Mas se você precisar de manipulação ou validação de parâmetros personalizados antes de serem atribuídos:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task SetParametersAsync(ParameterView parameters)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Custom logic before parameters are set&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (parameters.TryGetValue&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Title is being set to: {title}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">base&lt;/span>.SetParametersAsync(parameters);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Eu usei isso exatamente uma vez em um projeto real. Na maioria das vezes você vai pular isso.&lt;/p>
&lt;p>#OnInitialized/OnInitializedAsync&lt;/p>
&lt;p>É aqui que você faz o trabalho de configuração – carrega dados, inicializa serviços, define valores padrão. Ele é executado &lt;strong>uma vez&lt;/strong> quando o componente é criado pela primeira vez.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;Product&amp;gt; products = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> products = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/products&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Uma coisa que me surpreendeu: no Blazor Server, &lt;code>OnInitializedAsync&lt;/code> é chamado &lt;strong>duas vezes&lt;/strong> durante a pré-renderização. A primeira vez durante a pré-renderização do lado do servidor e a segunda vez quando a conexão SignalR é estabelecida. Se sua chamada de API for cara, você pode querer cuidar disso:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> isPrerendering = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> products = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/products&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> isPrerendering = &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ou melhor ainda, use &lt;code>PersistentComponentState&lt;/code> para evitar totalmente a chamada dupla.&lt;/p>
&lt;p>#OnParametersSet/OnParametersSetAsync&lt;/p>
&lt;p>Isso é acionado sempre que o componente pai é renderizado novamente e passa novos valores de parâmetro. Ele também dispara após &lt;code>OnInitialized&lt;/code>. Este é o lugar certo para reagir às alterações de parâmetros:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> CategoryId { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> previousCategoryId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;Product&amp;gt; products = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnParametersSetAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (CategoryId != previousCategoryId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> previousCategoryId = CategoryId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> products = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$&amp;#34;api/products?category={CategoryId}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A verificação de &lt;code>CategoryId != previousCategoryId&lt;/code> é importante - sem ela, você recarregaria os dados toda vez que o pai fosse renderizado novamente, mesmo que a categoria não mudasse.&lt;/p>
&lt;p>#OnAfterRender/OnAfterRenderAsync&lt;/p>
&lt;p>Isso é acionado depois que o componente é renderizado no DOM. O parâmetro &lt;code>firstRender&lt;/code> informa se é a renderização inicial. Este é o local para chamadas de interoperabilidade JS, já que os elementos DOM existem neste ponto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Inject]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> IJSRuntime JS { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnAfterRenderAsync(&lt;span style="color:#ff7b72">bool&lt;/span> firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JS.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;initializeChart&amp;#34;&lt;/span>, chartElement);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Eu uso muito &lt;code>firstRender&lt;/code> para evitar a reinicialização de bibliotecas JavaScript em cada nova renderização. Se você estiver configurando ouvintes de eventos, bibliotecas de gráficos ou qualquer coisa que toque diretamente no DOM, é aqui que tudo vai.&lt;/p>
&lt;h1 id="deverenderizar">Deverenderizar&lt;/h1>
&lt;p>Este é menos conhecido, mas super útil. Ele controla se um componente é renderizado novamente quando &lt;code>StateHasChanged&lt;/code> é chamado:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> shouldRender = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> ShouldRender() =&amp;gt; shouldRender;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> HeavyOperation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> shouldRender = &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Do a bunch of state changes without triggering renders&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">for&lt;/span> (&lt;span style="color:#ff7b72">int&lt;/span> i = &lt;span style="color:#a5d6ff">0&lt;/span>; i &amp;lt; &lt;span style="color:#a5d6ff">1000&lt;/span>; i++)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> items[i].Process();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> shouldRender = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged(); &lt;span style="color:#8b949e;font-style:italic">// Now render once with all changes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Usei isso para otimizar componentes que processam listas grandes. Em vez de renderizar novamente a cada alteração de item, você agrupa as atualizações e renderiza uma vez no final.&lt;/p>
&lt;h1 id="disposedisposeasync">Dispose/DisposeAsync&lt;/h1>
&lt;p>Quando um componente é removido da UI, você deve limpar todos os recursos. Implemente &lt;code>IDisposable&lt;/code> ou &lt;code>IAsyncDisposable&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@implements IAsyncDisposable
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> Timer? timer;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> IJSObjectReference? jsModule;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnInitialized()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> timer = &lt;span style="color:#ff7b72">new&lt;/span> Timer(OnTick, &lt;span style="color:#79c0ff">null&lt;/span>, &lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">1000&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnAfterRenderAsync(&lt;span style="color:#ff7b72">bool&lt;/span> firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> jsModule = &lt;span style="color:#ff7b72">await&lt;/span> JS.InvokeAsync&amp;lt;IJSObjectReference&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;import&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;./timer.js&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> ValueTask DisposeAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> timer?.Dispose();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (jsModule &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> jsModule.DisposeAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Coisas comuns para descartar: temporizadores, assinaturas de eventos, referências de módulos JS, &lt;code>CancellationTokenSource&lt;/code> e quaisquer serviços &lt;code>IDisposable&lt;/code> que você criou.&lt;/p>
&lt;p>#EstadoHasChangedEste não é um método de ciclo de vida, mas está intimamente relacionado. Diz ao Blazor &amp;ldquo;ei, meu estado mudou, por favor, me renderize novamente.&amp;rdquo; O Blazor o chama automaticamente após manipuladores de eventos, mas às vezes você precisa chamá-lo manualmente — normalmente quando o estado muda fora do fluxo de eventos normal do Blazor:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task StartPolling()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">while&lt;/span> (!cts.Token.IsCancellationRequested)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;Data&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/data&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged(); &lt;span style="color:#8b949e;font-style:italic">// Manual call needed since this isn&amp;#39;t a Blazor event&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(&lt;span style="color:#a5d6ff">5000&lt;/span>, cts.Token);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Uma observação importante: se você estiver atualizando a partir de um thread em segundo plano no Blazor Server, use &lt;code>InvokeAsync&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> InvokeAsync(() =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data = newData;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="a-imagem-completa">A imagem completa&lt;/h1>
&lt;p>Esta é a ordem em que tudo acontece:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Component created
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ SetParametersAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnInitialized / OnInitializedAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnParametersSet / OnParametersSetAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ Render
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnAfterRender(firstRender: true)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Parameter change from parent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ SetParametersAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnParametersSet / OnParametersSetAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ ShouldRender?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ Render
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnAfterRender(firstRender: false)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Component removed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ Dispose / DisposeAsync
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois de ter esse fluxo em mente, a depuração de problemas do ciclo de vida se torna muito mais fácil.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem! Sinta-se à vontade para entrar em contato comigo em qualquer mídia social em &lt;strong>@emimontesdeoca&lt;/strong>.&lt;/p>
&lt;h1 id="recursos">Recursos&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle">Ciclo de vida do componente ASP.NET Core Blazor&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable">Descarte de componentes com IDisposable e IAsyncDisposable&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>C#</category></item><item><title>Agent Framework da Microsoft para salvar o Natal</title><link>https://emimontesdeoca.github.io/pt/posts/agent-framework-christmas-presents/</link><pubDate>Tue, 16 Dec 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/agent-framework-christmas-presents/</guid><description>Crie um sistema multiagente de compras de presentes de Natal usando o Agent Framework da Microsoft com .NET.</description><content:encoded>&lt;h2 id="introdução">Introdução&lt;/h2>
&lt;p>Encontrar os presentes de Natal perfeitos pode ser estressante. Entre o brainstorming de ideias para presentes, a comparação de preços entre lojas e a garantia de que tudo chegue no prazo, as compras de fim de ano rapidamente se tornam cansativas. E se pudéssemos delegar essas tarefas a agentes especializados de IA que trabalham juntos? Nesta postagem, exploraremos como usar o Agent Framework da Microsoft para construir um sistema multiagente onde cada agente se especializa em uma tarefa específica, desde a geração de ideias de presentes até a comparação de preços, tudo coordenado por meio de fluxos de trabalho.&lt;/p>
&lt;h2 id="calendário-festivo-de-tecnologia-2025">Calendário Festivo de Tecnologia 2025&lt;/h2>
&lt;p alinhar="centro">
&lt;img src="https://sessionize.com/image/49aa-1140o400o3-sdJUGhdR3FCmm1KuPRM3D3.png"/>
&lt;/p>
&lt;p>Este projeto faz parte da minha sessão no &lt;strong>Festive Tech Calendar 2025&lt;/strong>, um evento comunitário incrível que celebra a tecnologia durante as festas de fim de ano. Você pode encontrar mais sobre o evento em &lt;a href="https://sessionize.com/festive-tech-calendar-2025/">Sessionize&lt;/a>.&lt;/p>
&lt;h2 id="o-que-é-o-agent-framework-da-microsoft">O que é o Agent Framework da Microsoft?&lt;/h2>
&lt;p>O Agent Framework é a solução da Microsoft para construir, orquestrar e implantar agentes de IA e sistemas multiagentes. Ele fornece uma base flexível para a criação de agentes que podem trabalhar sequencialmente, simultaneamente ou por meio de padrões de transferência. A estrutura oferece suporte aos modelos OpenAI, Azure OpenAI e Microsoft Foundry, tornando-a incrivelmente versátil.&lt;/p>
&lt;p>Os principais recursos incluem:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Orquestração multiagente&lt;/strong>: bate-papo em grupo, padrões sequenciais, simultâneos e de transferência&lt;/li>
&lt;li>&lt;strong>Ecossistema de plug-ins&lt;/strong>: Estenda com funções nativas, OpenAPI e Model Context Protocol (MCP)&lt;/li>
&lt;li>&lt;strong>Suporte ao fluxo de trabalho&lt;/strong>: crie pipelines de agentes complexos com executores e bordas&lt;/li>
&lt;/ul>
&lt;h2 id="pré-requisitos">Pré-requisitos&lt;/h2>
&lt;p>Antes de mergulhar no código, certifique-se de ter:&lt;/p>
&lt;p>-.NET 9&lt;/p>
&lt;ul>
&lt;li>Acesso Azure OpenAI (ou chave API OpenAI)&lt;/li>
&lt;li>Visual Studio ou Código do Visual Studio&lt;/li>
&lt;/ul>
&lt;p>Instale os pacotes necessários (observe que o sinalizador &lt;code>--prerelease&lt;/code> é necessário durante a visualização):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.Agents.AI.Workflows --prerelease
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.AI.OpenAI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.Identity
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.AI.Agents.Persistent --prerelease
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="os-agentes-de-compras-de-presentes">Os agentes de compras de presentes&lt;/h2>
&lt;p>Nosso localizador de presentes de Natal será composto por três agentes especializados trabalhando juntos:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Agente de ideias para presentes&lt;/strong> - Gera sugestões criativas de presentes com base no perfil do destinatário&lt;/li>
&lt;li>&lt;strong>Agente de comparação de preços&lt;/strong> - Encontra os melhores preços em diferentes lojas&lt;/li>
&lt;li>&lt;strong>Agente de Resumo&lt;/strong> - Compila as recomendações finais&lt;/li>
&lt;/ol>
&lt;h2 id="os-modelos">Os modelos&lt;/h2>
&lt;p>Vamos começar definindo nossos modelos de dados que fluirão pelo pipeline do agente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">GiftRecipient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Age { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; Interests { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = [];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> Budget { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">GiftIdea&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Description { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Category { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> EstimatedPrice { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">PriceResult&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GiftName { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Store { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> Price { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Url { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">GiftRecommendation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> GiftIdea Gift { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;PriceResult&amp;gt; Prices { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = [];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> PriceResult? BestDeal { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="construindo-os-agentes">Construindo os Agentes&lt;/h2>
&lt;p>A beleza do Agent Framework é a facilidade de criar agentes especializados. Cada agente é simplesmente um &lt;code>ChatClientAgent&lt;/code> com um prompt de sistema específico que define sua experiência.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.AI.OpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Agents.AI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Agents.AI.Workflows&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.AI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ChristmasAgentFactory&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> AIAgent CreateGiftIdeaAgent(IChatClient chatClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ChatClientAgent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">@&amp;#34;You are a creative Christmas gift advisor. When given information about a person
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> (age, interests, budget), you suggest thoughtful and personalized gift ideas.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> For each suggestion, provide:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Gift name
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Brief description of why it&amp;#39;s a good fit
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Category (Electronics, Books, Fashion, Home, Experience, etc.)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Estimated price range
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Always suggest 3-5 gift options within the specified budget.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Format your response as a structured list.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> AIAgent CreatePriceComparisonAgent(IChatClient chatClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ChatClientAgent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">@&amp;#34;You are a price comparison specialist. Given a list of gift ideas,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> you research and compare prices from different online retailers.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> For each gift, provide:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Store name (Amazon, Best Buy, Target, Walmart, etc.)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Current price
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Any available discounts or deals
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Always highlight the best deal for each item.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Consider shipping costs and delivery times for Christmas.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> AIAgent CreateSummaryAgent(IChatClient chatClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ChatClientAgent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">@&amp;#34;You are a gift recommendation summarizer. Take the gift ideas and price
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> comparisons and create a final recommendation report.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Your summary should:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Rank gifts by value (quality vs price)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Highlight the top pick with reasoning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Include total cost estimate
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Add any tips for holiday shopping
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Make the summary cheerful and festive!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="criando-o-fluxo-de-trabalho">Criando o fluxo de trabalho&lt;/h2>
&lt;p>Agora vem a parte divertida: conectar nossos agentes em um fluxo de trabalho sequencial. O Agent Framework fornece &lt;code>WorkflowBuilder&lt;/code> e &lt;code>AgentWorkflowBuilder&lt;/code> para compor agentes em diferentes padrões.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ChristmasGiftWorkflow&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task RunAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Set up the Azure OpenAI client&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> endpoint = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_ENDPOINT&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ?? &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> InvalidOperationException(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_ENDPOINT is not set.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> deploymentName = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_DEPLOYMENT_NAME&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ?? &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o-mini&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chatClient = &lt;span style="color:#ff7b72">new&lt;/span> AzureOpenAIClient(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Uri(endpoint),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GetChatClient(deploymentName)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AsIChatClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create our specialized agents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AIAgent giftIdeaAgent = ChristmasAgentFactory.CreateGiftIdeaAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AIAgent priceAgent = ChristmasAgentFactory.CreatePriceComparisonAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AIAgent summaryAgent = ChristmasAgentFactory.CreateSummaryAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Build a sequential workflow: Ideas -&amp;gt; Prices -&amp;gt; Summary&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> workflow = AgentWorkflowBuilder.BuildSequential(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;ChristmasGiftFinder&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [giftIdeaAgent, priceAgent, summaryAgent]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Define our gift recipient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> recipient = &lt;span style="color:#ff7b72">new&lt;/span> GiftRecipient
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Mom&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Age = &lt;span style="color:#a5d6ff">55&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Interests = [&lt;span style="color:#a5d6ff">&amp;#34;gardening&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;cooking&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;reading&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;yoga&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Budget = &lt;span style="color:#a5d6ff">100&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> prompt = &lt;span style="color:#a5d6ff">$@&amp;#34;Find Christmas gifts for {recipient.Name},
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> age {recipient.Age}, who enjoys {string.Join(&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;, recipient.Interests)}.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Budget: &lt;span style="color:#f85149">$&lt;/span>{recipient.Budget}&lt;span style="color:#a5d6ff">&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Execute the workflow with streaming&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">StreamingRun&lt;/span> run = &lt;span style="color:#ff7b72">await&lt;/span> InProcessExecution.StreamAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> workflow,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessage(ChatRole.User, prompt));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Send the turn token to start processing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> run.TrySendMessageAsync(&lt;span style="color:#ff7b72">new&lt;/span> TurnToken(emitEvents: &lt;span style="color:#79c0ff">true&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Watch for workflow events&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (WorkflowEvent evt &lt;span style="color:#ff7b72">in&lt;/span> run.WatchStreamAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (evt &lt;span style="color:#ff7b72">is&lt;/span> AgentRunUpdateEvent agentUpdate)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Write(agentUpdate.Data);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (evt &lt;span style="color:#ff7b72">is&lt;/span> WorkflowOutputEvent outputEvent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;\n🎄 Final Recommendations:&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(outputEvent.Data);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="executando-agentes-simultaneamente">Executando Agentes Simultaneamente&lt;/h2>
&lt;p>E se quisermos procurar presentes para várias pessoas ao mesmo tempo? O Agent Framework oferece suporte à execução simultânea, o que é perfeito para este cenário:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task FindGiftsForEveryoneAsync(IChatClient chatClient, List&amp;lt;GiftRecipient&amp;gt; recipients)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create an agent for each recipient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agents = recipients.Select(r =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> ChatClientAgent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$@&amp;#34;Find the perfect Christmas gift for {r.Name} (age {r.Age}),
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> who loves {string.Join(&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;, r.Interests)}. Budget: ${r.Budget}.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Provide one well-researched recommendation with price.&lt;span style="color:#a5d6ff">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> )).ToList();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Build a concurrent workflow - all agents run in parallel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> workflow = AgentWorkflowBuilder.BuildConcurrent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;FamilyGiftFinder&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> agents);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">StreamingRun&lt;/span> run = &lt;span style="color:#ff7b72">await&lt;/span> InProcessExecution.StreamAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> workflow,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessage(ChatRole.User, &lt;span style="color:#a5d6ff">&amp;#34;Find gifts now!&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> run.TrySendMessageAsync(&lt;span style="color:#ff7b72">new&lt;/span> TurnToken(emitEvents: &lt;span style="color:#79c0ff">true&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (WorkflowEvent evt &lt;span style="color:#ff7b72">in&lt;/span> run.WatchStreamAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (evt &lt;span style="color:#ff7b72">is&lt;/span> WorkflowOutputEvent output)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;🎁 All gift recommendations ready!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(output.Data);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="executores-personalizados-para-mais-controlepara-cenários-mais-complexos-você-pode-criar-executores-personalizados-que-oferecem-controle-refinado-sobre-o-fluxo-de-trabalho">Executores personalizados para mais controlePara cenários mais complexos, você pode criar executores personalizados que oferecem controle refinado sobre o fluxo de trabalho:&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">sealed&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">GiftValidatorExecutor&lt;/span> : Executor&amp;lt;List&amp;lt;ChatMessage&amp;gt;, List&amp;lt;ChatMessage&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> GiftValidatorExecutor() : &lt;span style="color:#ff7b72">base&lt;/span>(&lt;span style="color:#a5d6ff">&amp;#34;GiftValidator&amp;#34;&lt;/span>) { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> ValueTask&amp;lt;List&amp;lt;ChatMessage&amp;gt;&amp;gt; HandleAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;ChatMessage&amp;gt; messages,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IWorkflowContext context,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CancellationToken cancellationToken = &lt;span style="color:#ff7b72">default&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> lastMessage = messages.LastOrDefault()?.Text ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Validate that suggestions are within budget&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (lastMessage.Contains(&lt;span style="color:#a5d6ff">&amp;#34;over budget&amp;#34;&lt;/span>, StringComparison.OrdinalIgnoreCase))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;⚠️ Some suggestions exceeded budget, filtering...&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Add validation logic here&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;✅ Gift suggestions validated!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> messages;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Você pode então inserir este executor em seu fluxo de trabalho:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> validator = &lt;span style="color:#ff7b72">new&lt;/span> GiftValidatorExecutor();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> workflow = &lt;span style="color:#ff7b72">new&lt;/span> WorkflowBuilder(giftIdeaAgent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEdge(giftIdeaAgent, validator)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEdge(validator, priceAgent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEdge(priceAgent, summaryAgent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithOutputFrom(summaryAgent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Build();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="adicionando-pesquisa-real-na-web-com-o-bing-grounding">Adicionando pesquisa real na Web com o Bing Grounding&lt;/h2>
&lt;p>Até o momento, nossos agentes geram respostas baseadas no conhecimento do modelo de IA. Mas e se quisermos pesquisar na web os preços e a disponibilidade reais dos produtos? É aqui que entra o &lt;strong>Grounding with Bing Search&lt;/strong>. É uma ferramenta disponível no Microsoft Foundry (anteriormente Azure AI Foundry) que permite que seus agentes incorporem dados públicos da Web em tempo real ao gerar respostas.&lt;/p>
&lt;p>Primeiro, você precisará criar um recurso &lt;strong>Grounding with Bing Search&lt;/strong> no &lt;a href="https://portal.azure.com/#create/Microsoft.BingGroundingSearch">Portal do Azure&lt;/a>. Certifique-se de criá-lo no mesmo grupo de recursos do seu projeto de IA.&lt;/p>
&lt;h3 id="configurando-o-aterramento-com-o-bing-search">Configurando o aterramento com o Bing Search&lt;/h3>
&lt;p>A beleza do Grounding with Bing Search é que ele se integra diretamente aos Azure AI Agents. O agente decide quando usar a ferramenta de pesquisa com base na consulta do usuário, pesquisa na web e usa os resultados para gerar uma resposta fundamentada.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.AI.Agents.Persistent&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">BingGroundingSetup&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;PersistentAgent&amp;gt; CreateAgentWithBingGroundingAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> projectEndpoint,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> modelDeploymentName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> bingConnectionId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create the Persistent Agents client&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agentClient = &lt;span style="color:#ff7b72">new&lt;/span> PersistentAgentsClient(projectEndpoint, &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Configure the Bing Grounding tool&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bingGroundingTool = &lt;span style="color:#ff7b72">new&lt;/span> BingGroundingToolDefinition(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> BingGroundingSearchToolParameters(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [new BingGroundingSearchConfiguration(bingConnectionId)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create the agent with Bing Grounding enabled&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agent = &lt;span style="color:#ff7b72">await&lt;/span> agentClient.Administration.CreateAgentAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model: modelDeploymentName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#a5d6ff">&amp;#34;ChristmasPriceHunter&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> instructions: &lt;span style="color:#a5d6ff">@&amp;#34;You are a Christmas gift price comparison specialist.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> When given gift ideas, use Bing to search for:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Current prices at major retailers (Amazon, Best Buy, Target, Walmart)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Available discounts and holiday deals
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Shipping times to ensure delivery before Christmas
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Always provide URLs to the products you find.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Highlight the best deals and recommend where to buy.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tools: [bingGroundingTool]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> agent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="criando-um-agente-de-comparação-de-preços-com-pesquisa-real">Criando um agente de comparação de preços com pesquisa real&lt;/h3>
&lt;p>Agora vamos criar um comparador de preços completo que pesquisa na web informações reais sobre produtos:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.AI.Agents.Persistent&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ChristmasPriceAgent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> PersistentAgentsClient _client;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _modelDeployment;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _bingConnectionId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ChristmasPriceAgent(&lt;span style="color:#ff7b72">string&lt;/span> projectEndpoint, &lt;span style="color:#ff7b72">string&lt;/span> modelDeployment, &lt;span style="color:#ff7b72">string&lt;/span> bingConnectionId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _client = &lt;span style="color:#ff7b72">new&lt;/span> PersistentAgentsClient(projectEndpoint, &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _modelDeployment = modelDeployment;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _bingConnectionId = bingConnectionId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; FindGiftPricesAsync(&lt;span style="color:#ff7b72">string&lt;/span> giftIdeas)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create agent with Bing Grounding&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bingTool = &lt;span style="color:#ff7b72">new&lt;/span> BingGroundingToolDefinition(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> BingGroundingSearchToolParameters(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [new BingGroundingSearchConfiguration(_bingConnectionId)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agent = &lt;span style="color:#ff7b72">await&lt;/span> _client.Administration.CreateAgentAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model: _modelDeployment,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#a5d6ff">&amp;#34;PriceHunter&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> instructions: &lt;span style="color:#a5d6ff">@&amp;#34;Search the web for current prices on the given gift ideas.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> For each gift, find prices from at least 2-3 different stores.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Include direct links to the products.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Note any Christmas sales or discounts available.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tools: [bingTool]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create a thread for the conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> thread = &lt;span style="color:#ff7b72">await&lt;/span> _client.Threads.CreateThreadAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Add the gift ideas as a message&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _client.Messages.CreateMessageAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> thread.Id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MessageRole.User,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$&amp;#34;Find current prices for these gift ideas: {giftIdeas}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Run the agent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> run = &lt;span style="color:#ff7b72">await&lt;/span> _client.Runs.CreateRunAsync(thread.Id, agent.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Wait for completion&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(&lt;span style="color:#a5d6ff">500&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run = &lt;span style="color:#ff7b72">await&lt;/span> _client.Runs.GetRunAsync(thread.Id, run.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">while&lt;/span> (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Get the response&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> messages = _client.Messages.GetMessages(thread.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = messages
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(m =&amp;gt; m.Role == MessageRole.Agent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SelectMany(m =&amp;gt; m.ContentItems)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OfType&amp;lt;MessageTextContent&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Clean up&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _client.Threads.DeleteThreadAsync(thread.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response?.Text ?? &lt;span style="color:#a5d6ff">&amp;#34;No results found.&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Clean up the agent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _client.Administration.DeleteAgentAsync(agent.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="integrando-o-bing-grounding-ao-fluxo-de-trabalho">Integrando o Bing Grounding ao fluxo de trabalho&lt;/h3>
&lt;p>Veja como usar o agente de preços do Bing em um fluxo de trabalho completo para encontrar presentes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task RunWithBingGroundingAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> projectEndpoint = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;PROJECT_ENDPOINT&amp;#34;&lt;/span>)!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> modelDeployment = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;MODEL_DEPLOYMENT_NAME&amp;#34;&lt;/span>) ?? &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bingConnectionId = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;BING_CONNECTION_ID&amp;#34;&lt;/span>)!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Connection ID format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{account}/projects/{project}/connections/{connection}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;🔍 Searching the web for real prices with Bing Grounding...\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> priceAgent = &lt;span style="color:#ff7b72">new&lt;/span> ChristmasPriceAgent(projectEndpoint, modelDeployment, bingConnectionId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// First, generate gift ideas (could come from another agent)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> giftIdeas = &lt;span style="color:#a5d6ff">&amp;#34;1. Milwaukee cordless drill set, 2. Weber portable grill, 3. Carhartt beanie&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Search for real prices&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> priceResults = &lt;span style="color:#ff7b72">await&lt;/span> priceAgent.FindGiftPricesAsync(giftIdeas);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;📊 Price Comparison Results:&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(priceResults);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;\n🎁 Happy Shopping! 🎁&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="processando-citações-dos-resultados-do-bing">Processando citações dos resultados do Bing&lt;/h3>
&lt;p>Um aspecto importante do Grounding with Bing Search é que as respostas incluem citações com links para os sites de origem. Veja como extraí-los e exibi-los:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ProcessBingGroundingResponse(IEnumerable&amp;lt;PersistentThreadMessage&amp;gt; messages)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> messages.Where(m =&amp;gt; m.Role == MessageRole.Agent))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> content &lt;span style="color:#ff7b72">in&lt;/span> message.ContentItems)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (content &lt;span style="color:#ff7b72">is&lt;/span> MessageTextContent textContent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = textContent.Text;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Process URL citations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (textContent.Annotations != &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> annotation &lt;span style="color:#ff7b72">in&lt;/span> textContent.Annotations)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (annotation &lt;span style="color:#ff7b72">is&lt;/span> MessageTextUriCitationAnnotation uriAnnotation)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Replace citation placeholder with markdown link&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response = response.Replace(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uriAnnotation.Text,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$&amp;#34; [{uriAnnotation.UriCitation.Title}]({uriAnnotation.UriCitation.Uri})&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(response);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora, quando o agente de comparação de preços é executado, ele usa o &lt;strong>Grounding with Bing Search&lt;/strong> para encontrar listas de produtos reais, preços atuais e ofertas disponíveis na web ao vivo. O agente decide automaticamente quando pesquisar com base na consulta e retorna respostas fundamentadas com citações adequadas.&lt;/p>
&lt;h2 id="o-programa-completo">O Programa Completo&lt;/h2>
&lt;p>Veja como tudo acontece em &lt;code>Program.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.AI.OpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Agents.AI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Agents.AI.Workflows&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.AI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;🎄 Christmas Gift Finder - Powered by AI Agents 🎄\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> endpoint = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_ENDPOINT&amp;#34;&lt;/span>)!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> deployment = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_DEPLOYMENT_NAME&amp;#34;&lt;/span>) ?? &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o-mini&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> chatClient = &lt;span style="color:#ff7b72">new&lt;/span> AzureOpenAIClient(&lt;span style="color:#ff7b72">new&lt;/span> Uri(endpoint), &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GetChatClient(deployment)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AsIChatClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Create the agent team&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> ideaAgent = ChristmasAgentFactory.CreateGiftIdeaAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> priceAgent = ChristmasAgentFactory.CreatePriceComparisonAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> summaryAgent = ChristmasAgentFactory.CreateSummaryAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Build the workflow&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> workflow = AgentWorkflowBuilder.BuildSequential(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;ChristmasGiftWorkflow&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ideaAgent, priceAgent, summaryAgent]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;Who are you shopping for? &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> recipientName = Console.ReadLine() ?? &lt;span style="color:#a5d6ff">&amp;#34;Someone special&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;What are their interests? &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> interests = Console.ReadLine() ?? &lt;span style="color:#a5d6ff">&amp;#34;general&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;What&amp;#39;s your budget? $&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> budget = Console.ReadLine() ?? &lt;span style="color:#a5d6ff">&amp;#34;50&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> prompt = &lt;span style="color:#a5d6ff">$&amp;#34;Find Christmas gifts for {recipientName} who enjoys {interests}. Budget: ${budget}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;\n🔍 Searching for the perfect gifts...\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> run = &lt;span style="color:#ff7b72">await&lt;/span> InProcessExecution.StreamAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> workflow,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessage(ChatRole.User, prompt));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> run.TrySendMessageAsync(&lt;span style="color:#ff7b72">new&lt;/span> TurnToken(emitEvents: &lt;span style="color:#79c0ff">true&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> evt &lt;span style="color:#ff7b72">in&lt;/span> run.WatchStreamAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (evt)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> AgentRunUpdateEvent update:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Write(update.Update.Text);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> WorkflowOutputEvent output:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;\n\n🎁 Happy Shopping! 🎁&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>O Agent Framework da Microsoft torna surpreendentemente fácil a construção de sistemas multiagentes, onde cada agente é especializado em uma tarefa específica. Ao coordenar estes agentes através de fluxos de trabalho, podemos resolver problemas complexos como as compras de Natal de uma forma estruturada e eficiente.&lt;/p>
&lt;p>O que torna isso ainda mais poderoso é a capacidade de adicionar recursos do mundo real por meio de ferramentas. Ao integrar o &lt;strong>Grounding com o Bing Search&lt;/strong> do Microsoft Foundry, nosso agente de comparação de preços pode realmente pesquisar na Web preços, ofertas e disponibilidade atuais, transformando um simples chatbot de IA em um assistente de compras verdadeiramente útil com citações adequadas.&lt;/p>
&lt;p>O suporte da estrutura para padrões sequenciais, simultâneos e de transferência significa que você pode projetar sistemas de agentes que atendam exatamente às suas necessidades. Seja para encontrar presentes, planejar viagens ou qualquer outra tarefa de várias etapas, o Agent Framework fornece os blocos de construção para que isso aconteça.Nesta época de festas, deixe os agentes de IA cuidarem da pesquisa enquanto você se concentra em embrulhar presentes e aproveitar o tempo com a família!&lt;/p>
&lt;h2 id="código-fonte">Código Fonte&lt;/h2>
&lt;p>Os conceitos mostrados nesta postagem são baseados nos exemplos oficiais do Agent Framework. Você pode explorar mais exemplos no &lt;a href="https://github.com/microsoft/agent-framework">repositório GitHub do Microsoft Agent Framework&lt;/a>.&lt;/p>
&lt;p>Boas festas e boa codificação! 🎄&lt;/p></content:encoded><category>.NET</category><category>Azure</category><category>AI</category><category>Agent Framework</category></item><item><title>Construindo um agregador de feed RSS com tecnologia de IA</title><link>https://emimontesdeoca.github.io/pt/posts/ai-agent-socials/</link><pubDate>Fri, 12 Dec 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/ai-agent-socials/</guid><description>Automatize o monitoramento de feeds RSS e a pós-geração de mídias sociais usando o Kernel Semântico e o Azure OpenAI.</description><content:encoded>&lt;p>Como MVP da Microsoft e entusiasta de tecnologia, constantemente me afogo no oceano de conteúdo incrível publicado nos DevBlogs da Microsoft. Dos anúncios do .NET às atualizações do Visual Studio, das inovações do Azure aos aprofundamentos do Kernel Semântico – sempre há algo novo e interessante acontecendo no ecossistema da Microsoft.&lt;/p>
&lt;p>O problema? &lt;strong>Acompanhar tudo isso é quase impossível.&lt;/strong>&lt;/p>
&lt;p>Eu queria ficar por dentro dos anúncios mais recentes e compartilhá-los com minha rede, mas verificar manualmente sete feeds RSS diferentes, ler artigos, criar postagens envolventes nas redes sociais e acompanhar o que já compartilhei estava se tornando um trabalho de tempo integral por si só. Todas as manhãs eu abria várias abas do navegador, examinava dezenas de artigos, tentava lembrar quais já havia compartilhado e então gastava um tempo precioso escrevendo posts sobre aqueles que chamavam minha atenção.&lt;/p>
&lt;p>Então fiz o que qualquer desenvolvedor faria: &lt;strong>automatizei.&lt;/strong>&lt;/p>
&lt;p>Neste guia abrangente, mostrarei como construí um agregador de feeds RSS com tecnologia de IA que monitora vários feeds RSS do Microsoft DevBlogs para novos conteúdos, usa Azure OpenAI e Kernel Semântico para analisar artigos e gerar postagens envolventes, cria documentação detalhada de markdown para cada artigo analisado, envia notificações via Telegram para que eu possa revisar e compartilhar o conteúdo, rastreia tudo para evitar postagens duplicadas e é executado automaticamente por meio do GitHub Actions.&lt;/p>
&lt;p>Vamos nos aprofundar em cada aspecto desta solução.&lt;/p>
&lt;h2 id="a-história-por-trás-deste-projeto">A história por trás deste projeto&lt;/h2>
&lt;h3 id="vivendo-com-sobrecarga-de-informações">Vivendo com sobrecarga de informações&lt;/h3>
&lt;p>Deixe-me pintar um retrato da minha manhã típica antes de construir esta ferramenta. Eu acordava, pegava meu café e abria meu laptop para conferir o que há de novo no ecossistema de desenvolvedores da Microsoft. Primeiro, eu navegaria até o site principal do DevBlogs para ver se havia algum anúncio importante. Então eu verificaria o blog do .NET especificamente porque essa é minha principal pilha de tecnologia. Depois disso, eu iria para o blog do Kernel Semântico, já que a IA está se tornando cada vez mais importante. O blog do Visual Studio foi o próximo da lista porque as atualizações do IDE podem impactar significativamente meu fluxo de trabalho diário. Em seguida, veio o blog DevOps para notícias relacionadas a CI/CD e GitHub, seguido pelo blog All Things Azure para atualizações de infraestrutura em nuvem e, finalmente, o blog Azure SQL para inovações de banco de dados.&lt;/p>
&lt;p>São sete feeds diferentes para verificar. Cada um desses blogs publica vários artigos por semana, às vezes vários por dia durante períodos de anúncios importantes, como .NET Conf ou Build. São potencialmente dezenas de artigos para rastrear, ler e compartilhar. E o problema é o seguinte: como alguém que valoriza o compartilhamento de conhecimento com a comunidade, eu não queria apenas ler esses artigos. Eu queria compartilhar os mais valiosos com minha rede no LinkedIn, ajudando outros desenvolvedores a se manterem informados também.Mas criar uma boa postagem no LinkedIn leva tempo. Você precisa ler o artigo completamente, entender os pontos-chave, pensar por que ele é importante para o seu público, escrever um gancho envolvente e formatar tudo de maneira adequada. Multiplique isso por vários artigos por semana e você terá horas de trabalho.&lt;/p>
&lt;h3 id="o-que-eu-realmente-queria">O que eu realmente queria&lt;/h3>
&lt;p>Depois de lidar com isso durante meses, sentei-me e pensei em como seria a solução ideal. Em primeiro lugar, nunca mais queria perder anúncios importantes. O sistema deve capturar automaticamente novos artigos assim que forem publicados. Eu também queria economizar tempo na criação de conteúdo, permitindo que a IA ajudasse a criar postagens envolventes – não para substituir totalmente minha voz, mas para me dar um ponto de partida sólido que eu pudesse personalizar.&lt;/p>
&lt;p>A consistência foi outro grande fator. Eu queria compartilhar conteúdo regularmente, sem ter que me lembrar de fazer isso manualmente todos os dias. O aspecto do rastreamento também foi crucial – eu precisava de uma maneira de saber o que já compartilhei para evitar postar duplicatas e irritar meus seguidores. Por fim, queria me manter organizado com um registro permanente de tudo o que processei, para poder olhar para trás e ver quais tópicos abordei.&lt;/p>
&lt;h3 id="a-solução-toma-forma">A solução toma forma&lt;/h3>
&lt;p>A solução que imaginei seria executada de acordo com um cronograma usando GitHub Actions, totalmente sem usar as mãos. Ele buscaria todos os sete feeds automaticamente, sem que eu precisasse abrir uma única guia do navegador. O componente de IA realmente leria e compreenderia o conteúdo e depois o resumiria de uma forma que fosse útil para meu público. Em vez de eu ter que escrever postagens do zero, isso criaria conteúdo de mídia social pronto para compartilhar que eu poderia ajustar se necessário. Tudo seria enviado ao meu Telegram para revisão, para que eu pudesse rapidamente olhar para o meu telefone e decidir o que compartilhar. E, claro, manteria um registro permanente de tudo para referência futura.&lt;/p>
&lt;h2 id="antes-de-começarmos-a-construir">Antes de começarmos a construir&lt;/h2>
&lt;h3 id="o-que-você-precisa-em-sua-máquina">O que você precisa em sua máquina&lt;/h3>
&lt;p>Para acompanhar este tutorial, você precisará de algumas coisas instaladas em sua máquina de desenvolvimento. O mais importante é o .NET SDK versão 9.0 ou posterior. Este é o nosso tempo de execução e fornece todas as ferramentas de construção de que precisamos. Se você não o instalou, acesse dot.net e baixe a versão mais recente. A instalação é simples no Windows, macOS ou Linux.&lt;/p>
&lt;p>Você também desejará que o Git seja instalado para controle de versão. Enviaremos nosso código para o GitHub e usaremos GitHub Actions para automação, portanto, ter o Git configurado localmente é essencial. Qualquer versão recente funcionará bem.&lt;/p>
&lt;p>Para o seu ambiente de desenvolvimento, recomendo Visual Studio ou VS Code. Pessoalmente, eu uso o VS Code na maior parte do meu trabalho atualmente porque é leve e tem excelente suporte a C# por meio da extensão C# Dev Kit. Mas se você estiver mais confortável com o Visual Studio completo, isso também funciona perfeitamente.&lt;/p>
&lt;h3 id="serviços-e-contas-que-você-precisaalém-das-ferramentas-locais-você-precisará-de-contas-com-alguns-serviços-o-mais-importante-é-o-azure-openai-que-alimenta-nossa-análise-de-ia-este-é-um-serviço-pré-pago-mas-os-custos-são-mínimos-para-este-caso-de-uso--estamos-falando-de-centavos-por-artigo-analisado-se-não-tiver-uma-conta-do-azure-você-poderá-se-inscrever-para-uma-avaliação-gratuita-que-inclui-alguns-créditos-para-começar">Serviços e contas que você precisaAlém das ferramentas locais, você precisará de contas com alguns serviços. O mais importante é o Azure OpenAI, que alimenta nossa análise de IA. Este é um serviço pré-pago, mas os custos são mínimos para este caso de uso – estamos falando de centavos por artigo analisado. Se não tiver uma conta do Azure, você poderá se inscrever para uma avaliação gratuita que inclui alguns créditos para começar.&lt;/h3>
&lt;p>Para notificações, usaremos um Telegram Bot. A grande vantagem do Telegram é que sua API de bot é totalmente gratuita. Você pode criar quantos bots quiser e enviar mensagens ilimitadas. Orientarei você no processo de configuração posteriormente neste guia.&lt;/p>
&lt;p>Por fim, você precisará de uma conta GitHub para hospedar seu código e executar GitHub Actions. O nível gratuito é mais que suficiente para este projeto. O GitHub oferece 2.000 minutos de execução de Actions por mês em repositórios privados e minutos ilimitados em repositórios públicos.&lt;/p>
&lt;h3 id="as-bibliotecas-que-tornam-isso-possível">As bibliotecas que tornam isso possível&lt;/h3>
&lt;p>Nosso projeto depende de três pacotes NuGet principais, cada um servindo a um propósito específico.&lt;/p>
&lt;p>O primeiro é o HtmlAgilityPack, que é o padrão ouro para análise de HTML no .NET. Quando buscamos um artigo em um blog, recuperamos o HTML completo da página – incluindo menus de navegação, rodapés, anúncios e todo tipo de elementos com os quais não nos importamos. HtmlAgilityPack nos permite analisar esse HTML e extrair apenas o conteúdo do artigo que precisamos.&lt;/p>
&lt;p>O segundo pacote é Microsoft.SemanticKernel, que é o SDK da Microsoft para integração de modelos de IA em aplicativos. Pense nisso como uma ponte entre seu código .NET e grandes modelos de linguagem como GPT-4. Ele lida com toda a complexidade de chamadas de API, gerenciamento de tokens e análise de respostas, permitindo que você se concentre no que deseja que a IA realmente faça.&lt;/p>
&lt;p>O terceiro pacote é System.ServiceModel.Syndication, que fornece suporte integrado para análise de feeds RSS e Atom. RSS pode parecer uma tecnologia antiga, mas ainda é a melhor maneira de obter atualizações estruturadas de blogs e sites de notícias. Este pacote transforma feeds XML brutos em objetos C# fortemente tipados e fáceis de trabalhar.&lt;/p>
&lt;h2 id="compreendendo-a-arquitetura">Compreendendo a arquitetura&lt;/h2>
&lt;h3 id="como-as-peças-se-encaixam">Como as peças se encaixam&lt;/h3>
&lt;p>Antes de mergulharmos no código, deixe-me explicar como todos os componentes funcionam juntos. Compreender o panorama geral tornará os detalhes da implementação muito mais claros.&lt;/p>
&lt;p>No nível mais alto, temos nosso arquivo principal Program.cs que atua como orquestrador. Este é o ponto de entrada da nossa aplicação e coordena todos os outros componentes. Quando o aplicativo é executado, ele primeiro carrega a configuração das variáveis ​​de ambiente – coisas como chaves de API e credenciais do Telegram. Em seguida, ele busca feeds RSS de todas as sete fontes do Microsoft DevBlogs. À medida que processa esses feeds, ele desduplica artigos para lidar com casos em que o mesmo artigo aparece em vários feeds. Ele verifica cada artigo em nosso arquivo de rastreamento para ver se já o processamos. Para novos artigos, ele os entrega ao analisador de IA para processamento.A classe ArticleAnalyzer é onde a mágica da IA ​​acontece. Este componente recebe um artigo e faz diversas coisas com ele. Primeiro, ele busca o conteúdo HTML completo da URL do artigo. Em seguida, ele extrai texto limpo desse HTML, removendo todos os elementos de navegação, scripts e estilos desnecessários. Assim que tiver texto limpo, ele o envia para o Azure OpenAI por meio do Kernel Semântico com um prompt cuidadosamente elaborado. A IA analisa o artigo e retorna uma resposta estruturada que inclui um resumo, tópicos principais, explicação de relevância e, o mais importante, uma postagem no LinkedIn pronta para uso. O analisador analisa esta resposta e retorna um objeto ArticleAnalysis contendo todas essas informações.&lt;/p>
&lt;p>A classe MarkdownGenerator pega esse objeto ArticleAnalysis e cria um registro permanente dele. Ele gera um arquivo markdown bem formatado que inclui todos os metadados do artigo, a análise da IA ​​e a postagem gerada. Esses arquivos são armazenados em um diretório de postagens geradas, fornecendo um arquivo pesquisável de tudo o que você processou.&lt;/p>
&lt;p>Por fim, a integração do Telegram envia o conteúdo do post gerado para o seu telefone. Este é o ponto onde você, como ser humano, analisa o trabalho da IA ​​e decide se deseja compartilhá-lo. O bot envia uma mensagem com o conteúdo da postagem e você pode copiá-la diretamente para o LinkedIn ou modificá-la primeiro.&lt;/p>
&lt;h3 id="o-fluxo-de-dados">O Fluxo de Dados&lt;/h3>
&lt;p>Deixe-me explicar o que acontece quando um novo artigo é publicado no blog .NET. O fluxo de trabalho começa quando o GitHub Actions aciona nosso aplicativo de acordo com sua programação – digamos, a cada seis horas. O aplicativo é ativado e começa a buscar todos os sete feeds RSS. Cada feed retorna um documento XML contendo os artigos mais recentes daquele blog.&lt;/p>
&lt;p>À medida que analisamos cada feed, extraímos artigos individuais e os armazenamos em uma lista. Mas aqui está uma parte complicada: o feed principal do DevBlogs geralmente inclui artigos que também aparecem nos feeds de categorias individuais. Portanto, um artigo sobre &amp;ldquo;.NET 10&amp;rdquo; pode aparecer tanto no feed principal quanto no feed específico do .NET. Lidamos com isso rastreando URLs em um HashSet, que evita automaticamente duplicatas.&lt;/p>
&lt;p>Assim que tivermos nossa lista desduplicada de artigos, nós a filtraremos apenas para os mais recentes – normalmente artigos publicados no último dia ou depois. Não queremos processar artigos antigos que já tratamos em execuções anteriores. Em seguida, verificamos cada artigo recente em nosso arquivo de rastreamento. Se já processamos e postamos sobre um artigo, nós o ignoramos.&lt;/p>
&lt;p>Para cada novo artigo, iniciamos o pipeline de análise de IA. O analisador busca o HTML completo do artigo, limpa-o e envia-o para o GPT-4 com nosso prompt. A IA lê o artigo e gera uma análise abrangente junto com uma postagem no LinkedIn. Salvamos essa análise em um arquivo markdown para nossos registros.Com a análise concluída, formatamos uma mensagem e enviamos via Telegram. A mensagem inclui o conteúdo da postagem gerado com o URL e as hashtags anexadas. No meu celular recebo uma notificação, reviso a postagem e, se gostar, posso copiá-la e compartilhá-la no LinkedIn com apenas alguns toques.&lt;/p>
&lt;p>Por fim, atualizamos nosso arquivo de rastreamento para marcar este artigo como processado, para que não o tratemos novamente em execuções futuras. Se algum arquivo foi criado ou modificado, o GitHub Actions envia essas alterações de volta ao repositório, mantendo tudo sincronizado.&lt;/p>
&lt;h2 id="configurando-o-projeto-do-zero">Configurando o projeto do zero&lt;/h2>
&lt;h3 id="criando-a-estrutura-da-solução">Criando a estrutura da solução&lt;/h3>
&lt;p>Vamos começar a construir. Abra seu terminal e navegue até onde deseja criar o projeto. Gosto de manter meus projetos organizados em uma pasta Desenvolvimento, mas você pode colocá-los onde fizer sentido para você.&lt;/p>
&lt;p>Primeiro, criaremos um novo arquivo de solução. No .NET, uma solução é um contêiner que pode conter vários projetos. Embora tenhamos apenas um projeto no momento, começar com uma solução torna mais fácil adicionar mais projetos posteriormente, se necessário. Execute o comando &lt;code>dotnet new sln -n vs-feed-linkedin&lt;/code> para criar uma solução chamada vs-feed-linkedin.&lt;/p>
&lt;p>A seguir, precisamos criar nosso projeto de aplicativo de console. Colocaremos isso em um subdiretório src para manter as coisas organizadas. Execute &lt;code>dotnet new console -n VsFeedLinkedin -o src&lt;/code> para criar um projeto de console chamado VsFeedLinkedin na pasta src. Em seguida, adicione este projeto à nossa solução com &lt;code>dotnet sln add src/VsFeedLinkedin.csproj&lt;/code>.&lt;/p>
&lt;p>Agora navegue até o diretório src com &lt;code>cd src&lt;/code>. É aqui que adicionaremos nossos pacotes NuGet e faremos a maior parte do nosso desenvolvimento.&lt;/p>
&lt;h3 id="adicionando-os-pacotes-necessários">Adicionando os pacotes necessários&lt;/h3>
&lt;p>Com nosso projeto criado, precisamos adicionar os três pacotes NuGet que mencionei anteriormente. Execute cada um destes comandos em sequência:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package System.ServiceModel.Syndication --version 9.0.9
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel --version 1.30.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package HtmlAgilityPack --version 1.11.72
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois de executar esses comandos, seu arquivo de projeto deverá ficar parecido com isto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;Project&lt;/span> Sdk=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;OutputType&amp;gt;&lt;/span>Exe&lt;span style="color:#7ee787">&amp;lt;/OutputType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;TargetFramework&amp;gt;&lt;/span>net9.0&lt;span style="color:#7ee787">&amp;lt;/TargetFramework&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;ImplicitUsings&amp;gt;&lt;/span>enable&lt;span style="color:#7ee787">&amp;lt;/ImplicitUsings&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;Nullable&amp;gt;&lt;/span>enable&lt;span style="color:#7ee787">&amp;lt;/Nullable&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;ItemGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;HtmlAgilityPack&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;1.11.72&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.SemanticKernel&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;1.30.0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;System.ServiceModel.Syndication&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;9.0.9&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/ItemGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/Project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O arquivo do projeto informa ao .NET que estamos construindo um executável (OutputType é Exe), direcionado ao .NET 9.0 e usando recursos modernos do C#, como usos implícitos e tipos de referência anuláveis. A seção ItemGroup lista nossas três dependências de pacotes com suas versões exatas.&lt;/p>
&lt;h2 id="aprofunde-se-nos-feeds-rss">Aprofunde-se nos feeds RSS&lt;/h2>
&lt;h3 id="o-que-exatamente-é-rss">O que exatamente é RSS?&lt;/h3>
&lt;p>Antes de começarmos a escrever código para buscar feeds, vamos ter certeza de que entendemos com o que estamos trabalhando. RSS significa Really Simple Syndication e é um formato XML padronizado para distribuição de atualizações de conteúdo. A ideia é simples: em vez de exigir que os usuários visitem seu site para ver se há conteúdo novo, você publica um arquivo legível por máquina que lista seu conteúdo recente. Os aplicativos podem então pesquisar esse arquivo periodicamente para descobrir novos artigos.&lt;/p>
&lt;p>O RSS existe desde o final dos anos 1990 e início dos anos 2000. Você pode pensar que é uma tecnologia ultrapassada, mas na verdade ainda é amplamente utilizada – especialmente por blogs, sites de notícias e podcasts. A beleza do RSS é a sua simplicidade. É apenas XML com uma estrutura definida e qualquer aplicativo pode analisá-lo.&lt;/p>
&lt;h3 id="a-estrutura-de-um-feed-de-devblogsao-buscar-um-feed-rss-do-microsoft-devblogs-você-recebe-de-volta-um-documento-xml-que-segue-uma-estrutura-específica-no-nível-superior-há-um-elemento-rss-que-contém-um-único-elemento-de-canal-o-canal-representa-o-próprio-blog-e-inclui-metadados-como-título-url-e-descrição-do-blog">A estrutura de um feed de DevBlogsAo buscar um feed RSS do Microsoft DevBlogs, você recebe de volta um documento XML que segue uma estrutura específica. No nível superior, há um elemento rss que contém um único elemento de canal. O canal representa o próprio blog e inclui metadados como título, URL e descrição do blog.&lt;/h3>
&lt;p>Dentro do canal, você encontrará vários elementos de item, cada um representando uma postagem individual do blog. Cada item inclui um título (o título do artigo), um link (a URL onde você pode ler o artigo completo), um pubDate (quando o artigo foi publicado), um elemento dc:creator (o nome do autor), um ou mais elementos de categoria (tags para o artigo) e uma descrição (geralmente um resumo ou trecho do artigo).&lt;/p>
&lt;p>Aqui está um exemplo simplificado de como isso se parece:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;rss&lt;/span> version=&lt;span style="color:#a5d6ff">&amp;#34;2.0&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;channel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;title&amp;gt;&lt;/span>.NET Blog&lt;span style="color:#7ee787">&amp;lt;/title&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;link&amp;gt;&lt;/span>https://devblogs.microsoft.com/dotnet&lt;span style="color:#7ee787">&amp;lt;/link&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;description&amp;gt;&lt;/span>The latest news about .NET&lt;span style="color:#7ee787">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;item&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;title&amp;gt;&lt;/span>Announcing .NET 10&lt;span style="color:#7ee787">&amp;lt;/title&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;link&amp;gt;&lt;/span>https://devblogs.microsoft.com/dotnet/announcing-dotnet-10&lt;span style="color:#7ee787">&amp;lt;/link&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;pubDate&amp;gt;&lt;/span>Mon, 10 Dec 2025 12:00:00 GMT&lt;span style="color:#7ee787">&amp;lt;/pubDate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;dc:creator&amp;gt;&lt;/span>Microsoft&lt;span style="color:#7ee787">&amp;lt;/dc:creator&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;category&amp;gt;&lt;/span>Announcements&lt;span style="color:#7ee787">&amp;lt;/category&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;category&amp;gt;&lt;/span>.NET&lt;span style="color:#7ee787">&amp;lt;/category&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;description&amp;gt;&lt;/span>Article summary...&lt;span style="color:#7ee787">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/item&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/channel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/rss&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A grande vantagem do pacote System.ServiceModel.Syndication do .NET é que ele analisa tudo isso para nós. Não precisamos navegar manualmente nos nós XML ou nos preocupar com diferentes versões de RSS. Apenas carregamos o feed e recuperamos objetos fortemente digitados.&lt;/p>
&lt;h3 id="os-sete-feeds-que-monitoramos">Os sete feeds que monitoramos&lt;/h3>
&lt;p>Na minha implementação, monitoro sete feeds diferentes do Microsoft DevBlogs. O feed principal do DevBlogs em devblogs.microsoft.com/feed nos dá uma visão ampla de tudo o que a Microsoft está publicando em todos os seus blogs de desenvolvedores. O feed específico do .NET em devblogs.microsoft.com/dotnet/feed concentra-se especificamente em versões, recursos e práticas recomendadas do .NET. O feed do Kernel Semântico em devblogs.microsoft.com/semantic-kernel/feed abrange a orquestração e integração de IA – cada vez mais importante à medida que a IA se torna central para o desenvolvimento moderno.&lt;/p>
&lt;p>O feed do Visual Studio em devblogs.microsoft.com/visualstudio/feed me mantém atualizado sobre as melhorias do IDE e os recursos de produtividade. O feed DevOps em devblogs.microsoft.com/devops/feed abrange tópicos de Azure DevOps, GitHub e CI/CD. O feed All Things Azure em devblogs.microsoft.com/all-things-azure/feed concentra-se em serviços de nuvem e padrões de arquitetura. Por fim, o feed SQL do Azure em devblogs.microsoft.com/azure-sql/feed abrange inovações e recursos de banco de dados.&lt;/p>
&lt;p>Você pode estar se perguntando por que verifico o feed principal e os feeds de categorias individuais. O feed principal me dá amplitude – verei artigos de qualquer blog de desenvolvedor da Microsoft, incluindo aqueles que talvez eu não conheça. Os feeds de categoria me dão profundidade – eles garantem que eu não perca nada importante nas minhas principais áreas de interesse, mesmo que esses artigos sejam empurrados para fora do feed principal por causa de conteúdos mais recentes.&lt;/p>
&lt;h2 id="construindo-a-lógica-de-busca-de-rss">Construindo a lógica de busca de RSS&lt;/h2>
&lt;h3 id="a-função-principal-de-busca">A função principal de busca&lt;/h3>
&lt;p>Agora vamos escrever algum código. A base do nosso aplicativo é a capacidade de buscar e analisar feeds RSS. Aqui está a função que lida com isso:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;SyndicationFeed?&amp;gt; FetchRssFeedAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> httpClient.DefaultRequestHeaders.Add(&lt;span style="color:#a5d6ff">&amp;#34;User-Agent&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;VsFeedLinkedin/1.0&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> httpClient.GetStringAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> stringReader = &lt;span style="color:#ff7b72">new&lt;/span> StringReader(response);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> settings = &lt;span style="color:#ff7b72">new&lt;/span> XmlReaderSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DtdProcessing = DtdProcessing.Parse,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaxCharactersFromEntities = &lt;span style="color:#a5d6ff">1024&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> xmlReader = XmlReader.Create(stringReader, settings);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> SyndicationFeed.Load(xmlReader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Deixe-me explicar o que esse código faz. Começamos criando um HttpClient, que é a classe integrada do .NET para fazer solicitações HTTP. Definimos um cabeçalho User-Agent porque alguns servidores bloqueiam solicitações que não se identificam. É uma boa prática definir isso mesmo quando os servidores não exigem isso.Em seguida, fazemos uma solicitação GET para a URL do feed e recebemos a resposta como uma string. Esta string contém o XML bruto do feed RSS.&lt;/p>
&lt;p>Para analisar esse XML, criamos um StringReader para agrupar nossa string de resposta e, em seguida, configuramos alguns XmlReaderSettings. A configuração DtdProcessing é importante – os feeds RSS às vezes incluem declarações DTD (Document Type Definition) que precisam ser processadas. A configuração MaxCharactersFromEntities é uma medida de segurança que evita ataques de bomba XML, limitando a quantidade de expansão da entidade que pode ocorrer.&lt;/p>
&lt;p>Por fim, criamos um XmlReader com essas configurações e usamos SyndicationFeed.Load para analisar o XML em um objeto SyndicationFeed fortemente tipado. Isso nos dá acesso aos metadados do feed e a todos os seus itens por meio de boas propriedades C# em vez de navegação XML bruta.&lt;/p>
&lt;h3 id="buscando-vários-feeds-com-tratamento-de-erros">Buscando vários feeds com tratamento de erros&lt;/h3>
&lt;p>No mundo real, as solicitações de rede falham. Os servidores ficam inativos, as conexões atingem o tempo limite e o XML pode ficar malformado. Precisamos lidar com esses casos com elegância. Veja como buscamos todos os nossos feeds e ao mesmo tempo somos resilientes a falhas:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> allArticles = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;(SyndicationItem item, &lt;span style="color:#ff7b72">string&lt;/span> feedUrl)&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> seenUrls = &lt;span style="color:#ff7b72">new&lt;/span> HashSet&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> feedUrl &lt;span style="color:#ff7b72">in&lt;/span> feedUrls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34; 📡 Fetching {feedUrl}...&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> feed = &lt;span style="color:#ff7b72">await&lt;/span> FetchRssFeedAsync(feedUrl);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (feed?.Items != &lt;span style="color:#79c0ff">null&lt;/span> &amp;amp;&amp;amp; feed.Items.Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> item &lt;span style="color:#ff7b72">in&lt;/span> feed.Items)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> itemUrl = item.Links.FirstOrDefault()?.Uri.ToString() ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(itemUrl) &amp;amp;&amp;amp; seenUrls.Add(itemUrl))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> allArticles.Add((item, feedUrl));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34; ⚠️ Failed to fetch {feedUrl}: {ex.Message}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Mantemos duas coleções aqui. A lista allArticles conterá todos os artigos que encontrarmos, juntamente com o feed de onde eles vieram. O HashSet vistoUrls rastreia quais URLs de artigos já vimos, ajudando-nos a evitar duplicatas.&lt;/p>
&lt;p>Percorremos cada URL de feed e envolvemos a operação de busca em um bloco try-catch. Se a busca de um feed específico falhar – talvez o servidor esteja temporariamente inativo – registramos um aviso e continuamos com o próximo feed. Dessa forma, um problema com um feed não nos impede de processar os outros.&lt;/p>
&lt;p>Para cada feed obtido com sucesso, iteramos seus itens. Extraímos o URL do artigo da coleção Links do item. O método HashSet.Add retorna false se a URL já estiver no conjunto, o que é perfeito para nossa lógica de desduplicação. Só adicionamos o artigo à nossa lista se for novo.&lt;/p>
&lt;p>Armazenamos o URL do feed junto com cada artigo porque essas informações podem ser úteis posteriormente – por exemplo, podemos querer saber de qual feed específico veio um artigo para fins de depuração ou registro.&lt;/p>
&lt;h2 id="tratamento-de-duplicatas-e-estado-de-rastreamento">Tratamento de duplicatas e estado de rastreamento&lt;/h2>
&lt;h3 id="o-desafio-da-desduplicação">O desafio da desduplicação&lt;/h3>
&lt;p>Como mencionei anteriormente, o Microsoft DevBlogs possui uma estrutura de feed hierárquica que cria um desafio interessante. Quando um membro da equipe .NET publica um artigo sobre, digamos, melhorias de desempenho no .NET 10, esse artigo provavelmente aparecerá no feed principal do DevBlogs e no feed específico do .NET. Às vezes, pode até aparecer no feed do Visual Studio se estiver relacionado aos recursos do IDE.&lt;/p>
&lt;p>Se processássemos ingenuamente cada artigo de cada feed, acabaríamos analisando e postando o mesmo artigo diversas vezes. Isso desperdiçaria chamadas de API para o Azure OpenAI, enviaria spam para nosso Telegram com notificações duplicadas e potencialmente irritaria nossos seguidores se postássemos duplicatas.A solução é a desduplicação baseada em URL. Cada artigo possui um URL exclusivo, então podemos usá-lo como identificador. A estrutura de dados HashSet é perfeita para isso porque fornece tempo de pesquisa O(1) e evita automaticamente duplicatas. Quando tentamos adicionar uma URL que já está no conjunto, o método Add simplesmente retorna false, informando que devemos pular esse artigo.&lt;/p>
&lt;h3 id="estado-persistente-com-markdown">Estado persistente com Markdown&lt;/h3>
&lt;p>A desduplicação lida com duplicatas em uma única execução, mas e entre execuções? Quando nosso aplicativo é executado a cada seis horas, precisamos lembrar quais artigos já processamos para não manipulá-los novamente.&lt;/p>
&lt;p>Optei por armazenar esse estado em um arquivo markdown chamado postado-artigos.md. Por que redução? Algumas razões. Primeiro, é legível por humanos. Posso abrir o arquivo e ver imediatamente quais artigos compartilhei. Em segundo lugar, é controlado por versão. Como esse arquivo está em nosso repositório Git, tenho um histórico completo de quando os artigos foram processados. Terceiro, serve como documentação. Qualquer pessoa que olhar o repositório pode ver o que o aplicativo fez.&lt;/p>
&lt;p>O formato deste arquivo é simples. Ele tem um cabeçalho, um carimbo de data e hora mostrando quando o aplicativo foi executado pela última vez e, em seguida, uma lista de artigos em formato de link de desconto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-markdown" data-lang="markdown">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#79c0ff;font-weight:bold"># Posted Articles
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#79c0ff;font-weight:bold">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="font-style:italic">*Last run: 2025-12-10 15:30:00*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>List of articles posted to LinkedIn:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">-&lt;/span> [&lt;span style="color:#7ee787">Announcing .NET 10&lt;/span>](https://devblogs.microsoft.com/dotnet/announcing-dotnet-10?wt.mc_id=DT-MVP-5004972) - Posted on 2025-12-10 15:30:00 (Published: 2025-12-10)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">-&lt;/span> [&lt;span style="color:#7ee787">Visual Studio 2026 Preview&lt;/span>](https://devblogs.microsoft.com/visualstudio/vs-2026-preview?wt.mc_id=DT-MVP-5004972) - Posted on 2025-12-09 10:15:00 (Published: 2025-12-09)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="carregando-e-analisando-o-arquivo-de-rastreamento">Carregando e analisando o arquivo de rastreamento&lt;/h3>
&lt;p>Para verificar se já processamos um artigo, precisamos carregar este arquivo e extrair as URLs. Aqui está a função que faz isso:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> HashSet&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; LoadPostedArticles(&lt;span style="color:#ff7b72">string&lt;/span> filePath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> postedUrls = &lt;span style="color:#ff7b72">new&lt;/span> HashSet&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!File.Exists(filePath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> postedUrls;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> lines = File.ReadAllLines(filePath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> line &lt;span style="color:#ff7b72">in&lt;/span> lines)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> match = System.Text.RegularExpressions.Regex.Match(line, &lt;span style="color:#a5d6ff">@&amp;#34;\(([^)]+)\)&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (match.Success)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> url = match.Groups[&lt;span style="color:#a5d6ff">1&lt;/span>].Value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (url.Contains(&lt;span style="color:#a5d6ff">&amp;#34;?wt.mc_id=&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url = url.Substring(&lt;span style="color:#a5d6ff">0&lt;/span>, url.IndexOf(&lt;span style="color:#a5d6ff">&amp;#34;?wt.mc_id=&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (url.Contains(&lt;span style="color:#a5d6ff">&amp;#34;?&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url = url.Substring(&lt;span style="color:#a5d6ff">0&lt;/span>, url.IndexOf(&lt;span style="color:#a5d6ff">&amp;#34;?&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url = url.TrimEnd(&lt;span style="color:#a5d6ff">&amp;#39;/&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> postedUrls.Add(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> postedUrls;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esta função retorna um HashSet contendo todas as URLs que já processamos. Começamos verificando se o arquivo existe – na primeira execução, não existirá, então retornamos um conjunto vazio.&lt;/p>
&lt;p>Para cada linha do arquivo, usamos uma regex para extrair a URL do formato do link markdown. A regex &lt;code>\(([^)]+)\)&lt;/code> corresponde a qualquer coisa entre parênteses, que é onde os links markdown armazenam seus URLs.&lt;/p>
&lt;p>Depois vem uma etapa importante: normalização de URL. Os URLs do mesmo artigo podem variar em formato. O feed RSS pode nos fornecer &lt;code>https://devblogs.microsoft.com/dotnet/article&lt;/code>, mas nossa versão salva tem um parâmetro de rastreamento anexado: &lt;code>https://devblogs.microsoft.com/dotnet/article?wt.mc_id=DT-MVP-5004972&lt;/code>. Alguns URLs possuem barras finais, outros não.&lt;/p>
&lt;p>Para lidar com isso, eliminamos todos os parâmetros de consulta (tudo após &lt;code>?&lt;/code>) e removemos as barras finais. Essa normalização garante que reconhecemos os artigos como duplicados, mesmo que seus URLs sejam diferentes nessas formas superficiais.&lt;/p>
&lt;h3 id="salvando-novos-artigos">Salvando novos artigos&lt;/h3>
&lt;p>Quando processamos um artigo com sucesso, precisamos adicioná-lo ao nosso arquivo de rastreamento:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> SavePostedArticle(&lt;span style="color:#ff7b72">string&lt;/span> filePath, &lt;span style="color:#ff7b72">string&lt;/span> url, &lt;span style="color:#ff7b72">string&lt;/span> title, DateTimeOffset publishDate)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> markdownEntry = &lt;span style="color:#a5d6ff">$&amp;#34;- [{title}]({url}) - Posted on {DateTime.Now:yyyy-MM-dd HH:mm:ss} (Published: {publishDate:yyyy-MM-dd})\n&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!File.Exists(filePath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> File.WriteAllText(filePath, &lt;span style="color:#a5d6ff">&amp;#34;# Posted Articles\n\n*Last run: {DateTime.Now:yyyy-MM-dd HH:mm:ss}*\n\nList of articles posted:\n\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> File.AppendAllText(filePath, markdownEntry);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esta função cria uma entrada formatada em markdown com o título do artigo como um link, seguido por carimbos de data e hora mostrando quando o postamos e quando foi publicado originalmente. Se o arquivo ainda não existir, primeiro o criamos com um cabeçalho.&lt;/p>
&lt;h2 id="o-mecanismo-de-análise-de-ia">O mecanismo de análise de IA&lt;/h2>
&lt;h3 id="compreendendo-o-kernel-semânticoagora-chegamos-à-parte-mais-interessante-da-nossa-aplicação--a-análise-de-ia-o-kernel-semântico-é-o-sdk-de-código-aberto-da-microsoft-para-integração-de-grandes-modelos-de-linguagem-em-aplicativos-é-mais-do-que-apenas-um-wrapper-para-chamadas-de-api-ele-fornece-uma-estrutura-para-a-construção-de-aplicativos-sofisticados-de-ia-com-recursos-como-plug-ins-planejadores-e-memória">Compreendendo o kernel semânticoAgora chegamos à parte mais interessante da nossa aplicação – a análise de IA. O Kernel Semântico é o SDK de código aberto da Microsoft para integração de grandes modelos de linguagem em aplicativos. É mais do que apenas um wrapper para chamadas de API. Ele fornece uma estrutura para a construção de aplicativos sofisticados de IA com recursos como plug-ins, planejadores e memória.&lt;/h3>
&lt;p>Para nosso caso de uso, estamos usando os recursos de conclusão de bate-papo do Semantic Kernel. Enviaremos um prompt ao Azure OpenAI e o modelo analisará nosso artigo e gerará uma resposta. O Kernel Semântico lida com toda a complexidade de autenticação de API, formatação de solicitação e análise de resposta.&lt;/p>
&lt;h3 id="configurando-o-analisador-de-artigos">Configurando o analisador de artigos&lt;/h3>
&lt;p>Vejamos como configuramos nossa classe de analisador:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">HtmlAgilityPack&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">VsFeedLinkedin.Services&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ArticleAnalyzer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> Kernel _kernel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IChatCompletionService _chatService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ArticleAnalyzer(&lt;span style="color:#ff7b72">string&lt;/span> endpoint, &lt;span style="color:#ff7b72">string&lt;/span> apiKey, &lt;span style="color:#ff7b72">string&lt;/span> deploymentName)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: deploymentName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: endpoint,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: apiKey
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _chatService = _kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O Kernel Semântico usa um padrão de construtor para configuração. Criamos um KernelBuilder, adicionamos nosso serviço de conclusão de chat Azure OpenAI com as credenciais necessárias e, em seguida, construímos o kernel. Do kernel compilado, recuperamos a interface IChatCompletionService, que usaremos para enviar prompts e receber respostas.&lt;/p>
&lt;p>O construtor usa três parâmetros: o ponto de extremidade do Azure OpenAI (algo como &lt;code>https://your-resource.openai.azure.com/&lt;/code>), sua chave de API e o nome da implantação (como &lt;code>gpt-4o&lt;/code>). Eles são transmitidos a partir de variáveis ​​de ambiente, mantendo nossas credenciais seguras.&lt;/p>
&lt;h3 id="elaborando-o-prompt-perfeito">Elaborando o prompt perfeito&lt;/h3>
&lt;p>O aviso que enviamos para a IA é crucial. Um prompt bem elaborado produz resultados consistentes e de alta qualidade. Uma solicitação vaga ou mal estruturada produz resultados inconsistentes e medíocres. Passei um tempo considerável iterando esse prompt para obter resultados que me agradam:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> prompt = &lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> You are a professional tech content analyst and LinkedIn content creator.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Analyze the following Microsoft DevBlogs article and create an engaging LinkedIn post.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Article Title: {title}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Author: {author}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> URL: {url}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Tags: {string.Join(&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;, tags)}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Article Content:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {cleanContent}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Please provide:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">1.&lt;/span> A brief summary (&lt;span style="color:#a5d6ff">2&lt;/span>-&lt;span style="color:#a5d6ff">3&lt;/span> sentences) of the key points
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">2.&lt;/span> The main technologies or topics covered
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">3.&lt;/span> Why &lt;span style="color:#ff7b72">this&lt;/span> &lt;span style="color:#ff7b72">is&lt;/span> relevant &lt;span style="color:#ff7b72">for&lt;/span> developers/tech professionals
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">4.&lt;/span> An engaging LinkedIn post (max &lt;span style="color:#a5d6ff">1300&lt;/span> characters) that:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Starts with a hook or attention-grabbing statement
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Highlights the key &lt;span style="color:#ff7b72">value&lt;/span> &lt;span style="color:#ff7b72">for&lt;/span> readers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Includes a call to action
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Uses appropriate emojis (but not too many)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Maintains a professional yet approachable tone
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - DO NOT include hashtags &lt;span style="color:#ff7b72">in&lt;/span> the post (they will be added separately)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - DO NOT include the URL &lt;span style="color:#ff7b72">in&lt;/span> the post (it will be added separately)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Format your response &lt;span style="color:#ff7b72">as&lt;/span> follows:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">##&lt;/span> Summary
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Your summary here]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">##&lt;/span> Key Topics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [List of main topics/technologies]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">##&lt;/span> Relevance
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Why this matters]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">##&lt;/span> LinkedIn Post
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Your engaging LinkedIn post here]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Deixe-me explicar as decisões de design aqui. Começamos dando à IA um papel claro: “Você é um analista de conteúdo técnico profissional e criador de conteúdo do LinkedIn”. Isso prepara o modelo para responder com estilo e voz apropriados.&lt;/p>
&lt;p>Fornecemos todo o contexto que a IA precisa: título do artigo, autor, URL, tags do feed RSS e o conteúdo completo do artigo. Quanto mais contexto dermos, melhor será a análise.&lt;/p>
&lt;p>Então especificamos exatamente o que queremos de volta. Peço quatro coisas: um resumo, tópicos principais, explicação de relevância e uma postagem no LinkedIn. Especificamente para a postagem no LinkedIn, dou instruções detalhadas sobre o que constitui uma boa postagem - ela deve ter um gancho, destacar o valor, incluir uma frase de chamariz, usar emojis de maneira adequada e manter um tom profissional.&lt;/p>
&lt;p>As instruções negativas são igualmente importantes. Eu digo explicitamente à IA para NÃO incluir hashtags ou URL na postagem. Por que? Porque eu os adiciono separadamente e, se a IA os incluísse, teria duplicatas. Este tipo de instrução explícita evita erros comuns.&lt;/p>
&lt;p>Finalmente, especifico o formato de saída exato. Ao solicitar seções marcadas com ## cabeçalhos, facilito a análise programática da resposta. A IA é muito boa em seguir instruções de formatação e essa consistência torna nosso código de análise mais simples e confiável.&lt;/p>
&lt;h3 id="executando-a-análise">Executando a Análise&lt;/h3>
&lt;p>Veja como juntamos tudo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ArticleAnalysis&amp;gt; AnalyzeArticleAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> url,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> htmlContent,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> author,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; tags)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> cleanContent = ExtractTextFromHtml(htmlContent);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (cleanContent.Length &amp;gt; &lt;span style="color:#a5d6ff">8000&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cleanContent = cleanContent.Substring(&lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">8000&lt;/span>) + &lt;span style="color:#a5d6ff">&amp;#34;...&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chatHistory = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatHistory.AddUserMessage(prompt);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _chatService.GetChatMessageContentAsync(chatHistory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> responseText = response.Content ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ParseAnalysisResponse(responseText, title, url, author, tags);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Primeiro extraímos o texto limpo &lt;span style="color:#ff7b72">do&lt;/span> conteúdo HTML (explicarei isso na próxima seção). Então truncamos o conteúdo se &lt;span style="color:#ff7b72">for&lt;/span> muito longo. Modelos de linguagem grandes têm limites de tokens e artigos muito longos podem excedê-los. Ao limitar o limite de &lt;span style="color:#a5d6ff">8.000&lt;/span> caracteres, garantimos que permaneceremos dentro dos limites e, ao mesmo tempo, forneceremos um contexto substancial.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Criamos um objeto ChatHistory e adicionamos nosso prompt como uma mensagem &lt;span style="color:#ff7b72">do&lt;/span> usuário. Esta &lt;span style="color:#f85149">é&lt;/span> a abstração &lt;span style="color:#ff7b72">do&lt;/span> Semantic Kernel para interações baseadas em chat. Enviamos isso para o serviço de conclusão de chat e recebemos uma resposta. Finalmente, analisamos a resposta para extrair &lt;span style="color:#ff7b72">as&lt;/span> seções individuais.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> Analisando a resposta da IA
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>A IA retorna sua resposta como texto formatado com nossa estrutura solicitada. Precisamos analisar isso em campos individuais:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> ArticleAnalysis ParseAnalysisResponse(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> response,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> url,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> author,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; tags)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> analysis = &lt;span style="color:#ff7b72">new&lt;/span> ArticleAnalysis
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Title = title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Url = url,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Author = author,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Tags = tags,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RawAnalysis = response
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sections = response.Split(&lt;span style="color:#a5d6ff">&amp;#34;##&amp;#34;&lt;/span>, StringSplitOptions.RemoveEmptyEntries);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> section &lt;span style="color:#ff7b72">in&lt;/span> sections)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> lines = section.Trim().Split(&lt;span style="color:#a5d6ff">&amp;#39;\n&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">2&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (lines.Length &amp;lt; &lt;span style="color:#a5d6ff">2&lt;/span>) &lt;span style="color:#ff7b72">continue&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sectionTitle = lines[&lt;span style="color:#a5d6ff">0&lt;/span>].Trim().ToLower();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sectionContent = lines[&lt;span style="color:#a5d6ff">1&lt;/span>].Trim();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (sectionTitle)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;summary&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analysis.Summary = sectionContent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;key topics&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analysis.KeyTopics = sectionContent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;relevance&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analysis.Relevance = sectionContent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;linkedin post&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analysis.LinkedInPost = sectionContent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> analysis;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Dividimos a resposta pelos marcadores &lt;code>##&lt;/code>, o que nos dá cada seção. Para cada seção, dividimos por nova linha para separar o cabeçalho do conteúdo. Em seguida, usamos uma instrução switch para atribuir o conteúdo de cada seção à propriedade apropriada.&lt;/p>
&lt;p>Também armazenamos a resposta bruta e não analisada. Isso é útil para depuração – se algo der errado com a análise, podemos ver o que a IA realmente retornou.&lt;/p>
&lt;h2 id="extraindo-conteúdo-de-html">Extraindo conteúdo de HTML&lt;/h2>
&lt;h3 id="por-que-precisamos-limpar-o-html">Por que precisamos limpar o HTML&lt;/h3>
&lt;p>Quando buscamos um artigo em um blog, obtemos o HTML completo da página. Isso inclui muito mais do que apenas o conteúdo do artigo – há menus de navegação, cabeçalhos, rodapés, barras laterais, widgets de artigos relacionados, seções de comentários, scripts para análise e rastreamento, folhas de estilo e todos os tipos de outros elementos.&lt;/p>
&lt;p>Se enviássemos tudo isso para a nossa IA, várias coisas ruins aconteceriam. A IA teria que processar muitos textos irrelevantes, desperdiçando tokens e potencialmente confundindo a análise. O texto de navegação e rodapé pode ser incluído no resumo. Scripts e CSS seriam tratados como conteúdo, poluindo ainda mais a análise.&lt;/p>
&lt;p>Precisamos extrair apenas o conteúdo do artigo – a parte que um leitor humano realmente leria.&lt;/p>
&lt;h3 id="usando-htmlagilitypack">Usando HtmlAgilityPack&lt;/h3>
&lt;p>HtmlAgilityPack é uma biblioteca robusta de análise de HTML para .NET. Ao contrário do XML, o HTML geralmente é malformado – as tags podem não ser fechadas corretamente, os atributos podem não ser citados corretamente. HtmlAgilityPack lida com tudo isso com elegância, fornecendo-nos uma estrutura semelhante a DOM que podemos consultar e manipular.&lt;/p>
&lt;p>Aqui está nossa função de extração:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> ExtractTextFromHtml(&lt;span style="color:#ff7b72">string&lt;/span> html)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(html))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> doc = &lt;span style="color:#ff7b72">new&lt;/span> HtmlDocument();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> doc.LoadHtml(html);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> nodesToRemove = doc.DocumentNode.SelectNodes(&lt;span style="color:#a5d6ff">&amp;#34;//script|//style|//nav|//footer|//header&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (nodesToRemove != &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> node &lt;span style="color:#ff7b72">in&lt;/span> nodesToRemove)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> node.Remove();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> text = doc.DocumentNode.InnerText;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text = System.Text.RegularExpressions.Regex.Replace(text, &lt;span style="color:#a5d6ff">@&amp;#34;\s+&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34; &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> text.Trim();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Carregamos o HTML em um HtmlDocument, que o analisa em uma estrutura de árvore. Em seguida, usamos XPath para selecionar todos os nós que queremos remover. A expressão XPath &lt;code>//script|//style|//nav|//footer|//header&lt;/code> seleciona todos os elementos de script (código JavaScript que não precisamos), elementos de estilo (CSS que não precisamos), elementos de navegação (menus de navegação), elementos de rodapé e elementos de cabeçalho.&lt;/p>
&lt;p>Após remover esses nós, obtemos a propriedade InnerText, que extrai todo o conteúdo do texto enquanto remove as tags HTML. Isso nos dá o texto simples do artigo.Finalmente, limpamos os espaços em branco. O HTML geralmente tem muitos espaços em branco extras para fins de formatação – vários espaços, tabulações, novas linhas. Usamos um regex para substituir qualquer sequência de caracteres de espaço em branco por um único espaço e, em seguida, cortamos o resultado.&lt;/p>
&lt;h3 id="buscando-o-artigo-completo">Buscando o artigo completo&lt;/h3>
&lt;p>O feed RSS fornece apenas resumos, não o conteúdo completo do artigo. Para obter o texto completo, precisamos buscar a página web do artigo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; FetchArticleContentAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> httpClient.DefaultRequestHeaders.Add(&lt;span style="color:#a5d6ff">&amp;#34;User-Agent&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;VsFeedLinkedin/1.0&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">await&lt;/span> httpClient.GetStringAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;⚠️ Failed to fetch article content: {ex.Message}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso é simples – fazemos uma solicitação HTTP GET para o URL do artigo e retornamos a resposta HTML. Nós o envolvemos em um try-catch porque as solicitações de rede podem falhar e preferimos retornar uma string vazia do que travar todo o aplicativo.&lt;/p>
&lt;h2 id="criando-documentação-permanente">Criando Documentação Permanente&lt;/h2>
&lt;h3 id="por-que-gerar-arquivos-markdown">Por que gerar arquivos Markdown&lt;/h3>
&lt;p>Cada vez que analisamos um artigo, geramos um arquivo markdown detalhado que documenta essa análise. Isso serve a vários propósitos.&lt;/p>
&lt;p>Primeiro, ele cria um arquivo pesquisável. Com o tempo, você construirá uma coleção de artigos analisados. Você pode pesquisar nesses arquivos para encontrar conteúdo anterior sobre tópicos específicos.&lt;/p>
&lt;p>Em segundo lugar, proporciona transparência. Você pode ver exatamente o que a IA gerou para cada artigo, incluindo a análise completa e a postagem no LinkedIn.&lt;/p>
&lt;p>Terceiro, é útil para depuração. Se algo der errado com uma postagem, você pode consultar o arquivo markdown para entender o que aconteceu.&lt;/p>
&lt;h3 id="a-classe-geradora-de-markdown">A classe geradora de Markdown&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">MarkdownGenerator&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _outputDirectory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> MarkdownGenerator(&lt;span style="color:#ff7b72">string&lt;/span> outputDirectory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _outputDirectory = outputDirectory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!Directory.Exists(_outputDirectory))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Directory.CreateDirectory(_outputDirectory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GenerateMarkdownFile(ArticleAnalysis analysis)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sb = &lt;span style="color:#ff7b72">new&lt;/span> StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> safeTitle = GenerateSafeFileName(analysis.Title);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> fileName = &lt;span style="color:#a5d6ff">$&amp;#34;{analysis.AnalyzedAt:yyyy-MM-dd}_{safeTitle}.md&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> filePath = Path.Combine(_outputDirectory, fileName);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;# {analysis.Title}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;## Article Information&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **Author:** {analysis.Author}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **URL:** [{analysis.Url}]({analysis.Url})&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **Published:** {analysis.PublishDate:yyyy-MM-dd}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **Analyzed:** {analysis.AnalyzedAt:yyyy-MM-dd HH:mm:ss}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **Tags:** {string.Join(&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;, analysis.Tags)}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;## AI Analysis&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;### Summary&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(analysis.Summary);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;### Key Topics&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(analysis.KeyTopics);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;### Relevance for Developers&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(analysis.Relevance);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;## Generated LinkedIn Post&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;```&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(análise.LinkedInPost);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;```&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;*This analysis was generated using AI (Semantic Kernel with Azure OpenAI)*&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> File.WriteAllText(filePath, sb.ToString());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> filePath;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O construtor pega um caminho de diretório de saída e o cria se ele não existir. O método GenerateMarkdownFile pega um objeto ArticleAnalysis e produz um documento de redução bem formatado.&lt;/p>
&lt;p>O nome do arquivo inclui a data e uma versão higienizada do título. Isso torna os arquivos fáceis de classificar cronologicamente e identificar rapidamente.&lt;/p>
&lt;h3 id="lidando-com-nomes-de-arquivos-inseguros">Lidando com nomes de arquivos inseguros&lt;/h3>
&lt;p>Os títulos dos artigos podem conter caracteres que não são permitidos em nomes de arquivos – coisas como dois pontos, barras, pontos de interrogação e aspas. Precisamos higienizar estes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GenerateSafeFileName(&lt;span style="color:#ff7b72">string&lt;/span> title)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> invalidChars = Path.GetInvalidFileNameChars();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> safeTitle = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>(title
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(c =&amp;gt; !invalidChars.Contains(c))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToArray());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> safeTitle = safeTitle.Replace(&lt;span style="color:#a5d6ff">&amp;#34; &amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>).Replace(&lt;span style="color:#a5d6ff">&amp;#34;--&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (safeTitle.Length &amp;gt; &lt;span style="color:#a5d6ff">50&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> safeTitle = safeTitle.Substring(&lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">50&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> safeTitle.TrimEnd(&lt;span style="color:#a5d6ff">&amp;#39;-&amp;#39;&lt;/span>).ToLowerInvariant();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Usamos Path.GetInvalidFileNameChars() para obter uma lista de caracteres que não podem aparecer em nomes de arquivos no sistema operacional atual. Nós os filtramos, substituímos os espaços por hífens para facilitar a leitura, limitamos o comprimento a 50 caracteres e convertemos para letras minúsculas para maior consistência.&lt;/p>
&lt;h2 id="configurando-notificações-de-telegrama">Configurando notificações de telegrama&lt;/h2>
&lt;h3 id="por-que-escolhi-o-telegram">Por que escolhi o Telegram&lt;/h3>
&lt;p>Para o componente de notificação, considerei várias opções – email, SMS, Slack, Discord e Telegram. No final das contas, escolhi o Telegram por vários motivos.&lt;/p>
&lt;p>A API é totalmente gratuita, sem limites de taxa para uso razoável. Muitos serviços de notificação têm limites de quantas mensagens você pode enviar gratuitamente, mas o Telegram não restringe mensagens de bot a usuários individuais.&lt;/p>
&lt;p>A API do bot é incrivelmente simples. São apenas solicitações HTTP com cargas JSON. Não há fluxos de autenticação complexos nem webhooks necessários para funcionalidades básicas.O Telegram funciona em qualquer lugar – no meu telefone, no meu desktop, no meu navegador. Posso receber notificações onde quer que esteja e responder imediatamente.&lt;/p>
&lt;p>As mensagens suportam formatação avançada. Posso usar texto em negrito, itálico e até blocos de código para tornar minhas notificações mais legíveis.&lt;/p>
&lt;h3 id="criando-seu-bot-de-telegrama">Criando seu bot de telegrama&lt;/h3>
&lt;p>Configurar um bot do Telegram é surpreendentemente fácil. Abra o Telegram e pesquise @BotFather – este é o bot oficial do Telegram para criar e gerenciar bots. Inicie uma conversa com BotFather e envie o comando /newbot. O BotFather solicitará um nome para o seu bot (este é o nome de exibição) e um nome de usuário (deve ser exclusivo e terminar em &amp;ldquo;bot&amp;rdquo;). Depois de fornecer isso, o BotFather criará seu bot e fornecerá um token de API. Este token é como uma senha – mantenha-o em segredo e não o envie para repositórios públicos.&lt;/p>
&lt;p>Para encontrar seu ID de bate-papo para que o bot saiba para onde enviar mensagens, inicie uma conversa com seu novo bot procurando por ele e pressionando Iniciar. Em seguida, acesse a URL &lt;code>https://api.telegram.org/bot&amp;lt;YOUR_TOKEN&amp;gt;/getUpdates&lt;/code> no seu navegador ou usando curl. Procure o objeto &lt;code>chat&lt;/code> na resposta – o campo &lt;code>id&lt;/code> é o seu ID de bate-papo.&lt;/p>
&lt;h3 id="envio-de-mensagens-via-api">Envio de mensagens via API&lt;/h3>
&lt;p>Esta é a nossa função para enviar mensagens do Telegram:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task SendToTelegramAsync(&lt;span style="color:#ff7b72">string&lt;/span> botToken, &lt;span style="color:#ff7b72">string&lt;/span> chatId, &lt;span style="color:#ff7b72">string&lt;/span> message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> telegramApiUrl = &lt;span style="color:#a5d6ff">$&amp;#34;https://api.telegram.org/bot{botToken}/sendMessage&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> payload = &lt;span style="color:#ff7b72">new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chat_id = chatId,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text = message,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parse_mode = &lt;span style="color:#a5d6ff">&amp;#34;HTML&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> jsonContent = JsonSerializer.Serialize(payload);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> content = &lt;span style="color:#ff7b72">new&lt;/span> StringContent(jsonContent, Encoding.UTF8, &lt;span style="color:#a5d6ff">&amp;#34;application/json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> httpClient.PostAsync(telegramApiUrl, content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!response.IsSuccessStatusCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> errorContent = &lt;span style="color:#ff7b72">await&lt;/span> response.Content.ReadAsStringAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">$&amp;#34;Telegram API error: {response.StatusCode} - {errorContent}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A API Telegram Bot é baseada em REST. Fazemos uma solicitação POST para o endpoint sendMessage com um corpo JSON contendo o ID do chat (para onde enviar), o texto da mensagem (o que enviar) e, opcionalmente, um modo de análise (para formatação).&lt;/p>
&lt;p>Definir parse_mode como &amp;ldquo;HTML&amp;rdquo; nos permite usar tags HTML básicas em nossas mensagens – coisas como &lt;code>&amp;lt;b&amp;gt;bold&amp;lt;/b&amp;gt;&lt;/code> e &lt;code>&amp;lt;i&amp;gt;italic&amp;lt;/i&amp;gt;&lt;/code>. Isso pode tornar as notificações mais legíveis, embora, para nosso caso de uso atual, enviemos texto simples.&lt;/p>
&lt;p>Se a solicitação falhar, lançamos uma exceção com detalhes sobre o que deu errado. Isso ajuda na depuração se algo não estiver funcionando.&lt;/p>
&lt;h2 id="configurando-o-aplicativo">Configurando o aplicativo&lt;/h2>
&lt;h3 id="variáveis-de-ambiente">Variáveis de ambiente&lt;/h3>
&lt;p>Nosso aplicativo precisa de várias informações confidenciais – chaves de API, tokens de bot e URLs de endpoint. Nunca devemos codificá-los ou submetê-los ao controle de versão. Em vez disso, usamos variáveis ​​de ambiente, que podem ser definidas com segurança em cada ambiente onde o aplicativo é executado.&lt;/p>
&lt;p>Para o Telegram, precisamos de TELEGRAM_BOT_TOKEN (o token que o BotFather lhe deu) e TELEGRAM_CHAT_ID (seu ID do chat para onde as mensagens devem ser enviadas).&lt;/p>
&lt;p>Para o Azure OpenAI, precisamos de AZURE_OPENAI_ENDPOINT (a URL do seu recurso), AZURE_OPENAI_API_KEY (sua chave de API) e AZURE_OPENAI_DEPLOYMENT (o nome do seu modelo implantado, como &amp;ldquo;gpt-4o&amp;rdquo;).&lt;/p>
&lt;h3 id="carregando-configuração-no-código">Carregando configuração no código&lt;/h3>
&lt;p>Veja como carregamos esses valores na inicialização do aplicativo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> telegramBotToken = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;TELEGRAM_BOT_TOKEN&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> telegramChatId = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;TELEGRAM_CHAT_ID&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> azureOpenAiEndpoint = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_ENDPOINT&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> azureOpenAiKey = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_API_KEY&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> azureOpenAiDeployment = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_DEPLOYMENT&amp;#34;&lt;/span>) ?? &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> aiAnalysisEnabled = !&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(azureOpenAiEndpoint) &amp;amp;&amp;amp;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> !&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(azureOpenAiKey);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Usamos Environment.GetEnvironmentVariable para ler cada valor. Para o nome da implantação, fornecemos um padrão de &amp;ldquo;gpt-4o&amp;rdquo; se nenhum valor for definido.&lt;/p>
&lt;p>Em seguida, verificamos se a análise de IA deve ser habilitada, verificando se temos um endpoint e uma chave de API. Isso permite que o aplicativo seja executado em modo degradado se o Azure OpenAI não estiver configurado – ele ainda buscará feeds e rastreará artigos, mas sem a análise de IA.### Degradação Graciosa&lt;/p>
&lt;p>Este conceito de degradação graciosa é importante. Não queremos que o aplicativo trave só porque um recurso opcional não está configurado:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>ArticleAnalyzer? articleAnalyzer = &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MarkdownGenerator? markdownGenerator = &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (aiAnalysisEnabled)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;🤖 AI Analysis enabled - Using Azure OpenAI with Semantic Kernel&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> articleAnalyzer = &lt;span style="color:#ff7b72">new&lt;/span> ArticleAnalyzer(azureOpenAiEndpoint!, azureOpenAiKey!, azureOpenAiDeployment);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> markdownGenerator = &lt;span style="color:#ff7b72">new&lt;/span> MarkdownGenerator(articlesOutputDir);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;ℹ️ AI Analysis disabled - Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY to enable&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Se a IA estiver habilitada, criamos o analisador e o gerador de descontos. Caso contrário, os deixamos nulos e pulamos as etapas relacionadas à IA durante o processamento. O aplicativo ainda agrega valor ao buscar feeds e enviar notificações básicas, mesmo sem o aprimoramento de IA.&lt;/p>
&lt;h2 id="automatizando-com-ações-do-github">Automatizando com ações do GitHub&lt;/h2>
&lt;h3 id="por-que-as-ações-do-github">Por que as ações do GitHub&lt;/h3>
&lt;p>O verdadeiro poder desta solução vem da automação. Não queremos executar manualmente o aplicativo a cada poucas horas – queremos que ele seja executado automaticamente em segundo plano.&lt;/p>
&lt;p>GitHub Actions é perfeito para isso. Ele está integrado ao GitHub, portanto não há serviço adicional para configurar. É gratuito para repositórios públicos e inclui minutos gratuitos generosos para repositórios privados. Ele pode ser executado de acordo com uma programação, acionando nosso aplicativo em intervalos regulares. Possui gerenciamento de segredos integrado para armazenar nossas chaves de API com segurança. E pode enviar alterações de volta ao repositório, mantendo nosso arquivo de rastreamento atualizado.&lt;/p>
&lt;h3 id="o-arquivo-de-fluxo-de-trabalho">O arquivo de fluxo de trabalho&lt;/h3>
&lt;p>Crie um arquivo em .github/workflows/fetch-and-notify.yml com o seguinte conteúdo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Fetch DevBlogs and Notify&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">on&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">schedule&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">cron&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#39;0 */6 * * *&amp;#39;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">workflow_dispatch&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">jobs&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">fetch-and-notify&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">runs-on&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">ubuntu-latest&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">steps&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Checkout repository&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">actions/checkout@v4&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Setup .NET&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">actions/setup-dotnet@v4&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">with&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">dotnet-version&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#39;9.0.x&amp;#39;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Restore dependencies&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">dotnet restore src/VsFeedLinkedin.csproj&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Build&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">dotnet build src/VsFeedLinkedin.csproj --no-restore&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Run application&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">env&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">TELEGRAM_BOT_TOKEN&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.TELEGRAM_BOT_TOKEN }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">TELEGRAM_CHAT_ID&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.TELEGRAM_CHAT_ID }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">AZURE_OPENAI_ENDPOINT&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.AZURE_OPENAI_ENDPOINT }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">AZURE_OPENAI_API_KEY&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.AZURE_OPENAI_API_KEY }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">AZURE_OPENAI_DEPLOYMENT&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.AZURE_OPENAI_DEPLOYMENT }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">dotnet run --project src/VsFeedLinkedin.csproj&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Commit and push changes&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>|&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git config user.name &amp;#34;GitHub Actions Bot&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git config user.email &amp;#34;actions@github.com&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> if [[ -n $(git status --porcelain posted-articles.md generated-posts/) ]]; then
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> TIMESTAMP=$(date +%Y%m%d_%H%M%S)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git add posted-articles.md generated-posts/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git commit -m &amp;#34;chore($TIMESTAMP): processed new articles&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git push
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> else
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> echo &amp;#34;No changes to commit&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> fi&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Deixe-me explicar cada parte. A seção on define quando o fluxo de trabalho é executado. O gatilho de agendamento usa sintaxe cron – &lt;code>0 */6 * * *&lt;/code> significa &amp;ldquo;no minuto 0 de cada 6 horas.&amp;rdquo; Portanto, o fluxo de trabalho é executado à meia-noite, 6h, meio-dia e 18h UTC. O gatilho workflow_dispatch permite execuções manuais na UI do GitHub, o que é útil para testes.&lt;/p>
&lt;p>O trabalho é executado no ubuntu-latest, que é uma máquina virtual Linux. Verificamos nosso repositório, configuramos o .NET 9, restauramos os pacotes NuGet e construímos o projeto.&lt;/p>
&lt;p>A etapa Executar aplicativo é onde a mágica acontece. Passamos nossos segredos como variáveis ​​de ambiente usando a sintaxe ${{ secrets.SECRET_NAME }}. Esses segredos são armazenados com segurança no GitHub e nunca expostos em logs.&lt;/p>
&lt;p>Finalmente, enviamos todas as alterações de volta ao repositório. Configuramos o Git com uma identidade de bot, verificamos se há alguma alteração em nosso arquivo de rastreamento ou no diretório de postagens gerado e, em caso afirmativo, criamos um commit e enviamos por push.&lt;/p>
&lt;h3 id="configurando-segredos">Configurando segredos&lt;/h3>
&lt;p>Para adicionar segredos ao seu repositório GitHub, vá para Configurações do seu repositório, depois Segredos e variáveis e, em seguida, Ações. Clique em “Novo segredo do repositório” e adicione cada uma de suas variáveis ​​de ambiente. Os nomes devem corresponder exatamente ao que referenciamos no arquivo de fluxo de trabalho.&lt;/p>
&lt;h2 id="concluindo">Concluindo&lt;/h2>
&lt;h3 id="o-que-construímos">O que construímos&lt;/h3>
&lt;p>Analisando tudo o que abordamos, construímos um agregador de feed RSS abrangente e baseado em IA que automatiza o que costumava ser um processo manual tedioso. O aplicativo monitora automaticamente sete feeds do Microsoft DevBlogs, capturando cada novo artigo assim que é publicado. Ele lida com as complexidades da desduplicação, reconhecendo quando o mesmo artigo aparece em vários feeds.A análise de IA alimentada pelo Semantic Kernel e Azure OpenAI lê e entende o conteúdo do artigo, gerando resumos, identificando os principais tópicos e explicando a relevância – tudo automaticamente. Mais importante ainda, ele cria postagens envolventes no LinkedIn que posso compartilhar com o mínimo de edição.&lt;/p>
&lt;p>A integração do Telegram significa que sou notificado no meu telefone sempre que há novo conteúdo para revisar. Posso dar uma olhada na mensagem, decidir se quero compartilhá-la e agir imediatamente.&lt;/p>
&lt;p>E como ele é executado no GitHub Actions de acordo com uma programação, não preciso me lembrar de fazer nada. O sistema funciona em segundo plano e só envolvo quando há algo que vale a pena compartilhar.&lt;/p>
&lt;h3 id="as-tecnologias-que-tornaram-isso-possível">As tecnologias que tornaram isso possível&lt;/h3>
&lt;p>Este projeto reuniu diversas tecnologias e cada uma desempenhou um papel crucial. O .NET 9 forneceu uma base sólida com recursos de linguagem modernos e excelente desempenho. O Semantic Kernel simplificou a integração da IA, lidando com toda a complexidade das chamadas de API e gerenciamento de respostas. O Azure OpenAI forneceu a inteligência – a capacidade de realmente compreender e analisar conteúdo técnico. HtmlAgilityPack resolveu o complicado problema de extrair texto limpo de páginas da web. System.ServiceModel.Syndication facilitou muito a análise de RSS. A API do Telegram Bot nos forneceu notificações gratuitas e confiáveis. E o GitHub Actions uniu tudo isso com uma execução automatizada e programada.&lt;/p>
&lt;h3 id="pensando-em-custos">Pensando em custos&lt;/h3>
&lt;p>Uma pergunta que você pode ter é: quanto custa para funcionar? A resposta é: não muito.&lt;/p>
&lt;p>O Telegram é totalmente gratuito – sem custos para enviar mensagens através do seu bot.&lt;/p>
&lt;p>GitHub Actions é gratuito para repositórios públicos. Para repositórios privados, você recebe 2.000 minutos por mês no nível gratuito, o que é mais que suficiente para nosso caso de uso.&lt;/p>
&lt;p>O Azure OpenAI é o único componente pago e os custos são mínimos. Usando o GPT-4o, analisar um artigo típico de blog custa algo entre um e três centavos. Mesmo que você processe dezenas de artigos por mês, terá menos de um dólar em custos de IA.&lt;/p>
&lt;h3 id="onde-você-pode-levar-isso-a-seguir">Onde você pode levar isso a seguir&lt;/h3>
&lt;p>Embora esta solução funcione muito bem para minhas necessidades, há muitas maneiras de estendê-la. Você pode adicionar suporte para múltiplas plataformas sociais – talvez postar no Twitter/X, Mastodon ou Bluesky, além do LinkedIn. Você pode implementar a análise de sentimento para rastrear o tom dos artigos ao longo do tempo e identificar tendências. Você pode permitir diferentes modelos de prompt para diferentes feeds, gerando diferentes estilos de postagens para diferentes tópicos. Você poderia construir um painel web para revisar e gerenciar postagens em vez de usar o Telegram. Você pode acompanhar as métricas de engajamento do conteúdo postado para ver quais tópicos têm maior repercussão em seu público.&lt;/p>
&lt;h3 id="considerações-finaiso-que-mais-adoro-neste-projeto-é-que-ele-incorpora-uma-filosofia-na-qual-acredito-fortemente-a-automação-deve-cuidar-das-partes-tediosas-deixando-as-partes-criativas-e-de-tomada-de-decisão-para-os-humanos-o-sistema-faz-todo-o-trabalho-pesado--buscar-analisar-analisar-gerar--mas-eu-ainda-reviso-tudo-antes-de-compartilhar-as-postagens-geradas-por-ia-são-pontos-de-partida-que-posso-customizar-e-personalizar">Considerações FinaisO que mais adoro neste projeto é que ele incorpora uma filosofia na qual acredito fortemente: a automação deve cuidar das partes tediosas, deixando as partes criativas e de tomada de decisão para os humanos. O sistema faz todo o trabalho pesado – buscar, analisar, analisar, gerar – mas eu ainda reviso tudo antes de compartilhar. As postagens geradas por IA são pontos de partida que posso customizar e personalizar.&lt;/h3>
&lt;p>Ao combinar o poder do .NET, do Kernel Semântico e do Azure OpenAI, criamos uma ferramenta que economiza horas de trabalho manual todas as semanas, mantendo a qualidade e a consistência. É o tipo de automação prática que faz uma diferença real na vida diária.&lt;/p>
&lt;p>Se você construir algo semelhante ou tiver ideias para melhorias, adoraria ouvir sobre isso. Sinta-se à vontade para entrar em contato no LinkedIn!&lt;/p>
&lt;p>Boa codificação e feliz Natal! 🎄&lt;/p></content:encoded><category>.NET</category><category>Azure</category><category>NuGet</category><category>CI/CD</category><category>Docker</category><category>AI</category></item><item><title>Construindo sistemas de IA multiagentes com a estrutura de agentes da Microsoft</title><link>https://emimontesdeoca.github.io/pt/posts/microsoft-agent-framework-multi-agent/</link><pubDate>Mon, 01 Dec 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/microsoft-agent-framework-multi-agent/</guid><description>Um guia prático para construir, orquestrar e implantar sistemas de IA multiagentes usando o Agent Framework da Microsoft em .NET.</description><content:encoded>&lt;h2 id="introdução">Introdução&lt;/h2>
&lt;p>Entramos na era dos sistemas de IA multiagentes. Em vez de uma única IA monolítica cuidar de tudo, a indústria está migrando para agentes especializados que colaboram para resolver problemas complexos – como uma equipe bem organizada de especialistas. Um agente pesquisa, outro analisa, um terceiro escreve e um coordenador mantém todos no caminho certo.&lt;/p>
&lt;p>Se você trabalhou com grandes modelos de linguagem, provavelmente atingiu o limite do que um único prompt pode fazer. As janelas de contexto ficam cheias, as instruções ficam confusas e a qualidade diminui. As arquiteturas multiagentes resolvem isso decompondo tarefas complexas em responsabilidades específicas, onde cada agente é especialista em uma coisa.&lt;/p>
&lt;p>O Agent Framework da Microsoft, parte do ecossistema mais amplo do Kernel Semântico, oferece aos desenvolvedores .NET um kit de ferramentas de primeira classe para construir exatamente esses tipos de sistemas. Nesta postagem, iremos do zero a um pipeline multiagente totalmente funcional, abordando os principais conceitos, padrões de orquestração e código prático de que você precisa para começar.&lt;/p>
&lt;h2 id="o-que-é-o-agent-framework-da-microsoft">O que é o Agent Framework da Microsoft?&lt;/h2>
&lt;p>O Agent Framework é a resposta da Microsoft para construir, orquestrar e implantar agentes de IA e sistemas multiagentes em .NET. Ele acompanha – e se integra profundamente – o Kernel Semântico, que tem sido o SDK de código aberto da Microsoft para orquestração de IA desde 2023.&lt;/p>
&lt;p>Pense desta forma: o &lt;strong>Kernel Semântico&lt;/strong> fornece os primitivos (kernels, plug-ins, memória, planejadores), enquanto o &lt;strong>Estrutura do Agente&lt;/strong> fornece abstrações de nível superior projetadas especificamente para comunicação e coordenação entre agentes.&lt;/p>
&lt;p>A estrutura dá suporte a vários provedores de modelos, incluindo Azure OpenAI, OpenAI e modelos hospedados no Azure AI Foundry. Seu design é independente de modelo, mas está profundamente integrado ao ecossistema do Azure, o que o torna particularmente atraente para cenários empresariais.&lt;/p>
&lt;p>Os principais recursos incluem:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Vários tipos de agentes&lt;/strong>: &lt;code>ChatCompletionAgent&lt;/code>, &lt;code>OpenAIAssistantAgent&lt;/code> e &lt;code>AzureAIAgent&lt;/code> para diferentes back-ends&lt;/li>
&lt;li>&lt;strong>Padrões de orquestração&lt;/strong>: fluxos de trabalho sequenciais, simultâneos, de transferência e de bate-papo em grupo&lt;/li>
&lt;li>&lt;strong>Ecossistema de plug-ins&lt;/strong>: Estenda agentes com funções C# nativas, especificações OpenAPI e ferramentas Model Context Protocol (MCP)&lt;/li>
&lt;li>&lt;strong>Gerenciamento de conversas&lt;/strong>: threading integrado, gerenciamento de histórico e estratégias de encerramento&lt;/li>
&lt;li>&lt;strong>Observabilidade&lt;/strong>: Integração com OpenTelemetry para rastrear interações de agentes&lt;/li>
&lt;/ul>
&lt;h2 id="conceitos-chave">Conceitos-chave&lt;/h2>
&lt;p>Antes de escrevermos qualquer código, vamos estabelecer o vocabulário. O Agent Framework gira em torno de algumas abstrações básicas.&lt;/p>
&lt;h3 id="agentes">Agentes&lt;/h3>
&lt;p>Um agente é uma entidade apoiada por um modelo de IA, configurado com instruções específicas (um prompt do sistema), um nome e, opcionalmente, um conjunto de plug-ins ou ferramentas. Cada agente é um especialista – você define o que ele sabe, o que pode fazer e como deve se comportar.&lt;/p>
&lt;h3 id="chatcompletionagenteo-tipo-de-agente-mais-direto-ele-envolve-um-ponto-final-de-conclusão-de-chat-azure-openai-openai-etc-e-mantém-uma-conversa-não-há-monitoração-de-estado-entre-invocações--você-fornece-o-histórico-e-ele-responde-isso-o-torna-leve-e-fácil-de-raciocinar">ChatCompletionAgenteO tipo de agente mais direto. Ele envolve um ponto final de conclusão de chat (Azure OpenAI, OpenAI, etc.) e mantém uma conversa. Não há monitoração de estado entre invocações — você fornece o histórico e ele responde. Isso o torna leve e fácil de raciocinar.&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>ChatCompletionAgent agent = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Reviewer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;You are a senior code reviewer. Analyze the provided code for bugs, security issues, and style violations. Be concise and actionable.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="openaiaassistantagente">OpenAIAAssistantAgente&lt;/h3>
&lt;p>Este tipo de agente aproveita a API OpenAI Assistants, que fornece estado de conversação no lado do servidor, manipulação de arquivos e interpretação de código. É mais pesado, mas oferece threads persistentes e ferramentas integradas, como interpretador de código e pesquisa de arquivos.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>OpenAIAssistantAgent agent = &lt;span style="color:#ff7b72">await&lt;/span> OpenAIAssistantAgent.CreateAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> clientProvider: clientProvider,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> definition: &lt;span style="color:#ff7b72">new&lt;/span> OpenAIAssistantDefinition(&lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;DataAnalyst&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;You analyze datasets and produce statistical summaries.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="agentgroupchat">AgentGroupChat&lt;/h3>
&lt;p>Este é o orquestrador. &lt;code>AgentGroupChat&lt;/code> gerencia conversas múltiplas entre vários agentes, controlando quem fala em seguida, quando a conversa termina e como o histórico é compartilhado. É onde acontece a mágica da colaboração multiagente.&lt;/p>
&lt;h2 id="padrões-de-orquestração">Padrões de orquestração&lt;/h2>
&lt;p>A estrutura oferece suporte a quatro padrões principais de orquestração, cada um adequado para problemas diferentes.&lt;/p>
&lt;h3 id="sequencial">Sequencial&lt;/h3>
&lt;p>Os agentes executam um após o outro em uma ordem definida. A saída do Agente A alimenta o Agente B, cuja saída alimenta o Agente C. Isso é ideal para pipelines: rascunho → revisão → edição → publicação.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Conceptual flow&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> draft = &lt;span style="color:#ff7b72">await&lt;/span> writerAgent.InvokeAsync(&lt;span style="color:#a5d6ff">&amp;#34;Write a blog post about .NET 9&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> reviewed = &lt;span style="color:#ff7b72">await&lt;/span> reviewerAgent.InvokeAsync(&lt;span style="color:#a5d6ff">$&amp;#34;Review this: {draft}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> edited = &lt;span style="color:#ff7b72">await&lt;/span> editorAgent.InvokeAsync(&lt;span style="color:#a5d6ff">$&amp;#34;Edit based on feedback: {reviewed}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="simultâneo">Simultâneo&lt;/h3>
&lt;p>Vários agentes trabalham na mesma entrada simultaneamente. Você distribui o trabalho e depois agrega os resultados. Ótimo para obter perspectivas diversas – como ter três revisores analisando a mesma solicitação pull.&lt;/p>
&lt;h3 id="transferência">Transferência&lt;/h3>
&lt;p>Um agente decide transferir o controle para outro agente com base no contexto da conversa. Isso imita o funcionamento de uma equipe de atendimento ao cliente: o agente da linha de frente lida com dúvidas básicas e encaminha para especialistas quando necessário.&lt;/p>
&lt;h3 id="bate-papo-em-grupo">Bate-papo em grupo&lt;/h3>
&lt;p>Vários agentes participam de uma conversa aberta, revezando-se com base em uma estratégia de seleção. A classe &lt;code>AgentGroupChat&lt;/code> implementa esse padrão com lógica configurável de tomada de turno e terminação.&lt;/p>
&lt;h2 id="construindo-seu-primeiro-agente">Construindo seu primeiro agente&lt;/h2>
&lt;p>Vamos ser práticos. Veja como construir seu primeiro agente passo a passo.&lt;/p>
&lt;h3 id="pré-requisitos">Pré-requisitos&lt;/h3>
&lt;p>Você precisará de:&lt;/p>
&lt;ul>
&lt;li>SDK do .NET 9&lt;/li>
&lt;li>Um recurso Azure OpenAI com um modelo implantado (por exemplo, &lt;code>gpt-4o&lt;/code>)&lt;/li>
&lt;li>Visual Studio ou VS Code&lt;/li>
&lt;/ul>
&lt;h3 id="configurando-o-projeto">Configurando o Projeto&lt;/h3>
&lt;p>Crie um novo aplicativo de console e instale os pacotes necessários:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new console -n AgentDemo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd AgentDemo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel --prerelease
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Agents.Core --prerelease
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.AI.OpenAI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.Identity
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="criando-um-agente-simples">Criando um Agente Simples&lt;/h3>
&lt;p>Primeiro, configure o kernel com a configuração do Azure OpenAI:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> credentials: &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Kernel kernel = builder.Build();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora crie um agente e invoque-o:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>ChatCompletionAgent agent = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;TechWriter&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical writer specializing &lt;span style="color:#ff7b72">in&lt;/span> software documentation.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Write clear, concise content aimed at experienced developers.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Use code examples when appropriate.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Always structure your output with headings and bullet points.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatHistory history = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>history.AddUserMessage(&lt;span style="color:#a5d6ff">&amp;#34;Explain dependency injection in .NET in 200 words.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (ChatMessageContent response &lt;span style="color:#ff7b72">in&lt;/span> agent.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(response.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É isso. Você tem um agente trabalhando. Mas o verdadeiro poder surge quando os agentes trabalham juntos.&lt;/p>
&lt;h2 id="orquestração-multiagente-com-agentgroupchat">Orquestração Multiagente com AgentGroupChat&lt;/h2>
&lt;p>Vamos criar algo mais interessante: um bate-papo em grupo onde vários agentes colaboram. A classe &lt;code>AgentGroupChat&lt;/code> gerencia o fluxo da conversa, incluindo quem fala em seguida e quando parar.&lt;/p>
&lt;h3 id="definindo-os-agentes">Definindo os Agentes&lt;/h3>
&lt;p>Criaremos três agentes: um redator, um revisor e um editor.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>ChatCompletionAgent writer = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Writer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a content writer. When given a topic, produce a well-structured
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> draft. Focus &lt;span style="color:#ff7b72">on&lt;/span> clarity and technical accuracy.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> When you receive feedback &lt;span style="color:#ff7b72">from&lt;/span> the Reviewer, incorporate it &lt;span style="color:#ff7b72">into&lt;/span> a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> revised draft.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent reviewer = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Reviewer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a content reviewer. Analyze drafts &lt;span style="color:#ff7b72">for&lt;/span> technical accuracy,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> clarity, and completeness. Provide specific, actionable feedback.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Do NOT rewrite the content &lt;span style="color:#f85149">—&lt;/span> only provide feedback.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> When the content meets your standards, respond with: APPROVED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent editor = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Editor&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are an editor. Once content &lt;span style="color:#ff7b72">is&lt;/span> approved &lt;span style="color:#ff7b72">by&lt;/span> the Reviewer,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> polish it &lt;span style="color:#ff7b72">for&lt;/span> grammar, tone, and formatting.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Output only the final polished version.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> When you have produced the final version, respond with: COMPLETE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="configurando-o-bate-papo-em-grupo">Configurando o bate-papo em grupo&lt;/h3>
&lt;p>O &lt;code>AgentGroupChat&lt;/code> precisa de duas configurações principais: uma &lt;strong>estratégia de seleção&lt;/strong> (quem fala a seguir) e uma &lt;strong>estratégia de rescisão&lt;/strong> (quando parar).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>AgentGroupChat chat = &lt;span style="color:#ff7b72">new&lt;/span>(writer, reviewer, editor)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecutionSettings = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SelectionStrategy = &lt;span style="color:#ff7b72">new&lt;/span> SequentialSelectionStrategy(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TerminationStrategy = &lt;span style="color:#ff7b72">new&lt;/span> ApprovalTerminationStrategy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Agents = [editor],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaximumIterations = &lt;span style="color:#a5d6ff">12&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="estratégia-de-rescisão-personalizadaa-estratégia-de-encerramento-define-quando-a-conversa-termina-aqui-está-um-personalizado-que-procura-a-palavra-chave-complete">Estratégia de rescisão personalizadaA estratégia de encerramento define quando a conversa termina. Aqui está um personalizado que procura a palavra-chave “COMPLETE”:&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ApprovalTerminationStrategy&lt;/span> : TerminationStrategy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt; ShouldAgentTerminateAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Agent agent,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IReadOnlyList&amp;lt;ChatMessageContent&amp;gt; history,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CancellationToken cancellationToken = &lt;span style="color:#ff7b72">default&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">bool&lt;/span> isComplete = history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Last()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Content?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Contains(&lt;span style="color:#a5d6ff">&amp;#34;COMPLETE&amp;#34;&lt;/span>, StringComparison.OrdinalIgnoreCase) ?? &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Task.FromResult(isComplete);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="conduzindo-a-conversa">Conduzindo a conversa&lt;/h3>
&lt;p>Inicie a conversa com uma mensagem do usuário e deixe os agentes colaborarem:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>chat.AddChatMessage(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessageContent(AuthorRole.User,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Write a 300-word technical overview of gRPC vs REST for microservices.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (ChatMessageContent message &lt;span style="color:#ff7b72">in&lt;/span> chat.InvokeAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;[{message.AuthorName}]: {message.Content}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;---&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O fluxo será mais ou menos assim:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Escritor&lt;/strong> produz um rascunho&lt;/li>
&lt;li>&lt;strong>Revisor&lt;/strong> fornece feedback&lt;/li>
&lt;li>&lt;strong>Escritor&lt;/strong> revisa com base no feedback&lt;/li>
&lt;li>&lt;strong>Revisor&lt;/strong> diz &amp;ldquo;APROVADO&amp;rdquo;&lt;/li>
&lt;li>&lt;strong>Editor&lt;/strong> dá um polimento e diz &amp;ldquo;COMPLETO&amp;rdquo;&lt;/li>
&lt;li>A conversa termina&lt;/li>
&lt;/ol>
&lt;p>Esse vaivém continua automaticamente até que a condição de encerramento seja atendida ou &lt;code>MaximumIterations&lt;/code> seja alcançado.&lt;/p>
&lt;h2 id="plug-ins-e-ferramentas">Plug-ins e ferramentas&lt;/h2>
&lt;p>Os agentes tornam-se verdadeiramente poderosos quando podem interagir com sistemas externos. O Agent Framework oferece suporte a três mecanismos principais de extensão.&lt;/p>
&lt;h3 id="funções-nativas-plugins-do-kernel">Funções nativas (plugins do kernel)&lt;/h3>
&lt;p>Você pode conceder aos agentes acesso a métodos C# como ferramentas. O agente chamará essas funções quando determinar que são necessárias:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ContentToolsPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;word_count&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Counts the number of words in the provided text.&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> WordCount([Description(&lt;span style="color:#a5d6ff">&amp;#34;The text to count words in&amp;#34;&lt;/span>)] &lt;span style="color:#ff7b72">string&lt;/span> text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> text.Split(&lt;span style="color:#a5d6ff">&amp;#39; &amp;#39;&lt;/span>, StringSplitOptions.RemoveEmptyEntries).Length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;check_readability&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Calculates a readability score for the given text.&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> CheckReadability([Description(&lt;span style="color:#a5d6ff">&amp;#34;The text to analyze&amp;#34;&lt;/span>)] &lt;span style="color:#ff7b72">string&lt;/span> text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> words = text.Split(&lt;span style="color:#a5d6ff">&amp;#39; &amp;#39;&lt;/span>, StringSplitOptions.RemoveEmptyEntries).Length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sentences = text.Split([&lt;span style="color:#a5d6ff">&amp;#39;.&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#39;!&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#39;?&amp;#39;&lt;/span>], StringSplitOptions.RemoveEmptyEntries).Length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (sentences == &lt;span style="color:#a5d6ff">0&lt;/span>) &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Unable to calculate — no sentences found.&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">double&lt;/span> avgWordsPerSentence = (&lt;span style="color:#ff7b72">double&lt;/span>)words / sentences;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> avgWordsPerSentence &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt; &lt;span style="color:#a5d6ff">15&lt;/span> =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Easy to read (avg {avgWordsPerSentence:F1} words/sentence)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt; &lt;span style="color:#a5d6ff">25&lt;/span> =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Moderate difficulty (avg {avgWordsPerSentence:F1} words/sentence)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Difficult to read (avg {avgWordsPerSentence:F1} words/sentence). Consider shorter sentences.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Registre o plugin no kernel antes de criar o agente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>kernel.Plugins.AddFromType&amp;lt;ContentToolsPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent analyst = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;ContentAnalyst&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;Analyze content using available tools. Report word count and readability.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Kernel = kernel,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Arguments = &lt;span style="color:#ff7b72">new&lt;/span> KernelArguments(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> PromptExecutionSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="protocolo-de-contexto-do-modelo-mcp">Protocolo de Contexto do Modelo (MCP)&lt;/h3>
&lt;p>MCP é um padrão aberto para conectar modelos de IA a ferramentas e fontes de dados externas. O Agent Framework oferece suporte a MCP, o que significa que seus agentes podem usar ferramentas expostas por qualquer servidor compatível com MCP. Isso abre as portas para sistemas de arquivos, bancos de dados, APIs e muito mais — tudo por meio de uma interface padronizada.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Example: Adding an MCP server for file operations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kernel.Plugins.AddFromMcpServer(&lt;span style="color:#a5d6ff">&amp;#34;filesystem&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Uri(&lt;span style="color:#a5d6ff">&amp;#34;http://localhost:3000/mcp&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso é particularmente interessante porque significa que seus agentes não estão limitados ao que você cria — eles podem aproveitar um ecossistema de ferramentas MCP que outros desenvolvem e compartilham.&lt;/p>
&lt;h2 id="exemplo-do-mundo-real-pipeline-de-revisão-de-conteúdo">Exemplo do mundo real: pipeline de revisão de conteúdo&lt;/h2>
&lt;p>Vamos juntar tudo com um cenário prático. Imagine que você está construindo uma ferramenta interna que automatiza a revisão de conteúdo para uma equipe de documentação. O pipeline tem quatro etapas:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Pesquisador&lt;/strong> — Reúne informações técnicas relevantes&lt;/li>
&lt;li>&lt;strong>Escritor&lt;/strong> — Produz um rascunho baseado em pesquisas&lt;/li>
&lt;li>&lt;strong>Revisor&lt;/strong> — Verifica a precisão e integridade&lt;/li>
&lt;li>&lt;strong>Editor&lt;/strong> — Formata e prepara o resultado final&lt;/li>
&lt;/ol>
&lt;p>Aqui está uma implementação condensada:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Build the kernel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> credentials: &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Kernel kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Define specialized agents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent researcher = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Researcher&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical researcher. Given a topic, identify the key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> concepts, recent developments, and important details that should
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> be covered. Output a structured research brief.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent writer = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Writer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical writer. Using the research brief provided,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> write a comprehensive, well-structured article. Include code
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> examples &lt;span style="color:#ff7b72">where&lt;/span> relevant. Target audience: experienced developers.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent reviewer = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Reviewer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a senior technical reviewer. Check the article &lt;span style="color:#ff7b72">for&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Technical accuracy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Completeness relative to the research brief
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Code correctness
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Clarity and structure
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> If everything looks good, respond with APPROVED.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Otherwise, provide specific feedback &lt;span style="color:#ff7b72">for&lt;/span> revision.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent publisher = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Publisher&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a content publisher. Once the article &lt;span style="color:#ff7b72">is&lt;/span> approved,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> format it with proper markdown, &lt;span style="color:#ff7b72">add&lt;/span> a summary at the top,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and ensure consistent formatting throughout.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> End your response with COMPLETE.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the group chat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>AgentGroupChat chat = &lt;span style="color:#ff7b72">new&lt;/span>(researcher, writer, reviewer, publisher)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecutionSettings = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SelectionStrategy = &lt;span style="color:#ff7b72">new&lt;/span> SequentialSelectionStrategy(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TerminationStrategy = &lt;span style="color:#ff7b72">new&lt;/span> ApprovalTerminationStrategy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Agents = [publisher],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaximumIterations = &lt;span style="color:#a5d6ff">15&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Start the pipeline&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chat.AddChatMessage(&lt;span style="color:#ff7b72">new&lt;/span> ChatMessageContent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthorRole.User,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Create a technical article about implementing health checks in ASP.NET Core microservices.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (ChatMessageContent message &lt;span style="color:#ff7b72">in&lt;/span> chat.InvokeAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.ForegroundColor = message.AuthorName &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Researcher&amp;#34;&lt;/span> =&amp;gt; ConsoleColor.Cyan,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Writer&amp;#34;&lt;/span> =&amp;gt; ConsoleColor.Green,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Reviewer&amp;#34;&lt;/span> =&amp;gt; ConsoleColor.Yellow,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Publisher&amp;#34;&lt;/span> =&amp;gt; ConsoleColor.Magenta,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; ConsoleColor.White
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;\n{&amp;#39;=&amp;#39;new string(&amp;#39;=&amp;#39;, 60)}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;[{message.AuthorName}]&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>(&lt;span style="color:#a5d6ff">&amp;#39;=&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">60&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.ResetColor();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esse pipeline produz um artigo totalmente pesquisado, escrito, revisado e formatado – tudo por meio da colaboração do agente. Cada agente se concentra no que faz de melhor e o &lt;code>AgentGroupChat&lt;/code> coordena o fluxo de trabalho.&lt;/p>
&lt;h2 id="melhores-práticas">Melhores práticas&lt;/h2>
&lt;p>Depois de construir vários sistemas multiagentes, aqui estão os padrões e práticas que considero mais valiosos.&lt;/p>
&lt;h3 id="tratamento-de-erros">Tratamento de erros&lt;/h3>
&lt;p>Sempre defina &lt;code>MaximumIterations&lt;/code> em sua estratégia de rescisão. Sem ele, os agentes podem entrar em ciclos infinitos — especialmente quando um revisor continua encontrando problemas e um redator continua revisando sem melhorar.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>TerminationStrategy = &lt;span style="color:#ff7b72">new&lt;/span> ApprovalTerminationStrategy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaximumIterations = &lt;span style="color:#a5d6ff">12&lt;/span> &lt;span style="color:#8b949e;font-style:italic">// Safety net&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Envolva as invocações do seu agente em blocos try-catch. Limites de taxa de API, problemas de rede e erros de modelo são realidades dos sistemas de produção:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> chat.InvokeAsync(cancellationToken))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Process messages&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">catch&lt;/span> (HttpOperationException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Implement retry with exponential backoff&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(TimeSpan.FromSeconds(&lt;span style="color:#a5d6ff">30&lt;/span>), cancellationToken);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="observabilidadeo-agent-framework-se-integra-ao-opentelemetry-o-que-significa-que-você-pode-rastrear-cada-interação-do-agente-chamada-de-ferramenta-e-uso-de-token-isso-é-essencial-para-depurar-fluxos-de-trabalho-multiagentes-onde-nem-sempre-é-óbvio-qual-agente-causou-o-problema">ObservabilidadeO Agent Framework se integra ao OpenTelemetry, o que significa que você pode rastrear cada interação do agente, chamada de ferramenta e uso de token. Isso é essencial para depurar fluxos de trabalho multiagentes, onde nem sempre é óbvio qual agente causou o problema.&lt;/h3>
&lt;p>Configure a telemetria básica adicionando os pacotes de telemetria do Kernel Semântico e configurando seu exportador preferido (Application Insights, Jaeger, etc.):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddOpenTelemetry()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithTracing(tracing =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tracing.AddSource(&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.SemanticKernel*&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tracing.AddOtlpExporter();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="gerenciamento-de-custos">Gerenciamento de Custos&lt;/h3>
&lt;p>Os sistemas multiagentes multiplicam seus custos de API: cada turno de agente é uma chamada de API, e os bate-papos em grupo podem gerar muitos turnos. Algumas estratégias para manter os custos sob controle:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Use modelos mais baratos para agentes mais simples&lt;/strong>: Nem todo agente precisa do GPT-4o. Um revisor pode funcionar bem com um modelo menor, enquanto apenas o redator precisa do rebatedor pesado.&lt;/li>
&lt;li>&lt;strong>Limitar histórico&lt;/strong>: use &lt;code>ReducedHistoryCount&lt;/code> nas configurações de execução para limitar a quantidade de contexto de conversa que cada agente recebe.&lt;/li>
&lt;li>&lt;strong>Definir limites rígidos de iteração&lt;/strong>: Evite conversas descontroladas com valores &lt;code>MaximumIterations&lt;/code> razoáveis.&lt;/li>
&lt;li>&lt;strong>Cache quando possível&lt;/strong>: se um agente realizar a mesma pesquisa repetidamente, armazene em cache os resultados em um plugin.&lt;/li>
&lt;/ul>
&lt;h3 id="design-do-agente">Design do Agente&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Mantenha o foco nas instruções&lt;/strong>: cada agente deve ter uma responsabilidade única e clara. Instruções amplas levam a um desempenho medíocre em todas as tarefas.&lt;/li>
&lt;li>&lt;strong>Seja explícito sobre o formato de saída&lt;/strong>: diga aos agentes exatamente como estruturar suas respostas. Isso torna a análise downstream confiável.&lt;/li>
&lt;li>&lt;strong>Use palavras-chave de rescisão&lt;/strong>: defina sinais claros (como &amp;ldquo;APROVADO&amp;rdquo; ou &amp;ldquo;CONCLUÍDO&amp;rdquo;) que os agentes usam para indicar que terminaram. Isso torna as estratégias de rescisão simples e previsíveis.&lt;/li>
&lt;/ul>
&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>Os sistemas de IA multiagentes representam uma mudança fundamental na forma como construímos aplicações inteligentes. Em vez de lutar com um único prompt para lidar com tudo, podemos decompor os problemas em funções especializadas e permitir que os agentes colaborem.&lt;/p>
&lt;p>O Agent Framework da Microsoft torna isso prático para desenvolvedores .NET. As abstrações são claras – agentes, chats em grupo, estratégias de seleção e rescisão – e se compõem naturalmente. Combinado com o ecossistema de plug-ins do Kernel Semântico e a hospedagem de modelos do Azure, você tem uma pilha completa para criar sistemas multiagentes de nível de produção.&lt;/p>
&lt;p>A estrutura ainda está evoluindo (muitos pacotes estão em versão prévia), mas os padrões principais são sólidos e a direção é clara. Se você estiver criando aplicativos com tecnologia de IA em .NET, agora é a hora de começar a experimentar arquiteturas multiagentes.&lt;/p>
&lt;p>Comece aos poucos: crie dois agentes que colaborem em uma tarefa simples. Depois de ver a melhoria da qualidade em relação às abordagens de agente único, você desejará decompor tudo em equipes de agentes.&lt;/p>
&lt;p>Boa codificação!&lt;/p></content:encoded><category>AI</category><category>.NET</category><category>Agent Framework</category><category>Semantic Kernel</category></item><item><title>Renderizando HTML bruto no Blazor com MarkupString</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-markup-string-raw-html/</link><pubDate>Sat, 22 Nov 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-markup-string-raw-html/</guid><description>Renderize conteúdo HTML bruto em componentes Blazor com segurança usando MarkupString em vez de texto com escape.</description><content:encoded>&lt;p>Outro dia eu estava construindo um componente que precisava renderizar algum HTML vindo de um CMS. Eu tinha a string HTML em uma variável e simplesmente a coloquei no modelo como &lt;code>@myHtml&lt;/code>. E, claro, o Blazor escapou de tudo e renderizou as tags reais como texto na página. Não é o que eu queria.&lt;/p>
&lt;h1 id="o-problema">O problema&lt;/h1>
&lt;p>Por padrão, o Blazor codifica qualquer string renderizada em um modelo. Então, se você tiver:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> content = &lt;span style="color:#a5d6ff">&amp;#34;&amp;lt;strong&amp;gt;Hello&amp;lt;/strong&amp;gt; &amp;lt;em&amp;gt;world&amp;lt;/em&amp;gt;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E você faz isso:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;@content&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Você verá o texto literal &lt;code>&amp;lt;strong&amp;gt;Hello&amp;lt;/strong&amp;gt; &amp;lt;em&amp;gt;world&amp;lt;/em&amp;gt;&lt;/code> na página em vez de &lt;strong>Hello&lt;/strong> &lt;em>world&lt;/em>. O Blazor faz isso propositalmente para evitar ataques XSS, que é o comportamento padrão correto.&lt;/p>
&lt;h1 id="a-solução-markupstring">A solução: MarkupString&lt;/h1>
&lt;p>Se você realmente precisa renderizar HTML bruto, coloque sua string em um &lt;code>MarkupString&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;@((MarkupString)content)&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E é isso! Agora o Blazor renderizará o HTML como marcação real. Você também pode atribuí-lo a uma variável:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> rawHtml = &lt;span style="color:#a5d6ff">&amp;#34;&amp;lt;strong&amp;gt;Hello&amp;lt;/strong&amp;gt; &amp;lt;em&amp;gt;world&amp;lt;/em&amp;gt;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> MarkupString HtmlContent =&amp;gt; (MarkupString)rawHtml;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;@HtmlContent&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="um-exemplo-do-mundo-real">Um exemplo do mundo real&lt;/h1>
&lt;p>Eu estava extraindo o conteúdo do blog de uma API e precisava renderizá-lo em um componente de visualização. O conteúdo tinha todo tipo de HTML – títulos, blocos de código, links, imagens. Aqui está aproximadamente o que parecia:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@inject HttpClient Http
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@if (article &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;article&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h1&amp;gt;@article.Title&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;content&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>((MarkupString)article.HtmlBody)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/article&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> ArticleId { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> ArticleDto? article;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> article = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;ArticleDto&amp;gt;(&lt;span style="color:#a5d6ff">$&amp;#34;api/articles/{ArticleId}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Funciona perfeitamente. O HTML da API é renderizado como marcação real.&lt;/p>
&lt;h1 id="tenha-cuidado-com-conteúdo-não-confiável">Tenha cuidado com conteúdo não confiável&lt;/h1>
&lt;p>Isso é importante: &lt;code>MarkupString&lt;/code> &lt;strong>não&lt;/strong> limpa o HTML. Ele renderiza tudo o que você fornecer, incluindo tags &lt;code>&amp;lt;script&amp;gt;&lt;/code>. Portanto, se o conteúdo vier da entrada do usuário ou de uma fonte não confiável, você precisará higienizá-lo primeiro.&lt;/p>
&lt;p>Não há sanitizador de HTML integrado no Blazor, mas você pode usar uma biblioteca como &lt;a href="https://github.com/mganss/HtmlSanitizer">HtmlSanitizer&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Ganss.Xss&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> HtmlSanitizer sanitizer = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> MarkupString SafeHtml(&lt;span style="color:#ff7b72">string&lt;/span> html)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> clean = sanitizer.Sanitize(html);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> (MarkupString)clean;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;@SafeHtml(untrustedContent)&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso remove elementos perigosos como manipuladores &lt;code>&amp;lt;script&amp;gt;&lt;/code>, &lt;code>onclick&lt;/code> e outras coisas que você não deseja renderizar do conteúdo fornecido pelo usuário.&lt;/p>
&lt;h1 id="quando-usar">Quando usar&lt;/h1>
&lt;p>Eu uso &lt;code>MarkupString&lt;/code> para:&lt;/p>
&lt;ul>
&lt;li>Conteúdo CMS ou markdown que foi convertido para HTML no lado do servidor&lt;/li>
&lt;li>Saída do editor de rich text&lt;/li>
&lt;li>Visualização de modelos de e-mail&lt;/li>
&lt;li>Qualquer HTML pré-construído de fontes confiáveis&lt;/li>
&lt;/ul>
&lt;p>Para qualquer coisa que venha da entrada do usuário, sempre higienize primeiro. Melhor prevenir do que remediar.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem! Sinta-se à vontade para entrar em contato comigo em qualquer mídia social em &lt;strong>@emimontesdeoca&lt;/strong>.&lt;/p>
&lt;h1 id="recursos">Recursos&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.markupstring">Estrutura MarkupString&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/security/content-security-policy">Prevenção Blazor XSS&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>C#</category></item><item><title>O que há de novo no EF Core 9: os recursos que você precisa conhecer</title><link>https://emimontesdeoca.github.io/pt/posts/whats-new-ef-core-9/</link><pubDate>Tue, 18 Nov 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/whats-new-ef-core-9/</guid><description>Uma visão abrangente dos recursos de maior impacto no Entity Framework Core 9, desde melhorias no LINQ e operações em massa até colunas JSON e suporte à compilação AOT.</description><content:encoded>&lt;p>O Entity Framework Core 9 foi lançado junto com o .NET 9 em novembro de 2024 e, depois de passar muito tempo trabalhando com ele em vários projetos, posso dizer que é um dos lançamentos mais significativos dos últimos tempos. Não porque reinventa a roda, mas porque aperfeiçoa as áreas onde o EF Core tem causado historicamente mais atrito: tradução de consultas, desempenho e trabalho com padrões de dados modernos.&lt;/p>
&lt;p>Nesta postagem, examinarei os recursos que tiveram maior impacto no meu trabalho diário. Se você ainda estiver no EF Core 8 (ou mesmo 7), isso deverá lhe dar uma imagem clara do que o espera do outro lado da atualização.&lt;/p>
&lt;h2 id="ef-core-9-no-ecossistema-net-9">EF Core 9 no ecossistema .NET 9&lt;/h2>
&lt;p>O EF Core 9 é direcionado ao .NET 8 e .NET 9, o que significa que você não precisa necessariamente atualizar todo o seu aplicativo para o .NET 9 para aproveitar a maioria desses recursos. Dito isso, algumas das melhorias de AOT e de desempenho estão intimamente associadas às alterações de tempo de execução do .NET 9, portanto, você aproveitará ao máximo percorrendo todo o caminho.&lt;/p>
&lt;p>O lançamento segue a cadência ímpar/par estabelecida pela Microsoft: versões ímpares (como .NET 9) são Standard Term Support (STS) com 18 meses de suporte, enquanto versões pares (como .NET 8) são Long Term Support (LTS). Tenha isso em mente ao planejar seu cronograma de atualização.&lt;/p>
&lt;h2 id="melhorias-na-tradução-do-linq">Melhorias na tradução do LINQ&lt;/h2>
&lt;p>É aqui que a maioria dos desenvolvedores sentirá a diferença imediatamente. O EF Core 9 fez avanços significativos na tradução de expressões LINQ para SQL que realmente fazem sentido.&lt;/p>
&lt;h3 id="melhor-groupby-traduções">Melhor GroupBy Traduções&lt;/h3>
&lt;p>Se você já escreveu uma consulta &lt;code>GroupBy&lt;/code> no EF Core e acabou com avisos de avaliação do lado do cliente ou SQL bizarro, você conhece o problema. O EF Core 9 lida com um conjunto muito mais amplo de cenários &lt;code>GroupBy&lt;/code> diretamente no SQL.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> salesByCategory = &lt;span style="color:#ff7b72">await&lt;/span> context.Products
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GroupBy(p =&amp;gt; p.Category.Name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(g =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Category = g.Key,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TotalRevenue = g.Sum(p =&amp;gt; p.Price * p.UnitsSold),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AveragePrice = g.Average(p =&amp;gt; p.Price),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ProductCount = g.Count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(x =&amp;gt; x.TotalRevenue)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nas versões anteriores, as consultas envolvendo agregações sobre propriedades de navegação dentro de &lt;code>GroupBy&lt;/code> às vezes recorriam à avaliação do cliente. O EF Core 9 traduz isso de forma limpa para uma única consulta SQL com &lt;code>GROUP BY&lt;/code>, &lt;code>SUM&lt;/code>, &lt;code>AVG&lt;/code> e &lt;code>COUNT&lt;/code>.&lt;/p>
&lt;h3 id="projeções-e-subconsultas-complexas">Projeções e subconsultas complexas&lt;/h3>
&lt;p>Subconsultas aninhadas e projeções complexas também receberam uma grande atualização. Considere algo assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> orderSummaries = &lt;span style="color:#ff7b72">await&lt;/span> context.Customers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(c =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> CustomerSummaryDto
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = c.FullName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TotalOrders = c.Orders.Count(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MostRecentOrder = c.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(o =&amp;gt; o.OrderDate)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(o =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> OrderBriefDto
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Id = o.Id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Date = o.OrderDate,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Total = o.LineItems.Sum(li =&amp;gt; li.Quantity * li.UnitPrice)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TopCategory = c.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SelectMany(o =&amp;gt; o.LineItems)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GroupBy(li =&amp;gt; li.Product.Category.Name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(g =&amp;gt; g.Count())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(g =&amp;gt; g.Key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O EF Core 9 agora pode traduzir toda essa expressão em SQL sem acionar a avaliação do lado do cliente. A consulta gerada usa subconsultas correlacionadas e junções laterais quando apropriado, e o plano SQL é consideravelmente mais eficiente do que as versões anteriores produziriam.&lt;/p>
&lt;h3 id="coleções-primitivas-parametrizadas">Coleções Primitivas Parametrizadas&lt;/h3>
&lt;p>Uma das melhorias mais destacadas do LINQ é a capacidade de passar coleções de valores primitivos diretamente para consultas:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> statusFilter = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; { &lt;span style="color:#a5d6ff">&amp;#34;Active&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Pending&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Review&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> filteredOrders = &lt;span style="color:#ff7b72">await&lt;/span> context.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(o =&amp;gt; statusFilter.Contains(o.Status))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>No EF Core 8, isso foi traduzido usando cláusulas &lt;code>IN&lt;/code> com valores embutidos, o que significava que o cache do plano de consulta não poderia ser reutilizado quando a lista fosse alterada. O EF Core 9 parametriza essas coleções adequadamente, enviando-as como um parâmetro estruturado. Isso é importante para o cache do plano de consulta no SQL Server e no PostgreSQL.## Operações em massa — ExecuteUpdate e ExecuteDelete&lt;/p>
&lt;p>&lt;code>ExecuteUpdate&lt;/code> e &lt;code>ExecuteDelete&lt;/code> foram introduzidos no EF Core 7, mas o EF Core 9 expande o que você pode fazer com eles de maneiras significativas.&lt;/p>
&lt;h3 id="expressões-de-atualização-mais-complexas">Expressões de atualização mais complexas&lt;/h3>
&lt;p>Agora você pode usar expressões mais complexas em &lt;code>ExecuteUpdate&lt;/code>, incluindo referências a outras tabelas através de propriedades de navegação:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> context.Products
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(p =&amp;gt; p.Category.IsDiscontinued)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ExecuteUpdateAsync(setters =&amp;gt; setters
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SetProperty(p =&amp;gt; p.IsAvailable, &lt;span style="color:#79c0ff">false&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SetProperty(p =&amp;gt; p.DiscontinuedDate, DateTimeOffset.UtcNow)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SetProperty(p =&amp;gt; p.Price, p =&amp;gt; p.Price * &lt;span style="color:#a5d6ff">0.5&lt;/span>m));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso gera uma única instrução &lt;code>UPDATE&lt;/code> com um &lt;code>JOIN&lt;/code> para a tabela de categorias — sem necessidade de carregar entidades na memória, sem sobrecarga de controle de alterações.&lt;/p>
&lt;h3 id="exclusões-condicionais-em-massa-com-subconsultas">Exclusões condicionais em massa com subconsultas&lt;/h3>
&lt;p>Exclusões em massa com filtros de subconsulta agora são totalmente suportadas:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> context.AuditLogs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(log =&amp;gt; log.CreatedAt &amp;lt; DateTime.UtcNow.AddYears(-&lt;span style="color:#a5d6ff">2&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(log =&amp;gt; !context.ProtectedRecords
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Any(pr =&amp;gt; pr.AuditLogId == log.Id))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ExecuteDeleteAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso se traduz em uma &lt;code>DELETE&lt;/code> com uma subconsulta &lt;code>NOT EXISTS&lt;/code>, exatamente o que você escreveria à mão. Nenhuma entidade carregada, sem viagens de ida e volta.&lt;/p>
&lt;h2 id="melhorias-na-coluna-json">Melhorias na coluna JSON&lt;/h2>
&lt;p>As colunas JSON têm sido um dos recursos mais interessantes nas versões recentes do EF Core, e o EF Core 9 as leva ainda mais longe.&lt;/p>
&lt;h3 id="consultando-dentro-do-json">Consultando dentro do JSON&lt;/h3>
&lt;p>Agora você pode filtrar e projetar dados em colunas JSON com melhor suporte de tradução:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Order&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> CustomerName { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ShippingAddress Address { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } &lt;span style="color:#8b949e;font-style:italic">// Stored as JSON&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;OrderNote&amp;gt; Notes { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } &lt;span style="color:#8b949e;font-style:italic">// Stored as JSON&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ShippingAddress&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Street { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> City { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> State { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> ZipCode { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Country { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">OrderNote&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime CreatedAt { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Text { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Author { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Configuração em seu &lt;code>DbContext&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnModelCreating(ModelBuilder modelBuilder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> modelBuilder.Entity&amp;lt;Order&amp;gt;(builder =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.OwnsOne(o =&amp;gt; o.Address, ab =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ab.ToJson();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.OwnsMany(o =&amp;gt; o.Notes, nb =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> nb.ToJson();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora você pode consultar diretamente no JSON:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> newYorkOrders = &lt;span style="color:#ff7b72">await&lt;/span> context.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(o =&amp;gt; o.Address.State == &lt;span style="color:#a5d6ff">&amp;#34;NY&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(o =&amp;gt; o.Notes.Count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(o =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> o.CustomerName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> o.Address.City,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LatestNote = o.Notes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(n =&amp;gt; n.CreatedAt)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(n =&amp;gt; n.Text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O EF Core 9 gera chamadas &lt;code>JSON_VALUE&lt;/code> e &lt;code>JSON_QUERY&lt;/code> adequadas no SQL Server (ou equivalente em outros provedores), e a tradução cobre uma gama muito mais ampla de operações LINQ em elementos JSON do que antes.&lt;/p>
&lt;h3 id="atualizando-propriedades-json">Atualizando propriedades JSON&lt;/h3>
&lt;p>Um dos pontos de atrito no EF Core 8 era que a atualização de uma única propriedade dentro de uma coluna JSON faria com que todo o documento JSON fosse reescrito. O EF Core 9 melhora isso com um rastreamento de alterações mais granular para tipos mapeados em JSON, gerando atualizações mais direcionadas quando possível.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> order = &lt;span style="color:#ff7b72">await&lt;/span> context.Orders.FindAsync(orderId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>order.Address.ZipCode = &lt;span style="color:#a5d6ff">&amp;#34;10001&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> context.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Em fornecedores suportados, isto pode gerar uma modificação JSON mais direcionada em vez de reescrever todo o blob.&lt;/p>
&lt;h2 id="tipos-complexos--objetos-de-valor-sem-identidade">Tipos Complexos — Objetos de Valor Sem Identidade&lt;/h2>
&lt;p>Tipos complexos são um dos recursos que os profissionais de Design Orientado a Domínio estavam esperando. Ao contrário dos tipos próprios, os tipos complexos não têm identidade — são objetos de valor puro.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[ComplexType]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Money&lt;/span>(&lt;span style="color:#ff7b72">decimal&lt;/span> Amount, &lt;span style="color:#ff7b72">string&lt;/span> Currency);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[ComplexType]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">DateRange&lt;/span>(DateTime Start, DateTime End);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Project&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Money Budget { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateRange Timeline { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Eles são armazenados como colunas niveladas na tabela pai — &lt;code>Budget_Amount&lt;/code>, &lt;code>Budget_Currency&lt;/code>, &lt;code>Timeline_Start&lt;/code>, &lt;code>Timeline_End&lt;/code> — sem exigir uma tabela separada ou qualquer tipo de chave.&lt;/p>
&lt;p>A principal diferença em relação aos tipos próprios: os tipos complexos são comparados por valor, não por referência. Duas instâncias &lt;code>Money&lt;/code> com os mesmos &lt;code>Amount&lt;/code> e &lt;code>Currency&lt;/code> são consideradas iguais, independentemente da entidade a que pertencem.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> expensiveProjects = &lt;span style="color:#ff7b72">await&lt;/span> context.Projects
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(p =&amp;gt; p.Budget.Amount &amp;gt; &lt;span style="color:#a5d6ff">100_000&lt;/span>m &amp;amp;&amp;amp; p.Budget.Currency == &lt;span style="color:#a5d6ff">&amp;#34;USD&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(p =&amp;gt; p.Budget.Amount)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso se traduz diretamente na filtragem nas colunas niveladas – limpa, eficiente e exatamente o que você espera.&lt;/p>
&lt;h2 id="suporte-hierarchyid-para-sql-server">Suporte HierarchyId para SQL Server&lt;/h2>
&lt;p>Se você já trabalhou com dados hierárquicos no SQL Server — organogramas, árvores de categorias, sistemas de arquivos — você sabe que &lt;code>HierarchyId&lt;/code> é o tipo integrado para isso. O EF Core 9 oferece suporte de primeira classe para isso.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Employee&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Title { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> HierarchyId PathFromCeo { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora você pode consultar relacionamentos hierárquicos diretamente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> managerId = HierarchyId.Parse(&lt;span style="color:#a5d6ff">&amp;#34;/1/3/&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// All direct and indirect reports&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> allReports = &lt;span style="color:#ff7b72">await&lt;/span> context.Employees
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(e =&amp;gt; e.PathFromCeo.IsDescendantOf(managerId))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(e =&amp;gt; e.PathFromCeo != managerId) &lt;span style="color:#8b949e;font-style:italic">// exclude the manager&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderBy(e =&amp;gt; e.PathFromCeo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Direct reports only (one level down)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> directReports = &lt;span style="color:#ff7b72">await&lt;/span> context.Employees
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(e =&amp;gt; e.PathFromCeo.GetAncestor(&lt;span style="color:#a5d6ff">1&lt;/span>) == managerId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Get an employee&amp;#39;s depth in the hierarchy&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> employeesWithDepth = &lt;span style="color:#ff7b72">await&lt;/span> context.Employees
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(e =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.Name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.Title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Level = e.PathFromCeo.GetLevel()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderBy(e =&amp;gt; e.Level)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Tudo isso se traduz em métodos nativos &lt;span style="color:#f85149">`&lt;/span>HierarchyId&lt;span style="color:#f85149">`&lt;/span> &lt;span style="color:#ff7b72">do&lt;/span> SQL Server. Se você estiver implementando estruturas em &lt;span style="color:#f85149">á&lt;/span>rvore com chaves estrangeiras autorreferenciadas e CTEs recursivas, esta &lt;span style="color:#f85149">é&lt;/span> uma abordagem muito mais limpa.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> Modelos compilados e suporte AOT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Os desenvolvedores preocupados com o desempenho apreciarão o investimento contínuo em modelos compilados e suporte &lt;span style="color:#f85149">à&lt;/span> compilação antecipada (AOT).
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> Modelos compilados
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Os modelos compilados pré-geram os metadados &lt;span style="color:#ff7b72">do&lt;/span> modelo que o EF Core normalmente cria na inicialização. Para modelos grandes (pense em centenas de entidades), isso pode reduzir drasticamente o tempo de inicialização a frio.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>bash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet ef dbcontext optimize --output-dir CompiledModels --&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">MyApp.CompiledModels&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Então ligue:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddDbContext&amp;lt;AppDbContext&amp;gt;(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .UseSqlServer(connectionString)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .UseModel(MyApp.CompiledModels.AppDbContextModel.Instance));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>No EF Core 9, os modelos compilados são mais completos — eles suportam mais recursos de mapeamento e geram resultados menores. Para um modelo com cerca de 400 entidades, o tempo de inicialização pode cair de vários segundos para quase instantâneo.&lt;/p>
&lt;h3 id="progresso-da-compilação-aot">Progresso da compilação AOT&lt;/h3>
&lt;p>O suporte completo do AOT nativo para o EF Core ainda é um trabalho em andamento, mas o EF Core 9 faz avanços significativos. Muitos dos caminhos de código com muita reflexão foram refatorados para serem fáceis de cortar, e os modelos compilados são uma parte fundamental da história do AOT. Se você estiver visando cenários como Azure Functions ou microsserviços em que a inicialização a frio é importante, essas melhorias serão diretamente relevantes.&lt;/p>
&lt;h2 id="atualizações-do-provedor-cosmos-db">Atualizações do provedor Cosmos DB&lt;/h2>
&lt;p>O provedor Azure Cosmos DB continua a amadurecer com o EF Core 9. Algumas melhorias notáveis:&lt;/p>
&lt;h3 id="manipulação-de-chave-de-partição">Manipulação de chave de partição&lt;/h3>
&lt;p>O provedor agora oferece suporte a chaves de partição hierárquicas e lida com filtros de chaves de partição de maneira mais inteligente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TenantDocument&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> TenantId { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } &lt;span style="color:#8b949e;font-style:italic">// Partition key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Region { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } &lt;span style="color:#8b949e;font-style:italic">// Sub-partition key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Content { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// This query now correctly uses the partition key for routing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> docs = &lt;span style="color:#ff7b72">await&lt;/span> context.TenantDocuments
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(d =&amp;gt; d.TenantId == &lt;span style="color:#a5d6ff">&amp;#34;tenant-42&amp;#34;&lt;/span> &amp;amp;&amp;amp; d.Region == &lt;span style="color:#a5d6ff">&amp;#34;us-east&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(d =&amp;gt; d.Content.Contains(&lt;span style="color:#a5d6ff">&amp;#34;important&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="tradução-aprimorada-de-linq-para-nosql">Tradução aprimorada de LINQ para NoSQL&lt;/h3>
&lt;p>Mais operações LINQ agora são traduzidas para o dialeto SQL do Cosmos DB, incluindo melhor suporte para &lt;code>Contains&lt;/code>, &lt;code>Any&lt;/code>, operações de matriz aninhadas e funções matemáticas. As consultas que anteriormente dependiam da avaliação do cliente agora são tratadas no lado do servidor.&lt;/p>
&lt;h3 id="suporte-para-pesquisa-de-vetores">Suporte para pesquisa de vetores&lt;/h3>
&lt;p>O EF Core 9 introduz suporte antecipado para pesquisa de similaridade vetorial com o Cosmos DB, o que é útil se você estiver criando aplicativos que se integram a embeddings ou pesquisa orientada por IA:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> results = &lt;span style="color:#ff7b72">await&lt;/span> context.Documents
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderBy(d =&amp;gt; EF.Functions.VectorDistance(d.Embedding, queryVector))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Take(&lt;span style="color:#a5d6ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="melhorias-na-migração">Melhorias na migração&lt;/h2>
&lt;p>As migrações obtiveram algumas melhorias na qualidade de vida que tornam o trabalho com elas menos penoso em ambientes de equipe.&lt;/p>
&lt;h3 id="tabelas-temporais-em-migrações">Tabelas Temporais em Migrações&lt;/h3>
&lt;p>As migrações agora lidam com a configuração da tabela temporal de maneira mais elegante, com suporte adequado para colunas de período e nomenclatura de tabela de histórico:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnModelCreating(ModelBuilder modelBuilder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> modelBuilder.Entity&amp;lt;Employee&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToTable(&lt;span style="color:#a5d6ff">&amp;#34;Employees&amp;#34;&lt;/span>, b =&amp;gt; b.IsTemporal(t =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.HasPeriodStart(&lt;span style="color:#a5d6ff">&amp;#34;ValidFrom&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.HasPeriodEnd(&lt;span style="color:#a5d6ff">&amp;#34;ValidTo&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.UseHistoryTable(&lt;span style="color:#a5d6ff">&amp;#34;EmployeeHistory&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="scripts-idempotentes">Scripts Idempotentes&lt;/h3>
&lt;p>O comando &lt;code>Script-Migration&lt;/code> (e seu equivalente CLI) produz scripts idempotentes melhores por padrão, com tratamento aprimorado de casos extremos em torno de alterações de esquema que dependem de dados existentes em determinados estados.&lt;/p>
&lt;h3 id="pacotes-de-migração">Pacotes de migração&lt;/h3>
&lt;p>Os pacotes de migração, que empacotam suas migrações em um executável independente para implantação, são mais confiáveis no EF Core 9, com melhores relatórios de erros e lógica de nova tentativa para falhas transitórias.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet ef migrations bundle --self-contained -r linux-x64
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso produz um binário que você pode executar em seu pipeline de CI/CD sem precisar do SDK do .NET instalado em seu destino de implantação.&lt;/p>
&lt;h2 id="benchmarks-de-desempenhoaqui-estão-alguns-benchmarks-aproximados-de-meus-próprios-testes-eles-são-de-um-projeto-com-cerca-de-200-entidades-executado-no-sql-server-2022-medido-com-benchmarkdotnet-seus-números-irão-variar-mas-as-melhorias-relativas-devem-ser-semelhantes">Benchmarks de desempenhoAqui estão alguns benchmarks aproximados de meus próprios testes. Eles são de um projeto com cerca de 200 entidades, executado no SQL Server 2022, medido com BenchmarkDotNet. Seus números irão variar, mas as melhorias relativas devem ser semelhantes.&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Cenário&lt;/th>
&lt;th>EF Núcleo 8&lt;/th>
&lt;th>EF Núcleo 9&lt;/th>
&lt;th>Melhoria&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Construção de modelo (inicialização a frio)&lt;/td>
&lt;td>1.850m&lt;/td>
&lt;td>320m&lt;/td>
&lt;td>~5,8x mais rápido (compilado)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Consulta simples (entidade única por PK)&lt;/td>
&lt;td>0,42ms&lt;/td>
&lt;td>0,38ms&lt;/td>
&lt;td>~10% mais rápido&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Consulta complexa (junções + agregação)&lt;/td>
&lt;td>3,1ms&lt;/td>
&lt;td>2,4ms&lt;/td>
&lt;td>~23% mais rápido&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Atualização em massa (10 mil linhas)&lt;/td>
&lt;td>145m&lt;/td>
&lt;td>118m&lt;/td>
&lt;td>~19% mais rápido&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Consulta de coluna JSON&lt;/td>
&lt;td>2,8ms&lt;/td>
&lt;td>1,9ms&lt;/td>
&lt;td>~32% mais rápido&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Salvar alterações (100 entidades)&lt;/td>
&lt;td>48m&lt;/td>
&lt;td>41m&lt;/td>
&lt;td>~15% mais rápido&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>A melhoria do modelo compilado é a mais dramática, mas as melhorias constantes em geral se somam — especialmente em cenários de alto rendimento, onde você executa milhares de consultas por segundo.&lt;/p>
&lt;h2 id="atualizando-do-ef-core-8">Atualizando do EF Core 8&lt;/h2>
&lt;p>Se você estiver no EF Core 8, o caminho de atualização será relativamente tranquilo. Aqui está uma lista de verificação:&lt;/p>
&lt;p>&lt;strong>1. Atualize seus pacotes:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.EntityFrameworkCore&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;9.0.0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.EntityFrameworkCore.SqlServer&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;9.0.0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.EntityFrameworkCore.Tools&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;9.0.0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. Verifique se há alterações recentes.&lt;/strong> A lista do EF Core 9 é relativamente curta em comparação com algumas versões anteriores. Os mais notáveis:&lt;/p>
&lt;ul>
&lt;li>Algumas APIs anteriormente obsoletas foram removidas&lt;/li>
&lt;li>Mudanças na forma como certas consultas &lt;code>GroupBy&lt;/code> são traduzidas (elas agora vão para o lado do servidor, o que muda o comportamento se você estiver confiando na avaliação do cliente)&lt;/li>
&lt;li>Pequenas alterações na saída do andaime de migração&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>3. Gere novamente modelos compilados&lt;/strong> se você os estiver usando. O formato mudou, então modelos compilados antigos não funcionarão com o EF Core 9.&lt;/p>
&lt;p>&lt;strong>4. Execute seu conjunto de testes.&lt;/strong> Preste atenção especial às consultas que foram avaliadas anteriormente no cliente — elas agora podem ser avaliadas no servidor, o que geralmente é melhor, mas pode revelar diferenças de dados.&lt;/p>
&lt;p>&lt;strong>5. Verifique suas consultas do Cosmos DB&lt;/strong> se você estiver usando esse provedor. As traduções aprimoradas significam que algumas consultas serão executadas de maneira diferente (geralmente mais rápidas), mas vale a pena verificar se os resultados são idênticos.&lt;/p>
&lt;p>Uma atualização mínima para um projeto típico é assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.EntityFrameworkCore --version 9.0.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 9.0.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Se tudo for compilado e os testes passarem, você provavelmente está em boa forma. Se você encontrar problemas, a documentação de alterações significativas do EF Core 9 contém orientações detalhadas de migração para cada alteração.&lt;/p>
&lt;h2 id="concluindo">Concluindo&lt;/h2>
&lt;p>O EF Core 9 não é uma versão revolucionária – é uma versão evolutiva, e era exatamente isso que precisava ser. As melhorias do LINQ por si só justificam a atualização para a maioria dos projetos, e recursos como aprimoramentos de colunas JSON, tipos complexos e &lt;code>HierarchyId&lt;/code> suportam padrões de abertura que antes eram estranhos ou impossíveis.&lt;/p>
&lt;p>Se eu tivesse que escolher os três recursos que tiveram maior impacto em meus projetos:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Coleções primitivas parametrizadas&lt;/strong> — porque a eficiência do cache do plano de consulta é importante em escala&lt;/li>
&lt;li>&lt;strong>Melhorias na coluna JSON&lt;/strong> — porque o padrão de documento relacional híbrido é incrivelmente útil&lt;/li>
&lt;li>&lt;strong>Modelos compilados&lt;/strong> — porque o tempo de inicialização afeta diretamente a produtividade do desenvolvedor e a velocidade de implantaçãoA equipe do EF Core está em uma trajetória sólida desde o EF Core 5, e a versão 9 continua essa tendência. Se você já usa o EF Core 8, a atualização é de baixo risco e alta recompensa. Se você está em algo mais antigo, nunca houve melhor momento para dar o salto.&lt;/li>
&lt;/ol>
&lt;p>Boa codificação – e boas consultas.&lt;/p></content:encoded><category>.NET</category><category>Entity Framework</category><category>Database</category></item><item><title>Introdução ao Kernel Semântico: Orquestração de IA em C#</title><link>https://emimontesdeoca.github.io/pt/posts/getting-started-semantic-kernel/</link><pubDate>Sun, 05 Oct 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/getting-started-semantic-kernel/</guid><description>Aprenda a usar o Kernel Semântico da Microsoft para criar aplicativos baseados em IA em C#, desde plug-ins e planejadores até memória e chamadas de funções.</description><content:encoded>&lt;p>Se você está construindo aplicativos .NET e observando a evolução do cenário de IA, provavelmente já se perguntou: &lt;em>qual é a melhor maneira de integrar grandes modelos de linguagem em meus projetos C# sem transformar minha base de código em espaguete?&lt;/em> Esse é exatamente o problema que o Kernel Semântico da Microsoft resolve, e depois de passar o último ano construindo aplicativos de produção com ele, posso dizer que ele se tornou uma das ferramentas mais importantes em meu kit de ferramentas para desenvolvedores.&lt;/p>
&lt;p>Nesta postagem, explicarei tudo o que você precisa para começar a usar o Kernel Semântico – desde a compreensão dos conceitos básicos até a construção de um assistente de IA do mundo real. Esteja você apenas mergulhando no desenvolvimento de IA ou procurando uma maneira estruturada de orquestrar chamadas LLM em seus aplicativos .NET existentes, este guia tem o que você precisa.&lt;/p>
&lt;h2 id="o-que-é-kernel-semântico">O que é Kernel Semântico?&lt;/h2>
&lt;p>O Kernel Semântico (SK) é um SDK de código aberto da Microsoft que atua como uma &lt;strong>camada de orquestração&lt;/strong> entre o código do seu aplicativo e grandes modelos de linguagem como GPT-4o, Azure OpenAI ou outros serviços de IA. Pense nele como um middleware leve que permite combinar código C# tradicional com recursos de IA de uma forma limpa e combinável.&lt;/p>
&lt;p>Mas por que não chamar diretamente a API OpenAI? É absolutamente possível – e para casos de uso simples, tudo bem. Mas o momento em que você precisa:&lt;/p>
&lt;ul>
&lt;li>Deixe a IA decidir &lt;strong>quais funções&lt;/strong> chamar com base na entrada do usuário&lt;/li>
&lt;li>Combine &lt;strong>várias chamadas de IA&lt;/strong> com código tradicional em um pipeline&lt;/li>
&lt;li>Adicione &lt;strong>memória e contexto&lt;/strong> para que a IA se lembre de interações anteriores&lt;/li>
&lt;li>Crie &lt;strong>agentes de várias etapas&lt;/strong> que raciocinam em tarefas complexas&lt;/li>
&lt;/ul>
&lt;p>&amp;hellip;você se verá reinventando a roda. O Kernel Semântico oferece tudo isso pronto para uso, com suporte .NET de primeira classe, integração de injeção de dependência e uma arquitetura de plug-in que parece natural para qualquer desenvolvedor C#.&lt;/p>
&lt;p>O projeto está no GitHub no repositório &lt;code>microsoft/semantic-kernel&lt;/code> e possui SDKs para C#, Python e Java. O SDK C# é o mais maduro e é nele que nos concentraremos aqui.&lt;/p>
&lt;h2 id="conceitos-básicos">Conceitos Básicos&lt;/h2>
&lt;p>Antes de escrevermos qualquer código, vamos entender os blocos de construção.&lt;/p>
&lt;h3 id="o-núcleo">O Núcleo&lt;/h3>
&lt;p>O &lt;code>Kernel&lt;/code> é o objeto central no Kernel Semântico. É o orquestrador – aquilo que une seus serviços, plug-ins e configuração de IA. Você cria um, registra seus serviços e plug-ins nele e depois o usa para executar prompts ou invocar funções. Se você estiver familiarizado com injeção de dependência no ASP.NET Core, o Kernel parecerá muito familiar — é essencialmente um contêiner de serviço com superpoderes de IA.&lt;/p>
&lt;h3 id="plug-ins-e-funções">Plug-ins e funções&lt;/h3>
&lt;p>Um &lt;strong>plugin&lt;/strong> é uma coleção de &lt;strong>funções&lt;/strong> relacionadas que o Kernel pode invocar. As funções vêm em dois sabores:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Funções de prompt&lt;/strong> — definidas como modelos de linguagem natural que são enviados ao LLM&lt;/li>
&lt;li>&lt;strong>Funções nativas&lt;/strong> — métodos C# regulares decorados com atributos que o Kernel pode descobrir e chamar&lt;/li>
&lt;/ul>
&lt;p>Por exemplo, você pode ter um &lt;code>WeatherPlugin&lt;/code> com uma função nativa &lt;code>GetCurrentWeather(string city)&lt;/code> e uma função de prompt que resume os dados meteorológicos de maneira amigável.### Conectores AI&lt;/p>
&lt;p>Os conectores são a forma como o Kernel Semântico se comunica com os serviços de IA. Os mais comuns são:&lt;/p>
&lt;ul>
&lt;li>&lt;code>AzureOpenAIChatCompletion&lt;/code> — para serviço Azure OpenAI&lt;/li>
&lt;li>&lt;code>OpenAIChatCompletion&lt;/code> — para API da OpenAI diretamente&lt;/li>
&lt;li>Incorporação de conectores para pesquisa de vetores e memória&lt;/li>
&lt;/ul>
&lt;p>Você os registra no Kernel na inicialização e todo o resto funciona.&lt;/p>
&lt;h2 id="configurando-seu-projeto">Configurando seu projeto&lt;/h2>
&lt;p>Vamos sujar as mãos. Comece criando um novo aplicativo de console:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new console -n SemanticKernelDemo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd SemanticKernelDemo
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora adicione os pacotes NuGet do Kernel Semântico:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Se você estiver usando o OpenAI diretamente em vez do Azure OpenAI:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para suporte de memória e incorporações (usaremos isso mais tarde):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Plugins.Memory
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.Extensions.VectorData.Abstractions
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Seu &lt;code>.csproj&lt;/code> deve ser direcionado ao .NET 8 ou posterior. As versões mais recentes do Semantic Kernel aproveitam ao máximo os recursos modernos do .NET.&lt;/p>
&lt;h2 id="seu-primeiro-kernel">Seu primeiro kernel&lt;/h2>
&lt;p>Vamos começar com o exemplo mais simples possível: criar um Kernel, conectá-lo a um serviço de IA e fazer uma pergunta.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Build the kernel with Azure OpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Invoke a simple prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> kernel.InvokePromptAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Explain dependency injection in C# in three sentences.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(result);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Se você estiver usando o OpenAI diretamente, troque o registro do serviço:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.AddOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> modelId: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-openai-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É isso. Execute-o e você obterá uma explicação concisa sobre injeção de dependência. Mas isso está apenas arranhando a superfície.&lt;/p>
&lt;h3 id="usando-modelos-de-prompt">Usando modelos de prompt&lt;/h3>
&lt;p>Os modelos de prompt permitem parametrizar seus prompts com variáveis usando sintaxe no estilo Handlebars:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> prompt = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical writer. Write a brief summary of {{&lt;span style="color:#f85149">$&lt;/span>topic}}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aimed at developers with {{&lt;span style="color:#f85149">$&lt;/span>experienceLevel}} experience.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Keep it under &lt;span style="color:#a5d6ff">200&lt;/span> words.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> function = kernel.CreateFunctionFromPrompt(prompt);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> arguments = &lt;span style="color:#ff7b72">new&lt;/span> KernelArguments
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&amp;#34;topic&amp;#34;] = &lt;span style="color:#a5d6ff">&amp;#34;gRPC in .NET&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&amp;#34;experienceLevel&amp;#34;] = &lt;span style="color:#a5d6ff">&amp;#34;intermediate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> kernel.InvokeAsync(function, arguments);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(result);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É aqui que o Kernel Semântico começa a brilhar – você pode definir modelos de prompt reutilizáveis, versioná-los e compô-los em fluxos de trabalho maiores.&lt;/p>
&lt;h2 id="plugins-e-funções-nativas">Plugins e funções nativas&lt;/h2>
&lt;p>Os plug-ins são onde o Kernel Semântico preenche a lacuna entre a IA e seu código C# existente. Uma função nativa é apenas um método regular que você expõe ao Kernel.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.ComponentModel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TimePlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;get_current_time&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Gets the current date and time in UTC&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetCurrentTime()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> DateTime.UtcNow.ToString(&lt;span style="color:#a5d6ff">&amp;#34;yyyy-MM-dd HH:mm:ss UTC&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;get_time_in_timezone&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Gets the current time in a specific timezone&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetTimeInTimezone(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;The IANA timezone identifier, e.g. &amp;#39;America/New_York&amp;#39;&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> timezone)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> tz = TimeZoneInfo.FindSystemTimeZoneById(timezone);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> time = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> time.ToString(&lt;span style="color:#a5d6ff">&amp;#34;yyyy-MM-dd HH:mm:ss&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Observe os atributos &lt;code>[KernelFunction]&lt;/code> e &lt;code>[Description]&lt;/code>. Eles são críticos – as descrições são o que a IA lê para entender quando e como chamar suas funções. Boas descrições fazem a diferença entre uma IA que usa suas ferramentas de maneira eficaz e outra que é confusa.&lt;/p>
&lt;p>Registre o plugin em seu kernel:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromType&amp;lt;TimePlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Você também pode construir plugins mais complexos que injetam serviços. Como o Kernel Semântico se integra ao &lt;code>Microsoft.Extensions.DependencyInjection&lt;/code>, seus plug-ins podem receber dependências de construtor como qualquer outro serviço:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">OrderPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IOrderRepository _repository;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> OrderPlugin(IOrderRepository repository)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _repository = repository;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;get_order_status&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Retrieves the status of an order by its ID&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetOrderStatus(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;The order ID to look up&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> orderId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> order = &lt;span style="color:#ff7b72">await&lt;/span> _repository.GetByIdAsync(orderId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> order &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? &lt;span style="color:#a5d6ff">$&amp;#34;No order found with ID {orderId}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : &lt;span style="color:#a5d6ff">$&amp;#34;Order {orderId}: {order.Status}, placed on {order.CreatedAt:d}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="chamada-de-função-e-invocação-automática">Chamada de função e invocação automática&lt;/h2>
&lt;p>É aqui que as coisas ficam realmente interessantes. Com a &lt;strong>chamada de função&lt;/strong> (também conhecida como chamada de ferramenta), você permite que o modelo de IA decida quais das funções registradas chamar com base no contexto da conversa. O modelo não executa código — ele retorna uma solicitação estruturada dizendo “Quero chamar a função X com esses argumentos” e o Kernel trata da invocação real.&lt;/p>
&lt;p>Veja como ativar a chamada automática de função:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.OpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromType&amp;lt;TimePlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromType&amp;lt;WeatherPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Enable automatic function calling&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> settings = &lt;span style="color:#ff7b72">new&lt;/span> OpenAIPromptExecutionSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> kernel.InvokePromptAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;What time is it in Tokyo and what&amp;#39;s the weather like there?&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> KernelArguments(settings)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(result);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Com &lt;code>FunctionChoiceBehavior.Auto()&lt;/code>, o Kernel irá:&lt;/p>
&lt;ol>
&lt;li>Envie seu prompt para a IA junto com descrições de todas as funções disponíveis&lt;/li>
&lt;li>A IA decide que precisa ligar para &lt;code>get_time_in_timezone&lt;/code> e &lt;code>get_weather&lt;/code>&lt;/li>
&lt;li>O Kernel executa automaticamente essas funções&lt;/li>
&lt;li>Os resultados são enviados de volta para a IA&lt;/li>
&lt;li>A IA compõe uma resposta em linguagem natural usando os resultados da funçãoEsse loop pode acontecer várias vezes em uma única invocação — a IA pode chamar várias funções em sequência para reunir todas as informações necessárias. Você também pode usar &lt;code>FunctionChoiceBehavior.Required()&lt;/code> para forçar a IA a chamar pelo menos uma função ou fornecer uma lista específica de funções que ela pode usar.&lt;/li>
&lt;/ol>
&lt;h3 id="conclusão-do-bate-papo-com-histórico">Conclusão do bate-papo com histórico&lt;/h3>
&lt;p>Para aplicativos de conversação, você desejará usar o &lt;code>ChatCompletionService&lt;/code> diretamente com um objeto &lt;code>ChatHistory&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> chatService = kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>history.AddSystemMessage(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a helpful developer assistant. You have access to tools
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">for&lt;/span> checking the time and weather. Be concise and friendly.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">while&lt;/span> (&lt;span style="color:#79c0ff">true&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;You: &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> input = Console.ReadLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(input)) &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(input);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> chatService.GetChatMessageContentAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> executionSettings: settings,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> kernel: kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddAssistantMessage(response.Content ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Assistant: {response.Content}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso fornece um chatbot totalmente interativo que mantém o histórico de conversas e pode chamar seus plug-ins conforme necessário.&lt;/p>
&lt;h2 id="memória-e-incorporações">Memória e incorporações&lt;/h2>
&lt;p>Um dos padrões mais poderosos em aplicativos de IA é &lt;strong>Retrieval-Augmented Generation (RAG)&lt;/strong> — dando à IA acesso aos seus próprios dados, incorporando-os no espaço vetorial e recuperando pedaços relevantes no momento da consulta.&lt;/p>
&lt;p>O Kernel Semântico fornece abstrações para trabalhar com armazenamentos e embeddings de vetores. Veja como configurar um armazenamento de vetores na memória para desenvolvimento:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.VectorData&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.AzureOpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Embeddings&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#pragma&lt;/span> warning disable SKEXP0010
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Create an embedding generation service&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAITextEmbeddingGeneration(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;text-embedding-ada-002&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> embeddingService = kernel.GetRequiredService&amp;lt;ITextEmbeddingGenerationService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Generate embeddings for your documents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> documents = &lt;span style="color:#ff7b72">new&lt;/span>[]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Semantic Kernel is an open-source SDK for AI orchestration.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Azure OpenAI provides enterprise-grade AI models.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Plugins in SK allow you to expose C# methods to AI models.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> embeddings = &lt;span style="color:#ff7b72">await&lt;/span> embeddingService.GenerateEmbeddingsAsync(documents);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para cenários de produção, você armazenaria essas incorporações em um banco de dados vetorial dedicado, como Azure AI Search, Qdrant ou Pinecone. O Kernel Semântico possui conectores para tudo isso através das abstrações &lt;code>Microsoft.Extensions.VectorData&lt;/code>.&lt;/p>
&lt;p>Um fluxo RAG típico é assim:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Ingestão&lt;/strong>: fragmente seus documentos, gere embeddings e armazene-os em um banco de dados vetorial&lt;/li>
&lt;li>&lt;strong>Recuperar&lt;/strong>: quando um usuário fizer uma pergunta, incorpore a consulta e encontre os documentos mais semelhantes&lt;/li>
&lt;li>&lt;strong>Gerar&lt;/strong>: Passe os documentos recuperados como contexto para o LLM junto com a pergunta do usuário&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[KernelFunction(&amp;#34;search_knowledge_base&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Description(&amp;#34;Searches the internal knowledge base for relevant information&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; SearchKnowledgeBase(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;The search query&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> query)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> queryEmbedding = &lt;span style="color:#ff7b72">await&lt;/span> _embeddingService.GenerateEmbeddingAsync(query);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> results = &lt;span style="color:#ff7b72">await&lt;/span> _vectorStore.SearchAsync(queryEmbedding, limit: &lt;span style="color:#a5d6ff">3&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34;\n\n&amp;#34;&lt;/span>, results.Select(r =&amp;gt; r.Text));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ao expor seu pipeline RAG como uma função de kernel, a IA pode decidir automaticamente quando precisa pesquisar sua base de conhecimento – mantendo a orquestração limpa e deixando o modelo fazer o que faz de melhor.&lt;/p>
&lt;h2 id="planejadores-e-agentes">Planejadores e Agentes&lt;/h2>
&lt;p>À medida que seus aplicativos de IA se tornam mais complexos, você precisará da IA para &lt;strong>planejar e executar tarefas de várias etapas&lt;/strong>. É aqui que entra a estrutura de agente do Semantic Kernel.&lt;/p>
&lt;h3 id="o-básico-agente-de-conclusão-de-bate-papo">O básico: agente de conclusão de bate-papo&lt;/h3>
&lt;p>O tipo de agente mais simples envolve um modelo de conclusão de chat com instruções e plug-ins:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#pragma&lt;/span> warning disable SKEXP0110
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> agent = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;DevAssistant&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a senior .NET developer assistant. Help users with code
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reviews, architecture decisions, and debugging. Always provide
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> code examples when relevant. Use your available tools to look up
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current information when needed.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Arguments = &lt;span style="color:#ff7b72">new&lt;/span> KernelArguments(settings)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>history.AddUserMessage(&lt;span style="color:#a5d6ff">&amp;#34;How should I structure a clean architecture project in .NET 8?&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> agent.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.Add(message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="colaboração-multiagente">Colaboração multiagente&lt;/h3>
&lt;p>As coisas ficam realmente poderosas quando você tem &lt;strong>vários agentes trabalhando juntos&lt;/strong>. O Kernel Semântico oferece suporte a padrões de chat em grupo onde agentes com diferentes especializações colaboram:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#pragma&lt;/span> warning disable SKEXP0110
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> codeReviewer = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;CodeReviewer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You review C&lt;span style="color:#f85149">#&lt;/span> code &lt;span style="color:#ff7b72">for&lt;/span> bugs, performance issues, and best practices.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Be specific about what you find and suggest concrete fixes.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> securityAuditor = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;SecurityAuditor&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You focus exclusively &lt;span style="color:#ff7b72">on&lt;/span> security vulnerabilities &lt;span style="color:#ff7b72">in&lt;/span> code.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Look &lt;span style="color:#ff7b72">for&lt;/span> injection attacks, authentication issues, data exposure,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and OWASP Top &lt;span style="color:#a5d6ff">10&lt;/span> violations.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> groupChat = &lt;span style="color:#ff7b72">new&lt;/span> AgentGroupChat(codeReviewer, securityAuditor)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecutionSettings = &lt;span style="color:#ff7b72">new&lt;/span> AgentGroupChatSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TerminationStrategy = &lt;span style="color:#ff7b72">new&lt;/span> MaximumIterationTerminationStrategy(&lt;span style="color:#a5d6ff">4&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>groupChat.AddChatMessage(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessageContent(AuthorRole.User, &lt;span style="color:#a5d6ff">&amp;#34;Review this code: ...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> groupChat.InvokeAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;[{message.AuthorName}]: {message.Content}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esse padrão é extremamente útil para tarefas complexas onde diferentes perspectivas ou áreas de especialização precisam ser consideradas. Cada agente opera com seu próprio prompt de sistema e pode ter seu próprio conjunto de plug-ins.&lt;/p>
&lt;h2 id="exemplo-do-mundo-real-construindo-um-assistente-de-documentação-de-projeto">Exemplo do mundo real: construindo um assistente de documentação de projeto&lt;/h2>
&lt;p>Vamos unir tudo com um exemplo prático: um assistente de IA que ajuda os desenvolvedores a entender uma base de código lendo arquivos e respondendo perguntas.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.OpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.ComponentModel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Define our plugins&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">FileSystemPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _rootPath;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> FileSystemPlugin(&lt;span style="color:#ff7b72">string&lt;/span> rootPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _rootPath = rootPath;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;read_file&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Reads the contents of a file from the project directory&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ReadFile(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Relative path to the file&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> fullPath = Path.Combine(_rootPath, path);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!File.Exists(fullPath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;File not found: {path}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> content = &lt;span style="color:#ff7b72">await&lt;/span> File.ReadAllTextAsync(fullPath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Truncate very large files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (content.Length &amp;gt; &lt;span style="color:#a5d6ff">8000&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content = content[..&lt;span style="color:#a5d6ff">8000&lt;/span>] + &lt;span style="color:#a5d6ff">&amp;#34;\n... [truncated]&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> content;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;list_files&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Lists files in a directory, optionally filtered by extension&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> ListFiles(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Relative directory path&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> directory,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;File extension filter like &amp;#39;.cs&amp;#39; or &amp;#39;.json&amp;#39;&amp;#34;)] &lt;span style="color:#ff7b72">string?&lt;/span> extension = &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> fullPath = Path.Combine(_rootPath, directory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!Directory.Exists(fullPath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;Directory not found: {directory}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> files = Directory.GetFiles(fullPath, &lt;span style="color:#a5d6ff">&amp;#34;*.*&amp;#34;&lt;/span>, SearchOption.AllDirectories)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(f =&amp;gt; Path.GetRelativePath(_rootPath, f))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(f =&amp;gt; extension &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span> || f.EndsWith(extension))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Take(&lt;span style="color:#a5d6ff">50&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34;\n&amp;#34;&lt;/span>, files);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">DocumentationPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;generate_summary&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Generates a structured markdown summary for documentation&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GenerateSummaryTemplate(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Name of the component&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> componentName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Brief description&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> description,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Key responsibilities as comma-separated values&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> responsibilities)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> items = responsibilities.Split(&lt;span style="color:#a5d6ff">&amp;#39;,&amp;#39;&lt;/span>, StringSplitOptions.TrimEntries);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bullets = &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34;\n&amp;#34;&lt;/span>, items.Select(r =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;- {r}&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> ## {componentName}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> {description}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> ### Responsibilities
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> {bullets}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> ---
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> *Generated documentation — review and expand as needed.*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Wire it all up&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromObject(&lt;span style="color:#ff7b72">new&lt;/span> FileSystemPlugin(&lt;span style="color:#a5d6ff">&amp;#34;./src&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromType&amp;lt;DocumentationPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> chatService = kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>history.AddSystemMessage(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a codebase documentation assistant. You help developers understand
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> projects &lt;span style="color:#ff7b72">by&lt;/span> reading source files and explaining architecture, patterns,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and design decisions.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> When asked about code, use your tools to read the actual files rather
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> than guessing. Be specific and reference actual code when possible.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Generate documentation artifacts when asked.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> settings = &lt;span style="color:#ff7b72">new&lt;/span> OpenAIPromptExecutionSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;Documentation Assistant ready. Ask me about your codebase!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;Type &amp;#39;exit&amp;#39; to quit.\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">while&lt;/span> (&lt;span style="color:#79c0ff">true&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;You: &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> input = Console.ReadLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(input) || input.Equals(&lt;span style="color:#a5d6ff">&amp;#34;exit&amp;#34;&lt;/span>, StringComparison.OrdinalIgnoreCase))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(input);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> chatService.GetChatMessageContentAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> executionSettings: settings,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> kernel: kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;\nAssistant: {response.Content}\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddAssistantMessage(response.Content ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Este assistente pode:- &lt;strong>Liste e leia arquivos&lt;/strong> do diretório do seu projeto&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Responda a perguntas&lt;/strong> sobre a base de código lendo arquivos de origem reais&lt;/li>
&lt;li>&lt;strong>Gerar documentação&lt;/strong> em formato markdown&lt;/li>
&lt;li>&lt;strong>Mantenha o contexto da conversa&lt;/strong> para que as perguntas de acompanhamento funcionem naturalmente&lt;/li>
&lt;/ul>
&lt;p>A IA decide automaticamente quando ligar para &lt;code>read_file&lt;/code>, &lt;code>list_files&lt;/code> ou &lt;code>generate_summary&lt;/code> com base no que você pergunta. Pergunte &amp;ldquo;O que o OrderService faz?&amp;rdquo; e ele lerá o arquivo, analisará e explicará. Peça para &amp;ldquo;Gerar documentação para o módulo de autenticação&amp;rdquo; e ele explorará os arquivos, entenderá a estrutura e produzirá um resumo formatado.&lt;/p>
&lt;h2 id="dicas-para-produção">Dicas para Produção&lt;/h2>
&lt;p>Antes de enviar seu aplicativo de Kernel Semântico, aprendi algumas coisas da maneira mais difícil:&lt;/p>
&lt;p>&lt;strong>Use a injeção de dependência corretamente.&lt;/strong> Em aplicativos ASP.NET Core, registre o Kernel e os serviços em seu contêiner DI em vez de criá-los in-line:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddKernel()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: configuration[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:Endpoint&amp;#34;&lt;/span>]!,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: configuration[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:ApiKey&amp;#34;&lt;/span>]!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Plugins.AddFromType&amp;lt;TimePlugin&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddFromType&amp;lt;OrderPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Trate de erros normalmente.&lt;/strong> As chamadas LLM podem falhar, atingir o tempo limite ou retornar resultados inesperados. Envolva suas invocações em blocos try-catch e implemente políticas de repetição com Polly ou os recursos de resiliência integrados.&lt;/p>
&lt;p>&lt;strong>Monitore o uso de tokens.&lt;/strong> Cada prompt, cada descrição de função e cada parte do histórico de bate-papo consome tokens. Use filtros para registrar e rastrear o uso:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>kernel.FunctionInvocationFilters.Add(&lt;span style="color:#ff7b72">new&lt;/span> LoggingFilter());
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Mantenha as descrições de suas funções precisas.&lt;/strong> Descrições vagas levam a IA a chamar funções incorretamente. Teste suas descrições perguntando: “Se eu apenas lesse a descrição, saberia exatamente quando e como usar esta função?”&lt;/p>
&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>O Kernel Semântico é uma daquelas bibliotecas que muda fundamentalmente a forma como você pensa sobre a construção de aplicativos. Não é apenas um wrapper de API: é uma estrutura de orquestração que permite compor recursos de IA com código tradicional de uma forma que seja sustentável, testável e pronta para produção.&lt;/p>
&lt;p>O que mais adoro nele é que respeita o ecossistema .NET. Ele usa padrões que você já conhece – injeção de dependência, atributos, assíncrono/espera, interfaces – e os estende ao mundo da IA. Você não precisa aprender um paradigma completamente novo; você acabou de adicionar IA como outro recurso em seu kit de ferramentas.&lt;/p>
&lt;p>Se você está criando aplicativos .NET e ainda não explorou o Kernel Semântico, agora é a hora. O SDK é estável, a comunidade é ativa e os padrões que ele permite — desde a simples orquestração imediata até a colaboração multiagente — estão se tornando habilidades essenciais para os desenvolvedores modernos.&lt;/p>
&lt;p>Comece pequeno. Crie um Kernel, registre um plugin e observe a IA chamar seu código. Depois que isso acontecer, você começará a ver oportunidades para adicionar inteligência em todos os lugares dos seus aplicativos.&lt;/p>
&lt;p>A &lt;a href="https://learn.microsoft.com/semantic-kernel/overview/">documentação oficial&lt;/a> e o &lt;a href="https://github.com/microsoft/semantic-kernel">repositório GitHub&lt;/a> são excelentes recursos para continuar sua jornada. Feliz edifício!&lt;/p></content:encoded><category>AI</category><category>.NET</category><category>Semantic Kernel</category><category>Azure</category></item><item><title>Herdando componentes no Blazor</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-inherit-components/</link><pubDate>Thu, 04 Sep 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-inherit-components/</guid><description>Estenda e reutilize componentes do Blazor por meio de herança usando ComponentBase e classes base compartilhadas.</description><content:encoded>&lt;p>Eu estava construindo um projeto que tinha várias páginas de formulário e cada uma delas tinha a mesma lógica de estado de carregamento, o mesmo tratamento de erros e as mesmas notificações do sistema. Copiar e colar tudo isso parecia errado, então examinei a herança de componentes no Blazor. Acontece que é bastante simples, já que os componentes do Blazor são apenas classes C#.&lt;/p>
&lt;h1 id="o-básico">O básico&lt;/h1>
&lt;p>Cada componente Blazor herda de &lt;code>ComponentBase&lt;/code> por padrão. Você pode criar sua própria classe base que estende &lt;code>ComponentBase&lt;/code> e então fazer com que seus componentes sejam herdados dela.&lt;/p>
&lt;p>Digamos que a maioria de nossas páginas precise de estado de carregamento e tratamento de erros. Podemos criar uma classe base:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">PageBase&lt;/span> : ComponentBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> IsLoading { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> ErrorMessage { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task LoadDataAsync(Func&amp;lt;Task&amp;gt; action)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IsLoading = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ErrorMessage = &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> action();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ErrorMessage = ex.Message;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IsLoading = &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>#Usando a classe base&lt;/p>
&lt;p>Agora, em qualquer componente de página, em vez de herdar de &lt;code>ComponentBase&lt;/code>, herdamos de nosso &lt;code>PageBase&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/users&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inherits PageBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@if (IsLoading)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;spinner&amp;#34;&lt;/span>&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (ErrorMessage &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;alert alert-danger&amp;#34;&lt;/span>&amp;gt;@ErrorMessage&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ul&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @foreach (&lt;span style="color:#ff7b72">var&lt;/span> user &lt;span style="color:#ff7b72">in&lt;/span> users)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;li&amp;gt;@user.Name&amp;lt;/li&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/ul&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;User&amp;gt; users = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> LoadDataAsync(&lt;span style="color:#ff7b72">async&lt;/span> () =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> users = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;List&amp;lt;User&amp;gt;&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/users&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A diretiva &lt;code>@inherits PageBase&lt;/code> é a chave. Diz ao Blazor para usar nossa classe base em vez do padrão &lt;code>ComponentBase&lt;/code>. Agora temos &lt;code>IsLoading&lt;/code>, &lt;code>ErrorMessage&lt;/code> e &lt;code>LoadDataAsync()&lt;/code> gratuitamente em todas as páginas herdadas dele.&lt;/p>
&lt;h1 id="injetando-serviços-na-classe-base">Injetando serviços na classe base&lt;/h1>
&lt;p>Você também pode injetar serviços na classe base para que estejam disponíveis para todos os componentes filhos:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">PageBase&lt;/span> : ComponentBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Inject]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> NavigationManager Navigation { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">default&lt;/span>!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Inject]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> IToastService Toast { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">default&lt;/span>!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> IsLoading { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> ErrorMessage { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> NavigateBack() =&amp;gt; Navigation.NavigateTo(&lt;span style="color:#a5d6ff">&amp;#34;javascript:history.back()&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShowSuccess(&lt;span style="color:#ff7b72">string&lt;/span> message) =&amp;gt; Toast.ShowSuccess(message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Cada componente que herda de &lt;code>PageBase&lt;/code> agora tem acesso a &lt;code>Navigation&lt;/code>, &lt;code>Toast&lt;/code>, &lt;code>NavigateBack()&lt;/code> e &lt;code>ShowSuccess()&lt;/code> sem precisar injetar nada.&lt;/p>
&lt;h1 id="indo-mais-fundo-com-classes-base-genéricas">Indo mais fundo com classes base genéricas&lt;/h1>
&lt;p>Você pode até criar classes base genéricas para padrões CRUD comuns:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CrudPageBase&lt;/span>&amp;lt;T&amp;gt; : PageBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> List&amp;lt;T&amp;gt; Items { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> T? SelectedItem { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; FetchItems();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> Task DeleteItem(T item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> LoadDataAsync(&lt;span style="color:#ff7b72">async&lt;/span> () =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Items = &lt;span style="color:#ff7b72">await&lt;/span> FetchItems();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnDelete(T item)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> LoadDataAsync(&lt;span style="color:#ff7b72">async&lt;/span> () =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> DeleteItem(item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Items = &lt;span style="color:#ff7b72">await&lt;/span> FetchItems();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ShowSuccess(&lt;span style="color:#a5d6ff">&amp;#34;Item deleted.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Então sua página real fica super limpa:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inherits CrudPageBase&amp;lt;Product&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>* just the markup, all logic lives &lt;span style="color:#ff7b72">in&lt;/span> the &lt;span style="color:#ff7b72">base&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> *&lt;span style="color:#f85149">@&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> Task&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt; FetchItems()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; Http.GetFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/products&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> Task DeleteItem(Product item)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; Http.DeleteAsync(&lt;span style="color:#a5d6ff">$&amp;#34;api/products/{item.Id}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="quando-usar-e-quando-não-usar">Quando usar e quando não usar&lt;/h1>
&lt;p>A herança de componentes é ótima para comportamento compartilhado, como estados de carregamento, tratamento de erros, verificações de autenticação ou padrões CRUD. Mas não exagere com hierarquias de herança profundas - se você estiver indo mais de dois níveis de profundidade, provavelmente estará melhor com a composição (como a abordagem &lt;code>LoadingComponent&lt;/code> de uma postagem anterior).&lt;/p>
&lt;p>Eu normalmente mantenho uma classe base por &amp;ldquo;tipo&amp;rdquo; de página: &lt;code>PageBase&lt;/code> para páginas normais, &lt;code>FormPageBase&lt;/code> para formulários, e isso é tudo.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem! Sinta-se à vontade para entrar em contato comigo em qualquer mídia social em &lt;strong>@emimontesdeoca&lt;/strong>.&lt;/p>
&lt;h1 id="recursos">Recursos&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/#inheritance">Herança de componente ASP.NET Core Razor&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>C#</category></item><item><title>.NET Aspire: criando aplicativos nativos da nuvem da maneira certa</title><link>https://emimontesdeoca.github.io/pt/posts/dotnet-aspire-cloud-native/</link><pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/dotnet-aspire-cloud-native/</guid><description>Um guia detalhado para .NET Aspire — a pilha opinativa para a construção de aplicativos distribuídos, observáveis ​​e prontos para produção em .NET.</description><content:encoded>&lt;p>Se você já criou um aplicativo distribuído em .NET, você sabe o que fazer. Você cria uma API Web, adiciona um trabalhador em segundo plano, adiciona Redis para armazenamento em cache, PostgreSQL para persistência, talvez RabbitMQ para mensagens - e de repente você está gastando mais tempo conectando infraestrutura do que escrevendo lógica de negócios. Cadeias de conexão espalhadas por arquivos &lt;code>appsettings.json&lt;/code>, verificações de integridade que você esqueceu de configurar, observabilidade que é sempre o &amp;ldquo;problema do próximo sprint&amp;rdquo;.&lt;/p>
&lt;p>Eu estive lá. Mais vezes do que eu gostaria de admitir.&lt;/p>
&lt;p>Esse é exatamente o problema para o qual o &lt;strong>.NET Aspire&lt;/strong> foi projetado para resolver. Depois de executá-lo em produção por vários meses, quero compartilhar o que aprendi - o que é bom, o que é ótimo e as pegadinhas.&lt;/p>
&lt;h2 id="o-que-é-o-net-aspire">O que é o .NET Aspire?&lt;/h2>
&lt;p>O .NET Aspire é uma pilha opinativa para a construção de aplicativos distribuídos, observáveis ​​e prontos para produção com o .NET. Não é uma estrutura no sentido tradicional — ela não substitui o ASP.NET Core nem força você a adotar um novo modelo de programação. Em vez disso, ele se baseia no que você já sabe e preenche as lacunas que sempre existiram na criação de aplicativos nativos da nuvem.&lt;/p>
&lt;p>Basicamente, o Aspire oferece quatro coisas:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>AppHost&lt;/strong> — Um projeto que define toda a topologia de seu aplicativo distribuído. Quais serviços existem, de que dependem e como se conectam.&lt;/li>
&lt;li>&lt;strong>Padrões de serviço&lt;/strong> — Um projeto compartilhado que configura preocupações transversais, como verificações de integridade, políticas de resiliência e OpenTelemetry — uma vez, para todos os seus serviços.&lt;/li>
&lt;li>&lt;strong>Componentes&lt;/strong> — pacotes NuGet que fornecem integrações padronizadas com serviços de apoio como Redis, PostgreSQL, RabbitMQ, Azure Storage e muito mais.&lt;/li>
&lt;li>&lt;strong>Painel do desenvolvedor&lt;/strong> — Uma IU em tempo real que mostra logs, rastreamentos e métricas de todo o seu aplicativo distribuído durante o desenvolvimento local.&lt;/li>
&lt;/ol>
&lt;p>A filosofia é simples: se todo aplicativo em nuvem .NET precisa dessas coisas, por que estamos todos implementando-as do zero sempre?&lt;/p>
&lt;h2 id="configurando-seu-primeiro-projeto-aspire">Configurando seu primeiro projeto Aspire&lt;/h2>
&lt;p>Começar é simples. Você precisará do .NET 8 ou posterior e da carga de trabalho do Aspire instalada:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet workload update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet workload install aspire
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora crie um novo projeto inicial do Aspire:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new aspire-starter -n MyCloudApp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso gera uma solução com quatro projetos:&lt;/p>
&lt;ul>
&lt;li>&lt;code>MyCloudApp.AppHost&lt;/code> — O orquestrador&lt;/li>
&lt;li>&lt;code>MyCloudApp.ServiceDefaults&lt;/code> — Configuração compartilhada&lt;/li>
&lt;li>&lt;code>MyCloudApp.ApiService&lt;/code> — Um exemplo de API da Web&lt;/li>
&lt;li>&lt;code>MyCloudApp.Web&lt;/code> — Uma interface do Blazor&lt;/li>
&lt;/ul>
&lt;p>Execute o AppHost e você verá imediatamente o painel do Aspire aberto em seu navegador, mostrando todos os seus serviços, seus logs e sua saúde. Nenhum arquivo Docker Compose. Sem gerenciamento manual de portas. Simplesmente funciona.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet run --project MyCloudApp.AppHost
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Essa primeira experiência foi o que me fisgou. Em menos de um minuto, você tem um aplicativo distribuído totalmente orquestrado com observabilidade integrada.&lt;/p>
&lt;h2 id="o-padrão-apphost">O padrão AppHost&lt;/h2>
&lt;p>O AppHost é onde mora a magia. É um pequeno aplicativo de console que usa um padrão de construtor para definir a topologia do seu aplicativo distribuído — quais recursos existem e como os serviços se conectam a eles.&lt;/p>
&lt;p>Esta é a aparência de um AppHost realista:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = DistributedApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Infrastructure resources&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> postgres = builder.AddPostgres(&lt;span style="color:#a5d6ff">&amp;#34;postgres&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithPgAdmin()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddDatabase(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> redis = builder.AddRedis(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> rabbitmq = builder.AddRabbitMQ(&lt;span style="color:#a5d6ff">&amp;#34;messaging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithManagementPlugin();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Application services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> catalogApi = builder.AddProject&amp;lt;Projects.CatalogApi&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;catalog-api&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(postgres)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(redis)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(rabbitmq);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> orderApi = builder.AddProject&amp;lt;Projects.OrderApi&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;order-api&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(postgres)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(rabbitmq);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> frontend = builder.AddProject&amp;lt;Projects.WebFrontend&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;frontend&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithExternalHttpEndpoints()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(catalogApi)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(orderApi);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Build().Run();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Leia esse código em voz alta. Praticamente se documenta. &lt;span style="color:#a5d6ff">&amp;#34;A API do catálogo faz referência a PostgreSQL, Redis e RabbitMQ. O frontend faz referência à API do catálogo e à API do pedido.&amp;#34;&lt;/span> Essa &lt;span style="color:#f85149">é&lt;/span> a sua arquitetura, expressa em código.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Algumas coisas dignas de nota:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">`&lt;/span>WithReference&lt;span style="color:#f85149">`&lt;/span>** faz o trabalho pesado. Ele injeta automaticamente cadeias de conexão e URLs de serviço no projeto de consumo por meio de variáveis &lt;span style="color:#f85149">​​&lt;/span>de ambiente e configuração. Seus serviços não precisam saber *onde* o Redis está sendo executado &lt;span style="color:#f85149">—&lt;/span> o Aspire cuida disso.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">`&lt;/span>WithPgAdmin()&lt;span style="color:#f85149">`&lt;/span>** e **&lt;span style="color:#f85149">`&lt;/span>WithManagementPlugin()&lt;span style="color:#f85149">`&lt;/span>** ativam UIs de administração para PostgreSQL e RabbitMQ juntamente com os serviços reais. Durante o desenvolvimento, eles são inestimáveis.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">`&lt;/span>WithExternalHttpEndpoints()&lt;span style="color:#f85149">`&lt;/span>** marca um serviço como acessível externamente, o que &lt;span style="color:#f85149">é&lt;/span> importante no momento da implantação.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> Configuração de recursos
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Você pode configurar recursos com controle refinado:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> postgres = builder.AddPostgres(&lt;span style="color:#a5d6ff">&amp;#34;postgres&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithEnvironment(&lt;span style="color:#a5d6ff">&amp;#34;POSTGRES_MAX_CONNECTIONS&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;200&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithDataVolume(&lt;span style="color:#a5d6ff">&amp;#34;postgres-data&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddDatabase(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> redis = builder.AddRedis(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithDataVolume(&lt;span style="color:#a5d6ff">&amp;#34;redis-data&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Os volumes de dados garantem que seus dados de desenvolvimento local sobrevivam às reinicializações do contêiner. Pequenos detalhes, grande melhoria na qualidade de vida.&lt;/p>
&lt;h2 id="padrões-de-serviço-o-herói-desconhecido">Padrões de serviço: o herói desconhecido&lt;/h2>
&lt;p>O projeto &lt;code>ServiceDefaults&lt;/code> é a parte mais subestimada do Aspire. É uma biblioteca compartilhada à qual todos os serviços da sua solução fazem referência e configura todas as preocupações transversais que você esqueceria ou implementaria de forma inconsistente.&lt;/p>
&lt;p>Esta é a aparência de um &lt;code>Extensions.cs&lt;/code> típico em ServiceDefaults:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Extensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IHostApplicationBuilder AddServiceDefaults(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> IHostApplicationBuilder builder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.ConfigureOpenTelemetry();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.AddDefaultHealthChecks();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Services.AddServiceDiscovery();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Services.ConfigureHttpClientDefaults(http =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> http.AddStandardResilienceHandler();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> http.AddServiceDiscovery();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> builder;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IHostApplicationBuilder ConfigureOpenTelemetry(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> IHostApplicationBuilder builder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Logging.AddOpenTelemetry(logging =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logging.IncludeFormattedMessage = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logging.IncludeScopes = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Services.AddOpenTelemetry()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithMetrics(metrics =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> metrics.AddAspNetCoreInstrumentation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddHttpClientInstrumentation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddRuntimeInstrumentation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithTracing(tracing =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tracing.AddAspNetCoreInstrumentation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddHttpClientInstrumentation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddGrpcClientInstrumentation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.AddOpenTelemetryExporters();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> builder;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IHostApplicationBuilder AddDefaultHealthChecks(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> IHostApplicationBuilder builder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Services.AddHealthChecks()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddCheck(&lt;span style="color:#a5d6ff">&amp;#34;self&amp;#34;&lt;/span>, () =&amp;gt; HealthCheckResult.Healthy(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&amp;#34;live&amp;#34;]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> builder;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Então, em &lt;code>Program.cs&lt;/code> de cada serviço, uma linha faz tudo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddServiceDefaults();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Your service-specific configuration...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapDefaultEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Essa única chamada &lt;code>AddServiceDefaults()&lt;/code> fornece:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>OpenTelemetry&lt;/strong> com registro estruturado, métricas e rastreamento distribuído&lt;/li>
&lt;li>&lt;strong>Verificações de integridade&lt;/strong> com pontos de extremidade de atividade e prontidão&lt;/li>
&lt;li>&lt;strong>Descoberta de serviços&lt;/strong> para que os serviços possam se encontrar pelo nome&lt;/li>
&lt;li>&lt;strong>Políticas de resiliência&lt;/strong> em todas as chamadas HTTP de saída (novas tentativas, disjuntores, tempos limite)&lt;/li>
&lt;/ul>
&lt;p>Isso é o que separa uma demonstração “funciona na minha máquina” de um sistema pronto para produção. E o Aspire torna isso o padrão, não uma reflexão tardia.&lt;/p>
&lt;h2 id="componentes-do-aspire">Componentes do Aspire&lt;/h2>
&lt;p>Os componentes Aspire são pacotes NuGet que padronizam como seus serviços se conectam à infraestrutura de apoio. Elas são mais do que apenas bibliotecas cliente: elas incluem verificações de integridade, registro, rastreamento e resiliência configurável pronta para uso.&lt;/p>
&lt;h3 id="redis">Redis&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddServiceDefaults();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddRedisDistributedCache(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É isso. A string de conexão vem do AppHost via &lt;code>WithReference&lt;/code>. O componente registra um &lt;code>IDistributedCache&lt;/code> apoiado por Redis, com verificações de integridade e instrumentação OpenTelemetry já instaladas.&lt;/p>
&lt;p>Você também pode usar o Redis para cache de saída:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.AddRedisOutputCache(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="postgresql-com-entity-framework-core">PostgreSQL com Entity Framework Core&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.AddNpgsqlDbContext&amp;lt;CatalogDbContext&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>, settings =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> settings.DisableRetry = &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso registra seu &lt;code>DbContext&lt;/code> com uma conexão com o banco de dados &lt;code>catalogdb&lt;/code> definido no AppHost. Inclui pool de conexões, verificações de integridade e políticas de nova tentativa.&lt;/p>
&lt;h3 id="coelhomq">CoelhoMQ&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.AddRabbitMQClient(&lt;span style="color:#a5d6ff">&amp;#34;messaging&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Registra um &lt;code>IConnection&lt;/code> da biblioteca cliente RabbitMQ, totalmente configurado e com integridade verificada.&lt;/p>
&lt;h3 id="integrações-azure">Integrações Azure&lt;/h3>
&lt;p>O Aspire também possui componentes de primeira classe para serviços Azure:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Azure Blob Storage&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureBlobClient(&lt;span style="color:#a5d6ff">&amp;#34;blobs&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Azure Service Bus&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureServiceBusClient(&lt;span style="color:#a5d6ff">&amp;#34;servicebus&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Azure Key Vault&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureKeyVaultClient(&lt;span style="color:#a5d6ff">&amp;#34;secrets&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>O padrão &lt;span style="color:#f85149">é&lt;/span> sempre o mesmo: uma linha no AppHost para definir o recurso, uma linha no serviço consumidor para utilizá-lo. Os detalhes da conexão fluem automaticamente.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> O painel &lt;span style="color:#ff7b72">do&lt;/span> desenvolvedor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>O painel &lt;span style="color:#ff7b72">do&lt;/span> Aspire &lt;span style="color:#f85149">é&lt;/span> um daqueles recursos que parece interessante até você realmente usá-lo - então você não consegue se imaginar trabalhando sem ele.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Quando você executa seu AppHost localmente, o painel &lt;span style="color:#f85149">é&lt;/span> iniciado e fornece:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **Visão geral dos recursos** &lt;span style="color:#f85149">—&lt;/span> Visão geral de todos os seus serviços e infraestrutura, com indicadores de status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **Logs estruturados** &lt;span style="color:#f85149">—&lt;/span> Streaming de logs em tempo real de todos os serviços, filtráveis e pesquisáveis
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **Rastreamentos distribuídos** &lt;span style="color:#f85149">—&lt;/span> Rastreamentos de solicitação de ponta a ponta abrangendo vários serviços, visualizados como gráficos em chamas
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **Métricas** &lt;span style="color:#f85149">—&lt;/span> taxas de solicitação HTTP, taxas de erro, latências e métricas personalizadas em tempo real
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **Logs &lt;span style="color:#ff7b72">do&lt;/span> console** &lt;span style="color:#f85149">—&lt;/span> stdout/stderr brutos de cada contêiner e projeto
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>O rastreamento distribuído &lt;span style="color:#f85149">é&lt;/span> particularmente valioso. Quando uma solicitação chega ao seu frontend, flui pela API &lt;span style="color:#ff7b72">do&lt;/span> catálogo, toca no Redis e no PostgreSQL &lt;span style="color:#f85149">–&lt;/span> você vê toda a cadeia com tempo para cada salto. Chega de adivinhar onde está o gargalo.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Achei o painel mais &lt;span style="color:#f85149">ú&lt;/span>til durante a depuração. Em vez de seguir várias janelas de terminal ou alternar entre arquivos de log, tudo fica em um só lugar com IDs de correlação que vinculam eventos relacionados entre serviços.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>O painel também está disponível como um contêiner independente, o que significa que você pode usá-lo em ambientes de teste ou pipelines de CI:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>bash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker run --rm -p &lt;span style="color:#a5d6ff">18888&lt;/span>:&lt;span style="color:#a5d6ff">18888&lt;/span> \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mcr.microsoft.com/dotnet/aspire-dashboard:latest
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="implantação">Implantação&lt;/h2>
&lt;p>O Aspire ajuda durante o desenvolvimento, mas e a produção? É aqui que as coisas ficam práticas.&lt;/p>
&lt;h3 id="aplicativos-de-contêiner-do-azure">Aplicativos de contêiner do Azure&lt;/h3>
&lt;p>O caminho de implantação mais simples é o Azure Container Apps (ACA), que tem suporte Aspire de primeira classe. Você pode implantar diretamente usando a CLI do Desenvolvedor do Azure:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>azd init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>azd up
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O comando &lt;code>azd init&lt;/code> detecta seu Aspire AppHost e gera a infraestrutura como código necessária. &lt;code>azd up&lt;/code> provisiona tudo — registro de contêiner, aplicativos de contêiner, bancos de dados, instâncias Redis — com base em sua topologia AppHost.&lt;/p>
&lt;p>Seu AppHost se torna essencialmente seu manifesto de implantação. O mesmo código que define &amp;ldquo;catalog-api depende de PostgreSQL e Redis&amp;rdquo; orienta o provisionamento da infraestrutura.&lt;/p>
&lt;p>###Kubernetes&lt;/p>
&lt;p>Para implantações do Kubernetes, o Aspire não gera manifestos diretamente, mas a topologia do AppHost é mapeada de forma limpa para os recursos do Kubernetes. A ferramenta da comunidade &lt;code>aspirate&lt;/code> (Aspir8) pode gerar gráficos Helm ou manifestos Kubernetes a partir de seu AppHost:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet tool install -g aspirate
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>aspirate generate
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>aspirate apply
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="considerações-de-implantação">Considerações de implantação&lt;/h3>
&lt;p>Algumas coisas para manter em mente:- &lt;strong>As cadeias de conexão mudam entre ambientes.&lt;/strong> Localmente, o Aspire ativa contêineres e injeta cadeias de conexão automaticamente. Na produção, você apontará para serviços gerenciados. O Aspire usa configuração .NET padrão, portanto, as variáveis ​​de ambiente e o Azure Key Vault funcionam conforme esperado.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>O AppHost não funciona em produção.&lt;/strong> É uma ferramenta de orquestração de desenvolvimento e implantação. Na produção, seus serviços são executados de forma independente, configurados por meio de variáveis ​​de ambiente e orquestradores.&lt;/li>
&lt;li>&lt;strong>Os recursos de infraestrutura tornam-se serviços geridos.&lt;/strong> O seu contentor PostgreSQL local torna-se a Base de Dados Azure para PostgreSQL. O seu contentor Redis local torna-se Azure Cache para Redis. O código de consumo não muda.&lt;/li>
&lt;/ul>
&lt;h2 id="dicas-do-mundo-real">Dicas do mundo real&lt;/h2>
&lt;p>Depois de executar o Aspire em produção por um tempo, aqui estão as lições que nos pouparam tempo:&lt;/p>
&lt;h3 id="1-use-verificações-personalizadas-do-ciclo-de-vida-dos-recursos">1. Use verificações personalizadas do ciclo de vida dos recursos&lt;/h3>
&lt;p>Não confie apenas na inicialização do contêiner para determinar se um recurso está pronto. O PostgreSQL pode aceitar conexões TCP antes de estar realmente pronto para atender consultas.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> postgres = builder.AddPostgres(&lt;span style="color:#a5d6ff">&amp;#34;postgres&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddDatabase(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithHealthCheck();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O Aspire pode executar verificações de integridade de recursos e manter serviços dependentes até que estejam realmente prontos.&lt;/p>
&lt;h3 id="2-extraia-padrões-comuns-em-extensões">2. Extraia padrões comuns em extensões&lt;/h3>
&lt;p>Se vários serviços compartilharem configurações semelhantes, crie métodos de extensão:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">AppHostExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IResourceBuilder&amp;lt;ProjectResource&amp;gt; AddWorkerService(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> IDistributedApplicationBuilder builder,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IResourceBuilder&amp;lt;IResourceWithConnectionString&amp;gt; db,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IResourceBuilder&amp;lt;IResourceWithConnectionString&amp;gt; messaging)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> builder.AddProject&amp;lt;Projects.WorkerService&amp;gt;(name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(messaging)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReplicas(&lt;span style="color:#a5d6ff">3&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-aproveite-withreplicas-para-testes-de-carga">3. Aproveite WithReplicas para testes de carga&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> catalogApi = builder.AddProject&amp;lt;Projects.CatalogApi&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;catalog-api&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(postgres)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(redis)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReplicas(&lt;span style="color:#a5d6ff">5&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>WithReplicas&lt;/code> ativa várias instâncias de um serviço localmente. Isso é ótimo para testar balanceamento de carga, bugs de simultaneidade e comportamento de cache distribuído sem implantar em um cluster.&lt;/p>
&lt;h3 id="4-use-parâmetros-para-valores-sensíveis">4. Use parâmetros para valores sensíveis&lt;/h3>
&lt;p>Não codifique credenciais, mesmo para desenvolvimento local:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> dbPassword = builder.AddParameter(&lt;span style="color:#a5d6ff">&amp;#34;db-password&amp;#34;&lt;/span>, secret: &lt;span style="color:#79c0ff">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> postgres = builder.AddPostgres(&lt;span style="color:#a5d6ff">&amp;#34;postgres&amp;#34;&lt;/span>, password: dbPassword)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddDatabase(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ao executar localmente, o Aspire solicitará o valor ou o lerá nos segredos do usuário. No CI/CD, isso vem de variáveis ​​de ambiente.&lt;/p>
&lt;h3 id="5-testes-de-integração-tornam-se-triviais">5. Testes de integração tornam-se triviais&lt;/h3>
&lt;p>O Aspire inclui um pacote de testes que torna o teste de integração de aplicativos distribuídos extremamente fácil:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Fact]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task CatalogApiReturnsProducts()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> appHost = &lt;span style="color:#ff7b72">await&lt;/span> DistributedApplicationTestingBuilder
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .CreateAsync&amp;lt;Projects.MyCloudApp_AppHost&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> app = &lt;span style="color:#ff7b72">await&lt;/span> appHost.BuildAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> app.StartAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = app.CreateHttpClient(&lt;span style="color:#a5d6ff">&amp;#34;catalog-api&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> httpClient.GetAsync(&lt;span style="color:#a5d6ff">&amp;#34;/api/products&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response.EnsureSuccessStatusCode();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> products = &lt;span style="color:#ff7b72">await&lt;/span> response.Content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ReadFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.NotEmpty(products);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso acelera todo o seu aplicativo distribuído - incluindo bancos de dados e corretores de mensagens - executa seu teste e o desmonta. Testes reais de integração em infraestrutura real, em seu pipeline de CI. Sem zombarias.&lt;/p>
&lt;h3 id="6-monitore-o-uso-de-recursos-localmente">6. Monitore o uso de recursos localmente&lt;/h3>
&lt;p>Ao executar vários contêineres localmente, fique atento ao consumo de recursos. Uma instância PostgreSQL, Redis e RabbitMQ com UI de gerenciamento pode consumir facilmente de 2 a 3 GB de RAM. Se você estiver em uma máquina restrita, considere usar configurações de recursos mais leves:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> redis = builder.AddRedis(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithContainerRuntimeArgs(&lt;span style="color:#a5d6ff">&amp;#34;--memory=256m&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>O .NET Aspire mudou fundamentalmente a forma como construo aplicativos distribuídos. Não porque introduza conceitos revolucionários — verificações de integridade, OpenTelemetry e orquestração de contêineres não são novidade. Mas porque isso os torna &lt;em>padrão&lt;/em>. Ele toma as centenas de pequenas decisões que você normalmente teria que tomar, implementa-as com padrões sensatos e permite substituí-las quando necessário.O padrão AppHost é, na minha opinião, a maior vitória. Ter a topologia de seu aplicativo distribuído expressa como código (não espalhada em arquivos Docker Compose, manifestos do Kubernetes e documentos README) torna o sistema compreensível. Novos membros da equipe podem abrir &lt;code>Program.cs&lt;/code> no AppHost e entender toda a arquitetura em minutos.&lt;/p>
&lt;p>Se você estiver construindo aplicativos distribuídos com .NET, dê uma olhada séria no Aspire. Comece com o modelo &lt;code>aspire-starter&lt;/code>, explore o painel e adote gradualmente os componentes conforme necessário. Você não precisa apostar tudo no primeiro dia – o Aspire é aditivo por design.&lt;/p>
&lt;p>Os dias em que você passava seu primeiro sprint conectando o padrão de infraestrutura acabaram. Deixe o Aspire cuidar do encanamento para que você possa se concentrar no que realmente importa: sua aplicação.&lt;/p></content:encoded><category>.NET</category><category>Azure</category><category>Cloud</category><category>Docker</category></item><item><title>Autenticação e autorização no Blazor: um guia prático</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-authentication-authorization/</link><pubDate>Sat, 12 Jul 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-authentication-authorization/</guid><description>Um guia prático para implementar autenticação e autorização em aplicativos Blazor — do ASP.NET Identity ao OAuth, acesso baseado em função e componentes de segurança.</description><content:encoded>&lt;p>Se você trabalhou com ASP.NET MVC ou Razor Pages, provavelmente tem um modelo mental de como a autenticação funciona: o middleware intercepta a solicitação, verifica um cookie ou token, preenche &lt;code>HttpContext.User&lt;/code> e você está pronto para a corrida. O Blazor altera esse modelo mental de maneiras sutis, mas importantes — e se você não entender essas diferenças desde o início, acabará depurando problemas de autenticação que parecem impossíveis.&lt;/p>
&lt;p>Nesta postagem, quero explicar como a autenticação e a autorização realmente funcionam no Blazor, abrangendo os modelos de hospedagem Server e WebAssembly. Iremos desde o básico até provedores personalizados, OAuth externo e as armadilhas que vi atrapalhar até mesmo desenvolvedores .NET experientes.&lt;/p>
&lt;h2 id="por-que-o-auth-no-blazor-é-diferente">Por que o Auth no Blazor é diferente&lt;/h2>
&lt;p>No ASP.NET tradicional, cada interação do usuário é uma solicitação HTTP. O servidor valida as credenciais, define um cookie e cada solicitação subsequente carrega esse cookie. O pipeline de autenticação é linear e previsível.&lt;/p>
&lt;p>Blazor Server opera em uma conexão SignalR persistente. Depois que a solicitação HTTP inicial carrega a página, todas as interações subsequentes acontecem por meio de WebSockets. Não há nenhuma nova solicitação HTTP para cada clique de botão, portanto o middleware não é executado novamente em cada interação. O &lt;code>HttpContext&lt;/code> está disponível durante a conexão inicial, mas depender dele durante toda a vida útil de um circuito é uma receita para bugs.&lt;/p>
&lt;p>Blazor WebAssembly é executado inteiramente no navegador. Não há nenhum &lt;code>HttpContext&lt;/code> no lado do servidor. O estado de autenticação deve ser obtido de uma API, armazenado no lado do cliente e gerenciado por meio de tokens — normalmente JWTs. O servidor confia no cliente apenas no que diz respeito à validação do token.&lt;/p>
&lt;p>Isso significa que o Blazor precisa de sua própria abstração para o estado de autenticação, que funcione independentemente do modelo de hospedagem. Essa abstração é o &lt;code>AuthenticationStateProvider&lt;/code>.&lt;/p>
&lt;h2 id="estado-de-autenticação-the-foundation">Estado de autenticação: The Foundation&lt;/h2>
&lt;p>No coração do sistema de autenticação do Blazor está &lt;code>AuthenticationStateProvider&lt;/code>. Esta é uma classe abstrata que expõe um único método crítico:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> Task&amp;lt;AuthenticationState&amp;gt; GetAuthenticationStateAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O objeto &lt;code>AuthenticationState&lt;/code> envolve um &lt;code>ClaimsPrincipal&lt;/code> — o mesmo modelo de identidade usado em todo o .NET. Os componentes não se comunicam diretamente com cookies ou tokens; eles perguntam ao &lt;code>AuthenticationStateProvider&lt;/code> o estado atual.&lt;/p>
&lt;p>Para disponibilizar esse estado para toda a sua árvore de componentes, o Blazor fornece &lt;code>CascadingAuthenticationState&lt;/code>. Você normalmente envolve seu roteador com ele em &lt;code>App.razor&lt;/code> ou em seu layout:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;CascadingAuthenticationState&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Router AppAssembly=&amp;#34;@typeof(App).Assembly&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Found Context=&amp;#34;routeData&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;AuthorizeRouteView RouteData=&amp;#34;@routeData&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DefaultLayout=&amp;#34;@typeof(MainLayout)&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;You&amp;#39;re not authorized to view this page.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/AuthorizeRouteView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Found&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NotFound&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;LayoutView Layout=&amp;#34;@typeof(MainLayout)&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;Page not found.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/LayoutView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NotFound&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Router&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/CascadingAuthenticationState&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O &lt;code>AuthorizeRouteView&lt;/code> está cumprindo uma função dupla aqui: verifica se o usuário está autenticado e autorizado antes de renderizar o componente da página correspondente e fornece uma UI substituta quando não está.&lt;/p>
&lt;p>No .NET 8 e posterior com o modelo Blazor unificado, você configurará isso em seu &lt;code>App.razor&lt;/code> e a estrutura manipulará o parâmetro em cascata automaticamente quando você usar &lt;code>AddCascadingAuthenticationState()&lt;/code> em seu registro de serviço.&lt;/p>
&lt;h2 id="integração-de-identidade-aspnet">Integração de identidade ASP.NET&lt;/h2>
&lt;p>Para a maioria dos projetos, você não precisa criar a autenticação do zero. O ASP.NET Identity oferece gerenciamento de usuários, hash de senha, autenticação de dois fatores e confirmação de conta prontos para uso.A configuração com o Blazor começa em &lt;code>Program.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddDbContext&amp;lt;ApplicationDbContext&amp;gt;(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.UseSqlServer(builder.Configuration.GetConnectionString(&lt;span style="color:#a5d6ff">&amp;#34;DefaultConnection&amp;#34;&lt;/span>)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddDefaultIdentity&amp;lt;IdentityUser&amp;gt;(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.SignIn.RequireConfirmedAccount = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.Password.RequireDigit = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.Password.RequiredLength = &lt;span style="color:#a5d6ff">8&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddRoles&amp;lt;IdentityRole&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEntityFrameworkStores&amp;lt;ApplicationDbContext&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddAuthentication(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.DefaultScheme = IdentityConstants.ApplicationScheme;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Com o modelo Blazor Web App no .NET 8+, a UI de identidade com scaffolding usa componentes Razor diretamente. Você obtém páginas de login, registro e gerenciamento de contas que se integram naturalmente ao restante do seu aplicativo Blazor - chega de mistura estranha de Razor Pages e componentes Blazor.&lt;/p>
&lt;p>O &lt;code>ApplicationDbContext&lt;/code> herda de &lt;code>IdentityDbContext&lt;/code> e você precisará executar migrações para criar as tabelas de identidade:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ApplicationDbContext&lt;/span> : IdentityDbContext&amp;lt;IdentityUser&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ApplicationDbContext(DbContextOptions&amp;lt;ApplicationDbContext&amp;gt; options)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : &lt;span style="color:#ff7b72">base&lt;/span>(options) { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet ef migrations add InitialIdentity
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet ef database update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="o-componente-authorizeview">O componente AuthorizeView&lt;/h2>
&lt;p>Depois que o estado de autenticação estiver fluindo pela sua árvore de componentes, &lt;code>AuthorizeView&lt;/code> permite renderizar a UI condicionalmente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;Welcome, @context.User.Identity?.Name!&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a href=&amp;#34;/account/manage&amp;#34;&amp;gt;Manage Account&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form method=&amp;#34;post&amp;#34; action=&amp;#34;/account/logout&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34;&amp;gt;Log Out&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a href=&amp;#34;/account/login&amp;#34;&amp;gt;Log In&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a href=&amp;#34;/account/register&amp;#34;&amp;gt;Register&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O parâmetro &lt;code>context&lt;/code> dentro de &lt;code>&amp;lt;Authorized&amp;gt;&lt;/code> dá acesso ao &lt;code>AuthenticationState&lt;/code>, para que você possa inspecionar declarações, funções e a identidade do usuário diretamente em sua marcação.&lt;/p>
&lt;p>Você também pode usar &lt;code>AuthorizeView&lt;/code> com funções e políticas:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;AuthorizeView Roles=&amp;#34;Admin,Moderator&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button @onclick=&amp;#34;DeletePost&amp;#34;&amp;gt;Delete Post&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;AuthorizeView Policy=&amp;#34;CanEditArticles&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button @onclick=&amp;#34;EditArticle&amp;#34;&amp;gt;Edit&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Uma coisa a ter em mente: &lt;code>AuthorizeView&lt;/code> é uma preocupação da interface do usuário. Oculta ou mostra elementos, mas não protege a lógica subjacente. Se alguém puder chamar seu endpoint de API ou invocar seu método diretamente, ele ignorará &lt;code>AuthorizeView&lt;/code> completamente. Sempre imponha a autorização também no lado do servidor.&lt;/p>
&lt;h2 id="o-atributo-autorizar">O atributo [Autorizar]&lt;/h2>
&lt;p>Para proteger uma página inteira, aplique o atributo &lt;code>[Authorize]&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/admin/dashboard&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@attribute [Authorize(Roles = &amp;#34;Admin&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Admin Dashboard&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;Only administrators can see this page.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Quando um usuário não autenticado navega para esta página, o &lt;code>AuthorizeRouteView&lt;/code> entra em ação e renderiza o modelo &lt;code>&amp;lt;NotAuthorized&amp;gt;&lt;/code> que você definiu anteriormente. Você pode redirecionar para uma página de login lidando com o caso &lt;code>NotAuthorized&lt;/code> com navegação:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-zed" data-lang="zed">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>NotAuthorized&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">@&lt;/span>if&lt;span style="color:#6e7681"> &lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>context.User.Identity&lt;span style="color:#ff7b72;font-weight:bold">?&lt;/span>.IsAuthenticated&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">??&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>true)&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>RedirectToLogin&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>}&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>else&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>You&lt;span style="color:#6e7681"> &lt;/span>don&lt;span style="color:#f85149">&amp;#39;&lt;/span>t&lt;span style="color:#6e7681"> &lt;/span>have&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">permission&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>to&lt;span style="color:#6e7681"> &lt;/span>access&lt;span style="color:#6e7681"> &lt;/span>this&lt;span style="color:#6e7681"> &lt;/span>page.&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>}&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>NotAuthorized&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Um componente &lt;code>RedirectToLogin&lt;/code> simples pode ser parecido com:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject NavigationManager &lt;span style="color:#f0883e;font-weight:bold">Navigation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override void OnInitialized()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> returnUrl &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> Uri&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>EscapeDataString(&lt;span style="color:#f0883e;font-weight:bold">Navigation&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Uri);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f0883e;font-weight:bold">Navigation&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NavigateTo(&lt;span style="color:#ff7b72;font-weight:bold">$&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;/account/login?returnUrl={returnUrl}&amp;#34;&lt;/span>, forceLoad: true);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O &lt;code>forceLoad: true&lt;/code> é importante aqui - você deseja uma navegação HTTP real para que o middleware de autenticação do lado do servidor possa lidar com o fluxo de login corretamente.&lt;/p>
&lt;h2 id="autorização-baseada-em-funções-e-políticas">Autorização baseada em funções e políticas&lt;/h2>
&lt;p>As funções são o modelo mais simples: atribua usuários a grupos como “Administrador” ou “Editor” e verifique a associação. Mas as políticas oferecem muito mais flexibilidade.&lt;/p>
&lt;p>Registre políticas em &lt;code>Program.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddAuthorizationCore(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.AddPolicy(&lt;span style="color:#a5d6ff">&amp;#34;CanPublish&amp;#34;&lt;/span>, policy =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> policy.RequireClaim(&lt;span style="color:#a5d6ff">&amp;#34;Permission&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Publish&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.AddPolicy(&lt;span style="color:#a5d6ff">&amp;#34;MinimumAge&amp;#34;&lt;/span>, policy =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> policy.Requirements.Add(&lt;span style="color:#ff7b72">new&lt;/span> MinimumAgeRequirement(&lt;span style="color:#a5d6ff">18&lt;/span>)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.AddPolicy(&lt;span style="color:#a5d6ff">&amp;#34;PremiumUser&amp;#34;&lt;/span>, policy =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> policy.RequireRole(&lt;span style="color:#a5d6ff">&amp;#34;Premium&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .RequireClaim(&lt;span style="color:#a5d6ff">&amp;#34;Subscription&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Active&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Os requisitos personalizados precisam de um manipulador:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">MinimumAgeRequirement&lt;/span> : IAuthorizationRequirement
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> MinimumAge { &lt;span style="color:#ff7b72">get&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> MinimumAgeRequirement(&lt;span style="color:#ff7b72">int&lt;/span> minimumAge) =&amp;gt; MinimumAge = minimumAge;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">MinimumAgeHandler&lt;/span> : AuthorizationHandler&amp;lt;MinimumAgeRequirement&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> Task HandleRequirementAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthorizationHandlerContext context,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MinimumAgeRequirement requirement)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> dateOfBirthClaim = context.User.FindFirst(&lt;span style="color:#a5d6ff">&amp;#34;DateOfBirth&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (dateOfBirthClaim &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Task.CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> dateOfBirth = DateOnly.Parse(dateOfBirthClaim.Value);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> age = DateOnly.FromDateTime(DateTime.Today).Year - dateOfBirth.Year;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (age &amp;gt;= requirement.MinimumAge)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.Succeed(requirement);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Task.CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Registre o manipulador:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddSingleton&amp;lt;IAuthorizationHandler, MinimumAgeHandler&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nos componentes, você também pode verificar a autorização programaticamente quando precisar de lógica dinâmica:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject IAuthorizationService AuthorizationService
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject AuthenticationStateProvider AuthStateProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">bool&lt;/span> canPublish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> authState &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await AuthStateProvider&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetAuthenticationStateAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await AuthorizationService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>AuthorizeAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> authState&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>User, &lt;span style="color:#a5d6ff">&amp;#34;CanPublish&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> canPublish &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> result&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Succeeded;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="provedores-oauth-externos">Provedores OAuth externos&lt;/h2>
&lt;p>O suporte para &amp;ldquo;Fazer login com o Google&amp;rdquo; ou &amp;ldquo;Fazer login com GitHub&amp;rdquo; é simples com o middleware de autenticação ASP.NET. Eles são configurados no lado do servidor, pois o fluxo OAuth requer redirecionamentos HTTP.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddAuthentication()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddGoogle(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientId = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:Google:ClientId&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientSecret = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:Google:ClientSecret&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.Scope.Add(&lt;span style="color:#a5d6ff">&amp;#34;profile&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddMicrosoftAccount(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientId = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:Microsoft:ClientId&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientSecret = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:Microsoft:ClientSecret&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddGitHub(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientId = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:GitHub:ClientId&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientSecret = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:GitHub:ClientSecret&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para GitHub, você precisará do pacote &lt;code>AspNet.Security.OAuth.GitHub&lt;/code> NuGet, pois ele não está incluído nas bibliotecas ASP.NET padrão.&lt;/p>
&lt;p>A IU de login fornece links que acionam o desafio externo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/account/external-login&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h2&amp;gt;Sign in with an external provider&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;form method=&amp;#34;post&amp;#34; action=&amp;#34;/api/auth/external-login&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34; name=&amp;#34;provider&amp;#34; value=&amp;#34;Google&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Sign in with Google
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34; name=&amp;#34;provider&amp;#34; value=&amp;#34;Microsoft&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Sign in with Microsoft
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34; name=&amp;#34;provider&amp;#34; value=&amp;#34;GitHub&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Sign in with GitHub
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O endpoint da API aciona o desafio e lida com o retorno de chamada:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/api/auth/external-login&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">string&lt;/span> provider, HttpContext context) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> properties = &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationProperties
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RedirectUri = &lt;span style="color:#a5d6ff">&amp;#34;/api/auth/external-callback&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Challenge(properties, [provider]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A autenticação externa no Blazor sempre requer uma navegação de página inteira – você não pode concluir um redirecionamento OAuth dentro de um circuito SignalR ou de um aplicativo WebAssembly sem passar pelo servidor.&lt;/p>
&lt;h2 id="autenticação-baseada-em-token-no-blazor-webassemblyo-blazor-webassembly-é-executado-no-cliente-portanto-a-autenticação-baseada-em-cookies-não-se-aplica-da-mesma-maneira-em-vez-disso-normalmente-você-usa-jwts-armazenados-na-memória-e-anexados-a-solicitações-http-de-saída">Autenticação baseada em token no Blazor WebAssemblyO Blazor WebAssembly é executado no cliente, portanto, a autenticação baseada em cookies não se aplica da mesma maneira. Em vez disso, normalmente você usa JWTs armazenados na memória e anexados a solicitações HTTP de saída.&lt;/h2>
&lt;p>A estrutura fornece &lt;code>AuthorizationMessageHandler&lt;/code> para anexar tokens automaticamente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddHttpClient(&lt;span style="color:#a5d6ff">&amp;#34;API&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client =&amp;gt; client.BaseAddress = &lt;span style="color:#ff7b72">new&lt;/span> Uri(&lt;span style="color:#a5d6ff">&amp;#34;https://api.example.com&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddHttpMessageHandler&amp;lt;AuthorizationMessageHandler&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped(sp =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sp.GetRequiredService&amp;lt;IHttpClientFactory&amp;gt;().CreateClient(&lt;span style="color:#a5d6ff">&amp;#34;API&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para aplicativos Blazor WASM autônomos que são autenticados em sua própria API, você implementará um &lt;code>AuthenticationStateProvider&lt;/code> personalizado que analisa o JWT:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">JwtAuthenticationStateProvider&lt;/span> : AuthenticationStateProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ILocalStorageService _localStorage;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> JwtAuthenticationStateProvider(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ILocalStorageService localStorage,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> HttpClient httpClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _localStorage = localStorage;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;AuthenticationState&amp;gt; GetAuthenticationStateAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> token = &lt;span style="color:#ff7b72">await&lt;/span> _localStorage.GetItemAsync&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;authToken&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(token))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity()));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient.DefaultRequestHeaders.Authorization =
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationHeaderValue(&lt;span style="color:#a5d6ff">&amp;#34;Bearer&amp;#34;&lt;/span>, token);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> claims = ParseClaimsFromJwt(token);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> identity = &lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity(claims, &lt;span style="color:#a5d6ff">&amp;#34;jwt&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(identity));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> NotifyAuthStateChanged()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> IEnumerable&amp;lt;Claim&amp;gt; ParseClaimsFromJwt(&lt;span style="color:#ff7b72">string&lt;/span> jwt)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> payload = jwt.Split(&lt;span style="color:#a5d6ff">&amp;#39;.&amp;#39;&lt;/span>)[&lt;span style="color:#a5d6ff">1&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> padded = payload.Length % &lt;span style="color:#a5d6ff">4&lt;/span> &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">2&lt;/span> =&amp;gt; payload + &lt;span style="color:#a5d6ff">&amp;#34;==&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">3&lt;/span> =&amp;gt; payload + &lt;span style="color:#a5d6ff">&amp;#34;=&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; payload
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bytes = Convert.FromBase64String(padded);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> json = JsonSerializer.Deserialize&amp;lt;Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, JsonElement&amp;gt;&amp;gt;(bytes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> json?.Select(kvp =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> Claim(kvp.Key, kvp.Value.ToString()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ?? Enumerable.Empty&amp;lt;Claim&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Após um login bem-sucedido, você armazena o token e notifica o estado de autenticação:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">AuthService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ILocalStorageService _localStorage;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> JwtAuthenticationStateProvider _authStateProvider;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> AuthService(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> HttpClient httpClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ILocalStorageService localStorage,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthenticationStateProvider authStateProvider)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _localStorage = localStorage;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _authStateProvider = (JwtAuthenticationStateProvider)authStateProvider;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt; LoginAsync(&lt;span style="color:#ff7b72">string&lt;/span> email, &lt;span style="color:#ff7b72">string&lt;/span> password)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.PostAsJsonAsync(&lt;span style="color:#a5d6ff">&amp;#34;/api/auth/login&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> { Email = email, Password = password });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!response.IsSuccessStatusCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> response.Content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ReadFromJsonAsync&amp;lt;LoginResponse&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _localStorage.SetItemAsync(&lt;span style="color:#a5d6ff">&amp;#34;authToken&amp;#34;&lt;/span>, result!.Token);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _authStateProvider.NotifyAuthStateChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task LogoutAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _localStorage.RemoveItemAsync(&lt;span style="color:#a5d6ff">&amp;#34;authToken&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _authStateProvider.NotifyAuthStateChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Uma palavra de cautela: armazenar JWTs em &lt;code>localStorage&lt;/code> os expõe a ataques XSS. Para aplicativos de maior segurança, considere manter os tokens apenas na memória e usar tokens de atualização ou adotar o padrão Backend-for-Frontend (BFF), onde o servidor gerencia tokens e o cliente usa cookies somente HTTP.&lt;/p>
&lt;h2 id="blazor-server-vs-webassembly-considerações-de-segurança">Blazor Server vs. WebAssembly: considerações de segurança&lt;/h2>
&lt;p>O modelo de hospedagem muda fundamentalmente sua postura de segurança.&lt;/p>
&lt;p>&lt;strong>Blazor Server&lt;/strong> mantém toda a lógica do seu componente no servidor. O cliente só vê diferenças de HTML renderizadas no SignalR. Isso significa:&lt;/p>
&lt;ul>
&lt;li>A lógica sensível nunca sai do servidor&lt;/li>
&lt;li>Você pode acessar bancos de dados e serviços internos diretamente dos componentes&lt;/li>
&lt;li>O estado de autenticação vem do &lt;code>HttpContext&lt;/code> do servidor na conexão inicial&lt;/li>
&lt;li>O circuito pode sobreviver ao cookie de autenticação — se o cookie de um usuário expirar, o circuito permanecerá ativo até ser desconectado&lt;/li>
&lt;li>Você deve lidar com a desconexão do circuito normalmente e revalidar o estado de autenticação na reconexão&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Blazor WebAssembly&lt;/strong> é executado inteiramente no navegador. Isso significa:&lt;/p>
&lt;ul>
&lt;li>Todo o código do seu componente pode ser baixado e inspecionado&lt;/li>
&lt;li>Nunca coloque segredos, cadeias de conexão ou lógica de negócios confidenciais em componentes WASM&lt;/li>
&lt;li>A autenticação só é aplicada no cliente para UX; a aplicação real deve acontecer na sua camada API&lt;/li>
&lt;li>O gerenciamento de tokens é de sua responsabilidade&lt;/li>
&lt;li>Considere usar o modelo hospedado onde um projeto de servidor lida com autenticação e atende o aplicativo WASM&lt;/li>
&lt;/ul>
&lt;p>Um padrão que recomendo para aplicativos WebAssembly é tratar cada componente como se fosse uma &amp;ldquo;UI não confiável&amp;rdquo; e cada endpoint de API como se estivesse sendo chamado por um cliente desconhecido. Valide tudo do lado do servidor, independentemente do que o cliente verifique.&lt;/p>
&lt;h2 id="construindo-um-authenticationstateprovider-personalizado">Construindo um AuthenticationStateProvider personalizado&lt;/h2>
&lt;p>Às vezes, os provedores integrados não se adaptam à sua arquitetura. Talvez você esteja integrando um sistema de autenticação legado ou precise pesquisar alterações no estado de autenticação. Aqui está um provedor personalizado mais completo para Blazor Server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CustomAuthStateProvider&lt;/span> : AuthenticationStateProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IHttpContextAccessor _httpContextAccessor;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IUserService _userService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CustomAuthStateProvider(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IHttpContextAccessor httpContextAccessor,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IUserService userService)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpContextAccessor = httpContextAccessor;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _userService = userService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;AuthenticationState&amp;gt; GetAuthenticationStateAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> httpContext = _httpContextAccessor.HttpContext;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (httpContext?.User?.Identity?.IsAuthenticated != &lt;span style="color:#79c0ff">true&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity()));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (userId &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity()));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> user = &lt;span style="color:#ff7b72">await&lt;/span> _userService.GetUserWithClaimsAsync(userId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (user &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity()));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> claims = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;Claim&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span>(ClaimTypes.NameIdentifier, user.Id),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span>(ClaimTypes.Name, user.DisplayName),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span>(ClaimTypes.Email, user.Email)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> claims.AddRange(user.Roles.Select(r =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> Claim(ClaimTypes.Role, r)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> claims.AddRange(user.Permissions.Select(p =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> Claim(&lt;span style="color:#a5d6ff">&amp;#34;Permission&amp;#34;&lt;/span>, p)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> identity = &lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity(claims, &lt;span style="color:#a5d6ff">&amp;#34;Custom&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(identity));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> MarkUserAsAuthenticated(&lt;span style="color:#ff7b72">string&lt;/span> userId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> MarkUserAsLoggedOut()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> anonymous = &lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> authState = Task.FromResult(&lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(anonymous));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NotifyAuthenticationStateChanged(authState);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Registre-o em &lt;code>Program.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;AuthenticationStateProvider, CustomAuthStateProvider&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;CustomAuthStateProvider&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O principal insight aqui é &lt;code>NotifyAuthenticationStateChanged&lt;/code> - chamá-lo aciona o parâmetro em cascata para atualização, que reavalia cada &lt;code>AuthorizeView&lt;/code> e &lt;code>AuthorizeRouteView&lt;/code> em sua árvore de componentes. É assim que você faz a UI reagir aos eventos de login/logout sem uma atualização completa da página.&lt;/p>
&lt;h2 id="armadilhas-e-soluções-comuns">Armadilhas e soluções comuns&lt;/h2>
&lt;p>Depois de trabalhar com autenticação do Blazor em muitos projetos, aqui estão os problemas que vejo com mais frequência:&lt;/p>
&lt;h3 id="1-usando-httpcontext-nos-componentes-do-servidor-blazorhttpcontext-está-disponível-durante-a-solicitação-http-inicial-mas-é-null-ou-obsoleto-durante-as-interações-do-signalr-não-injete-ihttpcontextaccessor-em-componentes-executados-após-a-renderização-inicial">1. Usando HttpContext nos componentes do servidor Blazor&lt;code>HttpContext&lt;/code> está disponível durante a solicitação HTTP inicial, mas é &lt;code>null&lt;/code> ou obsoleto durante as interações do SignalR. Não injete &lt;code>IHttpContextAccessor&lt;/code> em componentes executados após a renderização inicial.&lt;/h3>
&lt;p>&lt;strong>Solução:&lt;/strong> capture o que você precisa de &lt;code>HttpContext&lt;/code> durante a inicialização e armazene-o em um serviço com escopo definido:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">UserContext&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> UserId { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> AccessToken { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// In a component that renders during the initial HTTP request:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inject IHttpContextAccessor HttpContextAccessor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inject UserContext UserContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnInitialized()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> context = HttpContextAccessor.HttpContext;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UserContext.UserId = context?.User.FindFirstValue(ClaimTypes.NameIdentifier);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UserContext.AccessToken = context?.Request.Headers.Authorization
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToString().Replace(&lt;span style="color:#a5d6ff">&amp;#34;Bearer &amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-estado-de-autenticação-não-atualizado-após-login">2. Estado de autenticação não atualizado após login&lt;/h3>
&lt;p>Você chama sua API de login, ela é bem-sucedida, mas a IU ainda mostra &amp;ldquo;Log In&amp;rdquo;.&lt;/p>
&lt;p>&lt;strong>Solução:&lt;/strong> Você deve chamar &lt;code>NotifyAuthenticationStateChanged&lt;/code> em seu &lt;code>AuthenticationStateProvider&lt;/code> depois que o estado de autenticação for alterado. A estrutura não detecta magicamente que um token foi armazenado ou um cookie foi definido.&lt;/p>
&lt;h3 id="3-atributo-de-autorização-que-não-funciona-em-componentes">3. Atributo de autorização que não funciona em componentes&lt;/h3>
&lt;p>Você adiciona &lt;code>[Authorize]&lt;/code> a um componente, mas ele não bloqueia usuários não autenticados.&lt;/p>
&lt;p>&lt;strong>Solução:&lt;/strong> Certifique-se de usar &lt;code>AuthorizeRouteView&lt;/code> em vez de &lt;code>RouteView&lt;/code> simples em seu &lt;code>App.razor&lt;/code>. O padrão &lt;code>RouteView&lt;/code> ignora totalmente os atributos de autorização.&lt;/p>
&lt;h3 id="4-a-pré-renderização-quebra-o-estado-de-autenticação">4. A pré-renderização quebra o estado de autenticação&lt;/h3>
&lt;p>Durante a pré-renderização do lado do servidor no Blazor WebAssembly, não há token de autenticação disponível. Os componentes são renderizados como não autenticados e, em seguida, passam para o estado autenticado após o carregamento do WASM.&lt;/p>
&lt;p>&lt;strong>Solução:&lt;/strong> desative a pré-renderização para páginas sensíveis à autenticação com &lt;code>@rendermode InteractiveWebAssembly&lt;/code> (sem pré-renderização) ou administre o estado de carregamento normalmente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;Welcome back, @context.User.Identity?.Name&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorizing&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorizing&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;Sign in&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="5-expiração-de-token-em-circuitos-de-longa-duração">5. Expiração de token em circuitos de longa duração&lt;/h3>
&lt;p>Os circuitos do Blazor Server podem permanecer ativos por horas. Se o seu token ou sessão expirar, o usuário permanecerá &amp;ldquo;autenticado&amp;rdquo; na IU, mas as chamadas de API começarão a falhar.&lt;/p>
&lt;p>&lt;strong>Solução:&lt;/strong> implemente uma verificação periódica ou use um &lt;code>RevalidatingServerAuthenticationStateProvider&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">RevalidatingAuthStateProvider&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : RevalidatingServerAuthenticationStateProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IServiceScopeFactory _scopeFactory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> RevalidatingAuthStateProvider(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ILoggerFactory loggerFactory,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IServiceScopeFactory scopeFactory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : &lt;span style="color:#ff7b72">base&lt;/span>(loggerFactory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _scopeFactory = scopeFactory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> TimeSpan RevalidationInterval =&amp;gt; TimeSpan.FromMinutes(&lt;span style="color:#a5d6ff">30&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt; ValidateAuthenticationStateAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthenticationState authenticationState, CancellationToken cancellationToken)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> scope = _scopeFactory.CreateAsyncScope();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> userManager = scope.ServiceProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GetRequiredService&amp;lt;UserManager&amp;lt;IdentityUser&amp;gt;&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> user = &lt;span style="color:#ff7b72">await&lt;/span> userManager.GetUserAsync(authenticationState.User);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> user &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso valida que o usuário ainda existe (e seu carimbo de segurança não mudou) a cada 30 minutos.&lt;/p>
&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>A autenticação e a autorização no Blazor exigem uma mudança de pensamento em relação ao ASP.NET tradicional de solicitação e resposta. A abstração &lt;code>AuthenticationStateProvider&lt;/code> é a chave para entender como tudo se encaixa - uma vez que você internaliza isso, o resto segue naturalmente.&lt;/p>
&lt;p>Para a maioria dos aplicativos, comece com o ASP.NET Identity e os modelos integrados. Eles lidam com o trabalho pesado de gerenciamento de usuários, hash de senha e geração de token. Adicione políticas e autorização baseada em declarações à medida que seus requisitos aumentam. Adicione provedores OAuth externos quando seus usuários esperarem.&lt;/p>
&lt;p>O modelo de hospedagem é importante: o Blazor Server oferece uma postura de segurança mais tradicional, onde o código permanece no servidor, enquanto o WebAssembly leva você a pensar primeiro na API, onde o cliente não é confiável por design. Nenhum deles é inerentemente mais seguro – eles apenas têm modelos de ameaças diferentes.&lt;/p>
&lt;p>Qualquer que seja a abordagem escolhida, lembre-se da regra de ouro: &lt;strong>a autorização na UI é para a experiência do usuário, a autorização no servidor é para segurança.&lt;/strong> Sempre aplique ambas.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Security</category><category>Web Development</category></item><item><title>JavaScript isolado no Blazor com arquivos JS colocados</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-isolated-js/</link><pubDate>Wed, 18 Jun 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-isolated-js/</guid><description>Use arquivos JavaScript colocados no Blazor para manter a lógica JS com escopo definido para componentes individuais.</description><content:encoded>&lt;p>Se você já trabalhou com Blazor e JS Interop antes, provavelmente acabou com um enorme arquivo &lt;code>app.js&lt;/code> cheio de funções aleatórias para diferentes componentes. Funciona, mas fica confuso rapidamente. Felizmente, existe uma abordagem muito mais limpa: arquivos JavaScript colocados.&lt;/p>
&lt;h1 id="a-ideia">A ideia&lt;/h1>
&lt;p>Assim como o isolamento CSS, o Blazor permite colocar um arquivo &lt;code>.razor.js&lt;/code> próximo ao seu componente. O módulo JavaScript é carregado sob demanda, somente quando o componente realmente precisa dele. Sem scripts globais, sem poluição.&lt;/p>
&lt;p>#Configurando&lt;/p>
&lt;p>Digamos que temos um componente &lt;code>Clipboard.razor&lt;/code> que copia texto para a área de transferência. Crie um arquivo chamado &lt;code>Clipboard.razor.js&lt;/code> ao lado dele:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">export&lt;/span> &lt;span style="color:#ff7b72">function&lt;/span> copyToClipboard(text) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> navigator.clipboard.writeText(text).then(() =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console.log(&lt;span style="color:#a5d6ff">&amp;#34;Copied to clipboard!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Observe a palavra-chave &lt;code>export&lt;/code> - isso é importante. Blazor carrega-o como um módulo ES padrão.&lt;/p>
&lt;h1 id="carregando-o-módulo-no-blazor">Carregando o módulo no Blazor&lt;/h1>
&lt;p>No seu componente, você usa &lt;code>IJSRuntime&lt;/code> para importar o módulo. O caminho segue uma convenção: &lt;code>./_content/{ASSEMBLY_NAME}/{COMPONENT_PATH}.razor.js&lt;/code> para bibliotecas, ou apenas o caminho relativo para o projeto atual.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@inject IJSRuntime JS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@implements IAsyncDisposable
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button @onclick=&lt;span style="color:#a5d6ff">&amp;#34;Copy&amp;#34;&lt;/span>&amp;gt;Copy to clipboard&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Text { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> IJSObjectReference? module;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnAfterRenderAsync(&lt;span style="color:#ff7b72">bool&lt;/span> firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> module = &lt;span style="color:#ff7b72">await&lt;/span> JS.InvokeAsync&amp;lt;IJSObjectReference&amp;gt;(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;import&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;./Components/Clipboard.razor.js&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task Copy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (module &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> module.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;copyToClipboard&amp;#34;&lt;/span>, Text);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> ValueTask DisposeAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (module &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> module.DisposeAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Algumas coisas a serem observadas aqui:&lt;/p>
&lt;ul>
&lt;li>Carregamos o módulo em &lt;code>OnAfterRenderAsync&lt;/code> porque JS Interop não está disponível durante a pré-renderização do lado do servidor&lt;/li>
&lt;li>Mantemos uma referência ao módulo com &lt;code>IJSObjectReference&lt;/code>&lt;/li>
&lt;li>Implementamos &lt;code>IAsyncDisposable&lt;/code> para limpar o módulo quando o componente é destruído&lt;/li>
&lt;/ul>
&lt;h1 id="por-que-isso-é-melhor">Por que isso é melhor&lt;/h1>
&lt;p>Antes, eu costumava despejar tudo em um único &lt;code>wwwroot/js/app.js&lt;/code>. Funcionou, mas encontrar funções era uma tarefa difícil, e cada página carregava JavaScript desnecessário. Com arquivos JS colocados:&lt;/p>
&lt;ul>
&lt;li>Cada componente possui seu próprio JavaScript&lt;/li>
&lt;li>Os módulos são carregados lentamente, somente quando necessário&lt;/li>
&lt;li>Sem conflitos de nomes de funções globais&lt;/li>
&lt;li>Mais fácil de manter e excluir — ao excluir o componente, você exclui o JS com ele&lt;/li>
&lt;/ul>
&lt;h1 id="uma-pegadinha-com-o-caminho">Uma pegadinha com o caminho&lt;/h1>
&lt;p>O caminho que você passa para &lt;code>import&lt;/code> depende se você está em um aplicativo Blazor independente ou em uma biblioteca de classes Razor. Para um aplicativo Blazor normal, o caminho é relativo a &lt;code>wwwroot&lt;/code>. O arquivo &lt;code>.razor.js&lt;/code> é copiado lá no momento da construção, então você faz referência a ele a partir do local do componente no projeto.&lt;/p>
&lt;p>Se você receber um 404 ao carregar o módulo, verifique novamente o caminho e certifique-se de que o arquivo termine em &lt;code>.razor.js&lt;/code>, não apenas em &lt;code>.js&lt;/code>.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem! Sinta-se à vontade para entrar em contato comigo em qualquer mídia social em &lt;strong>@emimontesdeoca&lt;/strong>.&lt;/p>
&lt;h1 id="recursos">Recursos&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/collocated-js">ASP.NET Core Blazor JavaScript com JS colocado&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>JavaScript</category></item><item><title>Blazor Interatividade em .NET 9 e .NET 10: um guia completo</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-interactivity-dotnet-9-10/</link><pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-interactivity-dotnet-9-10/</guid><description>Um mergulho profundo nos modos de renderização do Blazor, streaming de SSR, navegação aprimorada e os novos recursos de interatividade no .NET 9 e .NET 10.</description><content:encoded>&lt;p>Se você tem criado aplicativos da web com o Blazor nos últimos anos, sabe que a estrutura já percorreu um caminho &lt;em>longo&lt;/em>. O que começou como uma escolha entre Blazor Server e Blazor WebAssembly evoluiu para um modelo de renderização unificado e flexível que permite escolher a estratégia de interatividade certa para cada componente do seu aplicativo.&lt;/p>
&lt;p>Com o .NET 8, obtivemos uma mudança fundamental: a introdução de modos de renderização e renderização estática no lado do servidor (SSR) como padrão. Agora, o .NET 9 e o .NET 10 baseiam-se nessa base com refinamentos que tornam a experiência do desenvolvedor mais suave e a experiência do usuário final mais rápida.&lt;/p>
&lt;p>Nesta postagem, quero mostrar o quadro completo da interatividade do Blazor como está hoje: como funcionam os modos de renderização, o que o SSR de streaming traz para a mesa, como a navegação aprimorada e o manuseio de formulários mudam o jogo e o que há de novo nos lançamentos mais recentes. Se você está planejando um novo projeto do Blazor ou pensando em atualizar, este é o guia que eu gostaria de ter quando comecei a me aprofundar em tudo isso.&lt;/p>
&lt;h2 id="uma-rápida-retrospectiva-como-chegamos-aqui">Uma rápida retrospectiva: como chegamos aqui&lt;/h2>
&lt;p>Antes do .NET 8, você precisava se comprometer com um modelo de hospedagem no nível do projeto. Blazor Server significava que tudo rodava no servidor por meio de uma conexão SignalR. Blazor WebAssembly significava que tudo rodava no navegador. Cada um tinha vantagens e desvantagens e misturá-las era doloroso.&lt;/p>
&lt;p>O .NET 8 mudou o jogo ao introduzir um único modelo de projeto — o Blazor Web App — que unifica os dois modelos. O conceito principal é &lt;strong>modos de renderização&lt;/strong>: você decide &lt;em>por componente&lt;/em> como ele deve ser renderizado e onde a interatividade acontece. O padrão tornou-se SSR estático, o que significa que os componentes são renderizados no servidor e enviam HTML simples para o navegador – sem SignalR, sem WebAssembly, apenas HTML rápido.&lt;/p>
&lt;p>O .NET 9 aperfeiçoou esses conceitos, melhorou a experiência do desenvolvedor e otimizou o desempenho. O .NET 10 vai além com melhor manipulação de reconexão, estado persistente do componente nos modos de renderização e melhorias na forma como o próprio script Blazor é entregue.&lt;/p>
&lt;p>Vamos analisar tudo.&lt;/p>
&lt;h2 id="modos-de-renderização-no-net-9">Modos de renderização no .NET 9&lt;/h2>
&lt;p>No coração do Blazor moderno está o conceito de modos de renderização. Existem quatro modos que você deve conhecer:&lt;/p>
&lt;h3 id="1-ssr-estático-o-padrão">1. SSR estático (o padrão)&lt;/h3>
&lt;p>Quando você cria um novo Blazor Web App, os componentes são renderizados estaticamente no servidor por padrão. O servidor processa o componente Razor, gera HTML e o envia ao navegador. Não há conexão persistente, nem tempo de execução do WebAssembly – apenas solicitação/resposta tradicional.&lt;/p>
&lt;p>Isso é perfeito para páginas com muito conteúdo, painéis que exibem principalmente dados ou qualquer página onde você não precise de interação em tempo real.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Our Products&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> product &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> products)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;product-card&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Name&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Description&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>span &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;price&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Product&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> products &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> products &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ProductService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetAllAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esse componente é renderizado no servidor, produz HTML e pronto. Nenhuma conexão contínua. Carregamento inicial rápido, ótimo para SEO, uso mínimo de recursos do servidor.&lt;/p>
&lt;h3 id="2-servidor-interativoquando-você-precisar-de-interatividade-em-tempo-real--manipulação-de-cliques-em-botões-processamento-de-entrada-do-usuário-atualização-dinâmica-da-ui--você-pode-optar-pelo-modo-servidor-interativo-isso-estabelece-uma-conexão-signalr-entre-o-navegador-e-o-servidor-e-as-atualizações-da-iu-acontecem-por-meio-dessa-conexão">2. Servidor InterativoQuando você precisar de interatividade em tempo real – manipulação de cliques em botões, processamento de entrada do usuário, atualização dinâmica da UI – você pode optar pelo modo Servidor Interativo. Isso estabelece uma conexão SignalR entre o navegador e o servidor, e as atualizações da IU acontecem por meio dessa conexão.&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/counter&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@rendermode InteractiveServer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Counter&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;Current count: @currentCount&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button class=&amp;#34;btn btn-primary&amp;#34; @onclick=&amp;#34;IncrementCount&amp;#34;&amp;gt;Click me&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private int currentCount = 0;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void IncrementCount()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentCount++;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A diretiva &lt;code>@rendermode InteractiveServer&lt;/code> é suficiente. O componente é renderizado inicialmente no servidor e, em seguida, uma conexão SignalR é estabelecida para lidar com interações subsequentes. Seus manipuladores de eventos C# são executados no servidor e as diferenças da UI são enviadas para o navegador.&lt;/p>
&lt;p>&lt;strong>Quando usá-lo:&lt;/strong> Quando você precisa de interatividade, seus componentes precisam de acesso a recursos do lado do servidor (bancos de dados, APIs, sistemas de arquivos) e você deseja um carregamento inicial rápido, sem esperar o download do WebAssembly.&lt;/p>
&lt;h3 id="3-webassembly-interativo">3. WebAssembly interativo&lt;/h3>
&lt;p>Se você deseja interatividade sem manter uma conexão com o servidor, o Interactive WebAssembly executa a lógica do componente diretamente no navegador usando o tempo de execução do .NET WebAssembly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/search&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>rendermode InteractiveWebAssembly
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Product Search&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>input &lt;span style="color:#f85149">@&lt;/span>bind&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;searchTerm&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>bind:event&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;oninput&amp;#34;&lt;/span> placeholder&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Search products...&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (filteredProducts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ul&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> product &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> filteredProducts)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>li&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Name &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>li&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>ul&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private string searchTerm &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> string&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Product&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> allProducts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private IEnumerable&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Product&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> filteredProducts &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> allProducts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Where(p &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> p&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Name&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Contains(searchTerm, StringComparison&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>OrdinalIgnoreCase));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> allProducts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await Http&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetFromJsonAsync&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Product&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;gt;&lt;/span>(&lt;span style="color:#a5d6ff">&amp;#34;api/products&amp;#34;&lt;/span>) &lt;span style="color:#f85149">??&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A desvantagem: há um custo inicial de download para o tempo de execução do .NET e seus assemblies. Mas, uma vez carregado, o componente é executado inteiramente no navegador, sem viagens de ida e volta do servidor para interações da interface do usuário.&lt;/p>
&lt;p>&lt;strong>Quando usar:&lt;/strong> Para componentes altamente interativos onde a latência é importante (pense: editores de rich text, ferramentas de desenho, filtragem em tempo real), quando você deseja reduzir a carga do servidor ou quando está criando um aplicativo Web progressivo (PWA).&lt;/p>
&lt;h3 id="4-automático-interativo">4. Automático Interativo&lt;/h3>
&lt;p>Este é o meio-termo pragmático e um dos meus recursos favoritos. O Interactive Auto começa com a renderização do lado do servidor via SignalR para o primeiro carregamento e, em seguida, baixa silenciosamente o tempo de execução do WebAssembly em segundo plano. Nas visitas subsequentes, o componente é executado no WebAssembly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/dashboard&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>rendermode InteractiveAuto
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Dashboard&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>DashboardWidget Title&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Sales&amp;#34;&lt;/span> Value&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@salesTotal&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>DashboardWidget Title&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Users&amp;#34;&lt;/span> Value&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@activeUsers&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;RefreshData&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Refresh&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private decimal salesTotal;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">int&lt;/span> activeUsers;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> await RefreshData();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task RefreshData()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> data &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await DashboardService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetSummaryAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> salesTotal &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> data&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>SalesTotal;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> activeUsers &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> data&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ActiveUsers;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso oferece o melhor dos dois mundos: primeira renderização rápida (sem espera pelo download do WASM) e, eventualmente, o componente é executado no lado do cliente. O usuário não percebe a transição.&lt;/p>
&lt;p>&lt;strong>Quando usar:&lt;/strong> Quando você deseja carregamentos iniciais rápidos e eventual execução no lado do cliente. É uma ótima opção padrão para muitos componentes interativos.&lt;/p>
&lt;h2 id="streaming-ssr-o-melhor-dos-dois-mundos">Streaming SSR: o melhor dos dois mundos&lt;/h2>
&lt;p>Streaming SSR é um daqueles recursos que parece simples, mas faz uma enorme diferença no desempenho percebido. A ideia é a seguinte: em vez de esperar que todos os seus dados sejam carregados antes de enviar qualquer HTML, o servidor envia o shell da página imediatamente e então &lt;em>transmite&lt;/em> atualizações de conteúdo à medida que os dados ficam disponíveis.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/reports&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>attribute [StreamRendering]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Monthly Reports&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (reports is null)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;loading-spinner&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Loading reports&lt;span style="color:#ff7b72;font-weight:bold">...&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>table &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;table&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Month&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Revenue&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Growth&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> report &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> reports)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>report&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Month&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>report&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Revenue&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@(report.Growth &amp;gt;= 0 ? &amp;#34;&lt;/span>text&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>success&lt;span style="color:#a5d6ff">&amp;#34; : &amp;#34;&lt;/span>text&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>danger&lt;span style="color:#a5d6ff">&amp;#34;)&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>report&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Growth&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;P1&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>table&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>MonthlyReport&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">?&lt;/span> reports;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">//&lt;/span> This might take a couple of seconds
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reports &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ReportService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetMonthlyReportsAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Com o atributo &lt;code>[StreamRendering]&lt;/code>, eis o que acontece:&lt;/p>
&lt;ol>
&lt;li>O servidor envia imediatamente o HTML com o botão giratório de carregamento.&lt;/li>
&lt;li>&lt;code>OnInitializedAsync&lt;/code> executa e busca os dados.&lt;/li>
&lt;li>Quando os dados chegam, o servidor transmite o HTML atualizado (a tabela) para o navegador.&lt;/li>
&lt;li>O navegador corrige o DOM sem recarregar a página inteira.&lt;/li>
&lt;/ol>
&lt;p>O usuário vê a página &lt;em>instantaneamente&lt;/em> com um indicador de carregamento e, em seguida, o conteúdo é preenchido. Não é necessária nenhuma estrutura JavaScript. Nenhuma conexão WebSocket. Apenas um uso inteligente de streaming HTTP.&lt;strong>Quando usar:&lt;/strong> Qualquer página SSR estática que busca dados durante a renderização. Páginas de listagem de produtos, painéis, relatórios – em qualquer lugar onde o carregamento inicial de dados possa levar mais do que algumas centenas de milissegundos.&lt;/p>
&lt;p>&lt;strong>Quando NÃO usar:&lt;/strong> Se os dados forem carregados extremamente rápido (menos de 100 ms), a sobrecarga de streaming não vale a pena. Além disso, o streaming SSR não oferece interatividade contínua – para isso, você ainda precisa de um modo de renderização interativo.&lt;/p>
&lt;h2 id="navegação-aprimorada-e-tratamento-de-formulários">Navegação aprimorada e tratamento de formulários&lt;/h2>
&lt;p>Uma das melhorias sutis, mas poderosas, no Blazor moderno é a navegação aprimorada. Por padrão, o Blazor intercepta cliques em links internos e envios de formulários, buscando o conteúdo da nova página via &lt;code>fetch&lt;/code> e corrigindo o DOM em vez de fazer uma navegação completa no navegador.&lt;/p>
&lt;p>Isso significa que navegar entre páginas SSR estáticas parece um SPA – sem flash de página inteira, a posição de rolagem pode ser preservada e a experiência é suave como a seda.&lt;/p>
&lt;h3 id="como-funciona">Como funciona&lt;/h3>
&lt;p>Quando o script Blazor (&lt;code>blazor.web.js&lt;/code>) é carregado, ele intercepta automaticamente cliques em links internos. Em vez de uma navegação de navegador tradicional, ele:&lt;/p>
&lt;ol>
&lt;li>Faz uma solicitação &lt;code>fetch&lt;/code> para o URL de destino.&lt;/li>
&lt;li>Recebe a resposta HTML.&lt;/li>
&lt;li>Mescla o novo conteúdo no DOM existente.&lt;/li>
&lt;li>Atualiza o URL e o histórico do navegador.&lt;/li>
&lt;/ol>
&lt;p>Você não precisa fazer nada para ativar isso – ele está ativado por padrão. Mas você pode controlá-lo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&amp;lt;!-- Disable enhanced navigation for a specific link --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">a&lt;/span> href&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;/legacy-page&amp;#34;&lt;/span> data-enhance-nav&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;false&amp;#34;&lt;/span>&amp;gt;Legacy Page&amp;lt;/&lt;span style="color:#7ee787">a&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&amp;lt;!-- Force a full page reload for external-like behavior --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">a&lt;/span> href&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;/downloads/report.pdf&amp;#34;&lt;/span> data-enhance-nav&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;false&amp;#34;&lt;/span>&amp;gt;Download Report&amp;lt;/&lt;span style="color:#7ee787">a&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="manipulação-aprimorada-de-formulários">Manipulação aprimorada de formulários&lt;/h3>
&lt;p>Os formulários recebem o mesmo tratamento. Quando você usa &lt;code>EditForm&lt;/code> ou o elemento padrão &lt;code>&amp;lt;form&amp;gt;&lt;/code> com o tratamento de formulário do Blazor, os envios são interceptados e tratados via &lt;code>fetch&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/contact&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;EditForm Model=&amp;#34;contactModel&amp;#34; OnValidSubmit=&amp;#34;HandleSubmit&amp;#34; FormName=&amp;#34;contact&amp;#34; Enhance&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;DataAnnotationsValidator /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&amp;#34;mb-3&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label for=&amp;#34;name&amp;#34;&amp;gt;Name&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputText id=&amp;#34;name&amp;#34; @bind-Value=&amp;#34;contactModel.Name&amp;#34; class=&amp;#34;form-control&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&amp;#34;() =&amp;gt; contactModel.Name&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&amp;#34;mb-3&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label for=&amp;#34;email&amp;#34;&amp;gt;Email&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputText id=&amp;#34;email&amp;#34; @bind-Value=&amp;#34;contactModel.Email&amp;#34; class=&amp;#34;form-control&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&amp;#34;() =&amp;gt; contactModel.Email&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&amp;#34;mb-3&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label for=&amp;#34;message&amp;#34;&amp;gt;Message&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputTextArea id=&amp;#34;message&amp;#34; @bind-Value=&amp;#34;contactModel.Message&amp;#34; class=&amp;#34;form-control&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&amp;#34;() =&amp;gt; contactModel.Message&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34; class=&amp;#34;btn btn-primary&amp;#34;&amp;gt;Send&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/EditForm&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [SupplyParameterFromForm]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private ContactModel contactModel { get; set; } = new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task HandleSubmit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> await ContactService.SubmitAsync(contactModel);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> contactModel = new ContactModel();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O atributo &lt;code>Enhance&lt;/code> em &lt;code>EditForm&lt;/code> diz ao Blazor para interceptar o envio do formulário. O formulário é enviado para o servidor, o servidor o processa e renderiza novamente a página, e o HTML atualizado é transmitido de volta – tudo sem uma navegação de página inteira. Parece interativo, mas é totalmente renderizado pelo servidor.&lt;/p>
&lt;p>Observe o atributo &lt;code>[SupplyParameterFromForm]&lt;/code> - é assim que o Blazor vincula os dados do formulário postado ao seu modelo em cenários de SSR estáticos. É a ponte entre as postagens de formulário tradicionais e o modelo de componentes do Blazor.&lt;/p>
&lt;h2 id="interatividade-por-página-e-por-componente">Interatividade por página e por componente&lt;/h2>
&lt;p>Um dos aspectos mais poderosos do novo modelo é que você pode misturar modos de renderização em um único aplicativo. Sua página de listagem de produtos pode ser SSR estático, seu carrinho de compras pode ser Interactive Server e seu configurador de produto pode ser Interactive WebAssembly - tudo no mesmo aplicativo.&lt;/p>
&lt;h3 id="configurando-modos-de-renderização-no-nível-do-componente">Configurando modos de renderização no nível do componente&lt;/h3>
&lt;p>Você pode definir o modo de renderização diretamente em um componente usando a diretiva &lt;code>@rendermode&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@* This component is interactive via Server *@
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@rendermode InteractiveServer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h3&amp;gt;Live Chat&amp;lt;/h3&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;!-- chat UI here --&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ou você pode configurá-lo ao usar um componente de um pai:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/product/{Id:int}&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;@product?.Name&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;@product?.Description&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;!-- This child component gets its own interactive render mode --&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;ProductConfigurator Product=&amp;#34;product&amp;#34; @rendermode=&amp;#34;InteractiveWebAssembly&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;!-- This stays static --&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;ProductReviews ProductId=&amp;#34;Id&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public int Id { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private Product? product;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> product = await ProductService.GetByIdAsync(Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Neste exemplo, a página em si é SSR estática. O componente &lt;code>ProductConfigurator&lt;/code> é executado no WebAssembly para uma rica interatividade do lado do cliente. O componente &lt;code>ProductReviews&lt;/code> permanece estático porque está apenas exibindo dados.&lt;/p>
&lt;h3 id="configurando-modos-de-renderização-globalmente">Configurando modos de renderização globalmente&lt;/h3>
&lt;p>Se quiser que todas as páginas sejam interativas por padrão, você pode definir o modo de renderização no nível raiz em &lt;code>App.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Routes&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">@&lt;/span>rendermode&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;InteractiveServer&amp;#34;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">```&lt;/span>Isso&lt;span style="color:#6e7681"> &lt;/span>torna&lt;span style="color:#6e7681"> &lt;/span>cada&lt;span style="color:#6e7681"> &lt;/span>página&lt;span style="color:#6e7681"> &lt;/span>interativa&lt;span style="color:#6e7681"> &lt;/span>por&lt;span style="color:#6e7681"> &lt;/span>meio&lt;span style="color:#6e7681"> &lt;/span>da&lt;span style="color:#6e7681"> &lt;/span>renderização&lt;span style="color:#6e7681"> &lt;/span>do&lt;span style="color:#6e7681"> &lt;/span>servidor.&lt;span style="color:#6e7681"> &lt;/span>Componentes&lt;span style="color:#6e7681"> &lt;/span>individuais&lt;span style="color:#6e7681"> &lt;/span>ainda&lt;span style="color:#6e7681"> &lt;/span>podem&lt;span style="color:#6e7681"> &lt;/span>substituir&lt;span style="color:#6e7681"> &lt;/span>isso,&lt;span style="color:#6e7681"> &lt;/span>se&lt;span style="color:#6e7681"> &lt;/span>necessário.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">### Regras importantes a serem lembradas
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>Existem&lt;span style="color:#6e7681"> &lt;/span>algumas&lt;span style="color:#6e7681"> &lt;/span>restrições&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>serem&lt;span style="color:#6e7681"> &lt;/span>lembradas&lt;span style="color:#6e7681"> &lt;/span>ao&lt;span style="color:#6e7681"> &lt;/span>misturar&lt;span style="color:#6e7681"> &lt;/span>modos&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>renderização:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>Um&lt;span style="color:#6e7681"> &lt;/span>componente&lt;span style="color:#6e7681"> &lt;/span>filho&lt;span style="color:#6e7681"> &lt;/span>não&lt;span style="color:#6e7681"> &lt;/span>pode&lt;span style="color:#6e7681"> &lt;/span>ter&lt;span style="color:#6e7681"> &lt;/span>um&lt;span style="color:#6e7681"> &lt;/span>modo&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>renderização&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;mais interativo&amp;#34;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>que&lt;span style="color:#6e7681"> &lt;/span>seu&lt;span style="color:#6e7681"> &lt;/span>pai.&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Se&lt;span style="color:#6e7681"> &lt;/span>um&lt;span style="color:#6e7681"> &lt;/span>componente&lt;span style="color:#6e7681"> &lt;/span>pai&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">for&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>estático,&lt;span style="color:#6e7681"> &lt;/span>os&lt;span style="color:#6e7681"> &lt;/span>filhos&lt;span style="color:#6e7681"> &lt;/span>poderão&lt;span style="color:#6e7681"> &lt;/span>ser&lt;span style="color:#6e7681"> &lt;/span>estáticos&lt;span style="color:#6e7681"> &lt;/span>ou&lt;span style="color:#6e7681"> &lt;/span>interativos.&lt;span style="color:#6e7681"> &lt;/span>Mas&lt;span style="color:#6e7681"> &lt;/span>se&lt;span style="color:#6e7681"> &lt;/span>um&lt;span style="color:#6e7681"> &lt;/span>pai&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">for&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Interactive&lt;span style="color:#6e7681"> &lt;/span>Server,&lt;span style="color:#6e7681"> &lt;/span>um&lt;span style="color:#6e7681"> &lt;/span>filho&lt;span style="color:#6e7681"> &lt;/span>não&lt;span style="color:#6e7681"> &lt;/span>poderá&lt;span style="color:#6e7681"> &lt;/span>ser&lt;span style="color:#6e7681"> &lt;/span>Interactive&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#d2a8ff;font-weight:bold">WebAssembly&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>(precisaria&lt;span style="color:#6e7681"> &lt;/span>ser&lt;span style="color:#6e7681"> &lt;/span>Server&lt;span style="color:#6e7681"> &lt;/span>ou&lt;span style="color:#6e7681"> &lt;/span>Auto).&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>Componentes&lt;span style="color:#6e7681"> &lt;/span>interativos&lt;span style="color:#6e7681"> &lt;/span>não&lt;span style="color:#6e7681"> &lt;/span>podem&lt;span style="color:#6e7681"> &lt;/span>usar&lt;span style="color:#6e7681"> &lt;/span>diretamente&lt;span style="color:#6e7681"> &lt;/span>serviços&lt;span style="color:#6e7681"> &lt;/span>com&lt;span style="color:#6e7681"> &lt;/span>escopo&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>pais&lt;span style="color:#6e7681"> &lt;/span>estáticos.&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Se&lt;span style="color:#6e7681"> &lt;/span>um&lt;span style="color:#6e7681"> &lt;/span>componente&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">for&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>executado&lt;span style="color:#6e7681"> &lt;/span>no&lt;span style="color:#6e7681"> &lt;/span>WebAssembly,&lt;span style="color:#6e7681"> &lt;/span>ele&lt;span style="color:#6e7681"> &lt;/span>não&lt;span style="color:#6e7681"> &lt;/span>poderá&lt;span style="color:#6e7681"> &lt;/span>acessar&lt;span style="color:#6e7681"> &lt;/span>instâncias&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>DbContext&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>do&lt;span style="color:#6e7681"> &lt;/span>lado&lt;span style="color:#6e7681"> &lt;/span>do&lt;span style="color:#6e7681"> &lt;/span>servidor&lt;span style="color:#6e7681"> &lt;/span>diretamente&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">—&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>você&lt;span style="color:#6e7681"> &lt;/span>precisará&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>uma&lt;span style="color:#6e7681"> &lt;/span>camada&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>API.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>O&lt;span style="color:#6e7681"> &lt;/span>estado&lt;span style="color:#6e7681"> &lt;/span>não&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">é&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>transferido&lt;span style="color:#6e7681"> &lt;/span>automaticamente&lt;span style="color:#6e7681"> &lt;/span>entre&lt;span style="color:#6e7681"> &lt;/span>os&lt;span style="color:#6e7681"> &lt;/span>modos&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>renderização.&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Se&lt;span style="color:#6e7681"> &lt;/span>um&lt;span style="color:#6e7681"> &lt;/span>componente&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">for&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>pré&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>renderizado&lt;span style="color:#6e7681"> &lt;/span>no&lt;span style="color:#6e7681"> &lt;/span>servidor&lt;span style="color:#6e7681"> &lt;/span>e&lt;span style="color:#6e7681"> &lt;/span>depois&lt;span style="color:#6e7681"> &lt;/span>alternar&lt;span style="color:#6e7681"> &lt;/span>para&lt;span style="color:#6e7681"> &lt;/span>o&lt;span style="color:#6e7681"> &lt;/span>WebAssembly,&lt;span style="color:#6e7681"> &lt;/span>você&lt;span style="color:#6e7681"> &lt;/span>precisará&lt;span style="color:#6e7681"> &lt;/span>lidar&lt;span style="color:#6e7681"> &lt;/span>explicitamente&lt;span style="color:#6e7681"> &lt;/span>com&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>persistência&lt;span style="color:#6e7681"> &lt;/span>do&lt;span style="color:#6e7681"> &lt;/span>estado&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">—&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>mais&lt;span style="color:#6e7681"> &lt;/span>sobre&lt;span style="color:#6e7681"> &lt;/span>isso&lt;span style="color:#6e7681"> &lt;/span>na&lt;span style="color:#6e7681"> &lt;/span>seção&lt;span style="color:#6e7681"> &lt;/span>.NET&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">10&lt;/span>.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">## O que há de novo no .NET 10
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>O&lt;span style="color:#6e7681"> &lt;/span>.NET&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">10&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>continua&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>abordagem&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>melhoria&lt;span style="color:#6e7681"> &lt;/span>incremental,&lt;span style="color:#6e7681"> &lt;/span>com&lt;span style="color:#6e7681"> &lt;/span>foco&lt;span style="color:#6e7681"> &lt;/span>na&lt;span style="color:#6e7681"> &lt;/span>confiabilidade&lt;span style="color:#6e7681"> &lt;/span>e&lt;span style="color:#6e7681"> &lt;/span>na&lt;span style="color:#6e7681"> &lt;/span>experiência&lt;span style="color:#6e7681"> &lt;/span>do&lt;span style="color:#6e7681"> &lt;/span>desenvolvedor.&lt;span style="color:#6e7681"> &lt;/span>Aqui&lt;span style="color:#6e7681"> &lt;/span>estão&lt;span style="color:#6e7681"> &lt;/span>os&lt;span style="color:#6e7681"> &lt;/span>destaques&lt;span style="color:#6e7681"> &lt;/span>da&lt;span style="color:#6e7681"> &lt;/span>interatividade&lt;span style="color:#6e7681"> &lt;/span>do&lt;span style="color:#6e7681"> &lt;/span>Blazor:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">### Experiência de reconexão aprimorada
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>Se&lt;span style="color:#6e7681"> &lt;/span>você&lt;span style="color:#6e7681"> &lt;/span>usou&lt;span style="color:#6e7681"> &lt;/span>o&lt;span style="color:#6e7681"> &lt;/span>modo&lt;span style="color:#6e7681"> &lt;/span>Servidor&lt;span style="color:#6e7681"> &lt;/span>Interativo&lt;span style="color:#6e7681"> &lt;/span>na&lt;span style="color:#6e7681"> &lt;/span>produção,&lt;span style="color:#6e7681"> &lt;/span>provavelmente&lt;span style="color:#6e7681"> &lt;/span>encontrou&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>sobreposição&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>reconexão&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">—&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>aquele&lt;span style="color:#6e7681"> &lt;/span>momento&lt;span style="color:#6e7681"> &lt;/span>em&lt;span style="color:#6e7681"> &lt;/span>que&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>conexão&lt;span style="color:#6e7681"> &lt;/span>do&lt;span style="color:#6e7681"> &lt;/span>SignalR&lt;span style="color:#6e7681"> &lt;/span>cai&lt;span style="color:#6e7681"> &lt;/span>e&lt;span style="color:#6e7681"> &lt;/span>o&lt;span style="color:#6e7681"> &lt;/span>usuário&lt;span style="color:#6e7681"> &lt;/span>vê&lt;span style="color:#6e7681"> &lt;/span>uma&lt;span style="color:#6e7681"> &lt;/span>mensagem&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Reconectando...&amp;#34;&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>No&lt;span style="color:#6e7681"> &lt;/span>.NET&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">10&lt;/span>,&lt;span style="color:#6e7681"> &lt;/span>essa&lt;span style="color:#6e7681"> &lt;/span>experiência&lt;span style="color:#6e7681"> &lt;/span>fica&lt;span style="color:#6e7681"> &lt;/span>significativamente&lt;span style="color:#6e7681"> &lt;/span>melhor.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>A&lt;span style="color:#6e7681"> &lt;/span>lógica&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>reconexão&lt;span style="color:#6e7681"> &lt;/span>agora&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">é&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>mais&lt;span style="color:#6e7681"> &lt;/span>inteligente&lt;span style="color:#6e7681"> &lt;/span>para&lt;span style="color:#6e7681"> &lt;/span>tentar&lt;span style="color:#6e7681"> &lt;/span>novamente.&lt;span style="color:#6e7681"> &lt;/span>Em&lt;span style="color:#6e7681"> &lt;/span>vez&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>um&lt;span style="color:#6e7681"> &lt;/span>intervalo&lt;span style="color:#6e7681"> &lt;/span>fixo&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>novas&lt;span style="color:#6e7681"> &lt;/span>tentativas,&lt;span style="color:#6e7681"> &lt;/span>ele&lt;span style="color:#6e7681"> &lt;/span>usa&lt;span style="color:#6e7681"> &lt;/span>uma&lt;span style="color:#6e7681"> &lt;/span>estratégia&lt;span style="color:#6e7681"> &lt;/span>de&lt;span style="color:#6e7681"> &lt;/span>espera&lt;span style="color:#6e7681"> &lt;/span>que&lt;span style="color:#6e7681"> &lt;/span>se&lt;span style="color:#6e7681"> &lt;/span>adapta&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">à&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>situação.&lt;span style="color:#6e7681"> &lt;/span>A&lt;span style="color:#6e7681"> &lt;/span>IU&lt;span style="color:#6e7681"> &lt;/span>durante&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>reconexão&lt;span style="color:#6e7681"> &lt;/span>também&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">é&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>mais&lt;span style="color:#6e7681"> &lt;/span>refinada&lt;span style="color:#6e7681"> &lt;/span>e&lt;span style="color:#6e7681"> &lt;/span>você&lt;span style="color:#6e7681"> &lt;/span>tem&lt;span style="color:#6e7681"> &lt;/span>ganchos&lt;span style="color:#6e7681"> &lt;/span>melhores&lt;span style="color:#6e7681"> &lt;/span>para&lt;span style="color:#6e7681"> &lt;/span>personalizar&lt;span style="color:#6e7681"> &lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>experiência:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">```&lt;/span>razor&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!&lt;/span>&lt;span style="color:#8b949e;font-style:italic">-- In your App.razor or layout --&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>id&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;components-reconnect-modal&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;reconnect-visible&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Connection&lt;span style="color:#6e7681"> &lt;/span>lost.&lt;span style="color:#6e7681"> &lt;/span>Attempting&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">to&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>reconnect...&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;spinner&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;reconnect-failed&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Could&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">not&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>reconnect&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">to&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>the&lt;span style="color:#6e7681"> &lt;/span>server.&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button&lt;span style="color:#6e7681"> &lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;location.reload()&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Reload&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;reconnect-rejected&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Your&lt;span style="color:#6e7681"> &lt;/span>session&lt;span style="color:#6e7681"> &lt;/span>has&lt;span style="color:#6e7681"> &lt;/span>expired.&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>href&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#ff7b72">Return&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">to&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Home&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>a&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A estrutura agora também tenta a reconexão de forma mais agressiva em falhas transitórias e pode restaurar o estado do circuito de forma mais confiável. Isso significa menos momentos do tipo &amp;ldquo;recarregue a página&amp;rdquo; para seus usuários.&lt;/p>
&lt;h3 id="estado-persistente-do-componente-nos-modos-de-renderização">Estado persistente do componente nos modos de renderização&lt;/h3>
&lt;p>Este é um grande problema. No .NET 9, quando um componente é pré-renderizado no servidor e depois faz a transição para o WebAssembly (ou alterna entre os modos de renderização), o estado do componente é perdido. O componente é reinicializado efetivamente, o que pode levar a buscas de dados duplicadas e intermitência na interface do usuário.&lt;/p>
&lt;p>O .NET 10 melhora o serviço &lt;code>PersistentComponentState&lt;/code> para funcionar de maneira mais integrada nas transições do modo de renderização:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/weather&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>rendermode InteractiveWebAssembly
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject PersistentComponentState ApplicationState
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Weather Forecast&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (forecasts is null)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Loading&lt;span style="color:#ff7b72;font-weight:bold">...&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> forecast &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> forecasts)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;forecast-card&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h4&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>forecast&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToShortDateString()&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h4&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>forecast&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Summary &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">@&lt;/span>forecast&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>TemperatureC&lt;span style="color:#f85149">°&lt;/span>C&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>WeatherForecast&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">?&lt;/span> forecasts;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private PersistingComponentStateSubscription persistingSubscription;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> persistingSubscription &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> ApplicationState&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>RegisterOnPersisting(PersistData);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>ApplicationState&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>TryTakeFromJson&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>WeatherForecast&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;gt;&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;weather-forecasts&amp;#34;&lt;/span>, out &lt;span style="color:#ff7b72">var&lt;/span> restored))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">//&lt;/span> Data wasn&lt;span style="color:#a5d6ff">&amp;#39;t persisted from prerendering — fetch it&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> forecasts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await Http&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetFromJsonAsync&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>WeatherForecast&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;gt;&lt;/span>(&lt;span style="color:#a5d6ff">&amp;#34;api/weather&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> forecasts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> restored;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private Task PersistData()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ApplicationState&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>PersistAsJson(&lt;span style="color:#a5d6ff">&amp;#34;weather-forecasts&amp;#34;&lt;/span>, forecasts);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Task&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public void Dispose()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> persistingSubscription&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Dispose();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Com as melhorias no .NET 10, esse padrão funciona de maneira mais confiável. O estado serializado durante a pré-renderização está adequadamente disponível quando o componente é inicializado em seu modo interativo, evitando buscas duplas de dados e a breve oscilação que os usuários podem ver.&lt;/p>
&lt;h3 id="blazor-script-como-um-ativo-da-web-estático">Blazor Script como um ativo da Web estático&lt;/h3>
&lt;p>Nas versões anteriores, o arquivo Blazor JavaScript (&lt;code>blazor.web.js&lt;/code>) era servido a partir do endpoint interno da estrutura. No .NET 10, ele é entregue como um ativo da Web estático. Isso pode parecer uma pequena mudança, mas traz benefícios práticos:- &lt;strong>Melhor armazenamento em cache:&lt;/strong> os ativos estáticos da Web obtêm cabeçalhos de cache e URLs com impressão digital adequados, para que os navegadores os armazenem em cache com mais eficiência.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Compatível com CDN:&lt;/strong> Como é um arquivo estático regular, os CDNs podem armazená-lo em cache e servi-lo a partir de pontos de presença.&lt;/li>
&lt;li>&lt;strong>Compressão:&lt;/strong> A compactação estática de ativos da Web é aplicada automaticamente, reduzindo o tamanho do script na transmissão.&lt;/li>
&lt;/ul>
&lt;p>Você não precisa alterar a forma como faz referência a ele — a estrutura trata o caminho atualizado automaticamente.&lt;/p>
&lt;h2 id="melhores-práticas-escolhendo-o-modo-de-renderização-correto">Melhores práticas: escolhendo o modo de renderização correto&lt;/h2>
&lt;p>Depois de trabalhar com todos esses modos em vários projetos, aqui está minha estrutura de decisão:&lt;/p>
&lt;h3 id="comece-com-ssr-estático">Comece com SSR estático&lt;/h3>
&lt;p>Torne o SSR estático seu padrão. A maioria das páginas na maioria dos aplicativos trata principalmente da exibição de dados. Páginas de produtos, postagens de blog, perfis de usuário, páginas de configurações – não precisam de interatividade em tempo real. O SSR estático oferece o melhor desempenho, o menor uso de recursos e o modelo mental mais simples.&lt;/p>
&lt;h3 id="adicione-interatividade-apenas-quando-necessário">Adicione interatividade apenas quando necessário&lt;/h3>
&lt;p>Identifique os componentes específicos que precisam responder às interações do usuário em tempo real. Um botão “curtir”, um widget de bate-papo, uma interface de arrastar e soltar – tudo isso precisa de interatividade. Mas a página que os cerca provavelmente não.&lt;/p>
&lt;h3 id="use-o-interactive-auto-como-seu-modo-interativo-preferido">Use o Interactive Auto como seu modo interativo preferido&lt;/h3>
&lt;p>Quando você precisa de interatividade, o Interactive Auto costuma ser a melhor opção padrão. Oferece carregamentos iniciais rápidos (renderização do servidor) com eventual execução no lado do cliente (WebAssembly). O usuário obtém o melhor dos dois mundos e você escreve seu código uma vez.&lt;/p>
&lt;h3 id="reservar-servidor-interativo-para-casos-específicos">Reservar Servidor Interativo para Casos Específicos&lt;/h3>
&lt;p>Use o Servidor Interativo quando:&lt;/p>
&lt;ul>
&lt;li>Seu componente precisa de acesso direto aos recursos do servidor (bancos de dados, sistema de arquivos, APIs internas).&lt;/li>
&lt;li>O tamanho do download do WebAssembly é uma preocupação e você não pode usar o Auto.&lt;/li>
&lt;li>Você precisa que o componente seja sempre executado no servidor por motivos de segurança (por exemplo, processamento de dados confidenciais).&lt;/li>
&lt;/ul>
&lt;h3 id="use-streaming-ssr-generosamente">Use streaming SSR generosamente&lt;/h3>
&lt;p>Se suas páginas SSR estáticas buscarem algum dado, adicione &lt;code>[StreamRendering]&lt;/code>. O custo é mínimo e a melhoria de desempenho percebida é significativa. Os usuários veem o conteúdo aparecendo progressivamente, em vez de ficarem olhando para uma página em branco.&lt;/p>
&lt;h3 id="lide-com-as-transições-de-estado-com-cuidado">Lide com as transições de estado com cuidado&lt;/h3>
&lt;p>Se você estiver usando Interactive Auto ou misturando pré-renderização com WebAssembly, sempre use &lt;code>PersistentComponentState&lt;/code> para evitar buscas de dados duplicadas. Seus usuários agradecerão pela ausência de conteúdo oscilante.&lt;/p>
&lt;h3 id="mantenha-a-árvore-de-componentes-em-mente">Mantenha a árvore de componentes em mente&lt;/h3>
&lt;p>Lembre-se das regras de hierarquia do modo de renderização. Planeje sua árvore de componentes para que os limites interativos façam sentido. Um padrão comum é ter um layout estático com &amp;ldquo;ilhas&amp;rdquo; interativas incorporadas quando necessário:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@* Layout: Static SSR *@
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;header&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NavMenu /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;UserMenu @rendermode=&amp;#34;InteractiveServer&amp;#34; /&amp;gt; @* Needs real-time auth state *@
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/header&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;main&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @Body
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/main&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;footer&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ChatWidget @rendermode=&amp;#34;InteractiveAuto&amp;#34; /&amp;gt; @* Rich interactivity *@
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/footer&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>O modelo de interatividade do Blazor no .NET 9 e .NET 10 representa uma abordagem madura e bem pensada para a construção de aplicativos da web. A capacidade de escolher modos de renderização por componente, a navegação aprimorada e contínua, o SSR de streaming e as melhorias contínuas na reconexão e no gerenciamento de estado tornam-no uma escolha atraente para uma ampla gama de aplicações.A principal ideia é que &lt;strong>a interatividade é um espectro, não uma escolha binária&lt;/strong>. A maior parte do seu aplicativo pode ser estática. Algumas partes precisam de interatividade orientada pelo servidor. Alguns podem se beneficiar da execução no navegador. O Blazor agora permite que você faça essa escolha com a granularidade mais fina — o componente individual — sem lutar contra a estrutura.&lt;/p>
&lt;p>Se você estiver iniciando um novo projeto, meu conselho é simples: crie um Blazor Web App, comece com SSR estático, adicione &lt;code>[StreamRendering]&lt;/code> a páginas com muitos dados e promova componentes individuais para modos interativos somente quando precisar deles. Você acabará com um aplicativo rápido, eficiente e bem dimensionável.&lt;/p>
&lt;p>Boa codificação e, como sempre, fique à vontade para entrar em contato se tiver dúvidas!&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category></item><item><title>APIs mínimas em .NET: criando APIs HTTP leves</title><link>https://emimontesdeoca.github.io/pt/posts/minimal-apis-dotnet/</link><pubDate>Thu, 10 Apr 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/minimal-apis-dotnet/</guid><description>Um guia abrangente para criar APIs HTTP limpas e rápidas com APIs .NET Minimal — desde manipuladores de rotas e vinculação de parâmetros até filtros e integração OpenAPI.</description><content:encoded>&lt;p>Se você já cria APIs com ASP.NET Core há algum tempo, provavelmente está familiarizado com a abordagem baseada em controlador: crie uma classe de controlador, decore-a com atributos, injete seus serviços por meio do construtor e conecte suas rotas. Funciona, e funciona bem - mas às vezes parece que você está escrevendo muita cerimônia no que equivale a &amp;ldquo;aceitar este pedido, fazer algo, retornar uma resposta&amp;rdquo;.&lt;/p>
&lt;p>Esse é exatamente o problema que as APIs mínimas pretendem resolver. Introduzidas no .NET 6, as APIs mínimas permitem definir pontos de extremidade HTTP com muito pouco padrão. Sem controladores, sem atributos, sem malabarismo com classes de inicialização — apenas mapeamentos diretos de rota para manipulador em um estilo limpo e funcional.&lt;/p>
&lt;p>Nesta postagem, quero orientar você sobre tudo o que você precisa saber sobre APIs mínimas: desde o primeiro endpoint até a organização de grandes aplicativos, tratamento de autenticação, validação, documentos OpenAPI e considerações de desempenho. Vamos lá.&lt;/p>
&lt;h2 id="por-que-apis-mínimas">Por que APIs mínimas?&lt;/h2>
&lt;p>Antes de começarmos, vamos falar sobre &lt;em>por que&lt;/em> você escolheria APIs mínimas em vez da abordagem tradicional baseada em controlador.&lt;/p>
&lt;p>&lt;strong>Controladores&lt;/strong> são ótimos quando você precisa de uma estrutura estruturada e opinativa. Eles fornecem vinculação de modelo, filtros, negociação de conteúdo e uma separação clara de preocupações prontas para uso. Para aplicações empresariais de grande porte com dezenas de desenvolvedores, a consistência imposta pelos controladores pode ser um benefício real.&lt;/p>
&lt;p>&lt;strong>APIs mínimas&lt;/strong>, por outro lado, brilham quando você deseja:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Menos clichê&lt;/strong> — sem classes de controlador, sem atributos &lt;code>[ApiController]&lt;/code>, sem configuração de inicialização separada.&lt;/li>
&lt;li>&lt;strong>Inicialização mais rápida&lt;/strong> — menos operações baseadas em reflexão no momento da inicialização.&lt;/li>
&lt;li>&lt;strong>Modelo mental mais simples&lt;/strong> — uma rota é mapeada diretamente para um manipulador. É isso.&lt;/li>
&lt;li>&lt;strong>Otimizado para microsserviços&lt;/strong> — quando sua API tem de 5 a 10 endpoints, uma configuração completa do controlador pode parecer um exagero.&lt;/li>
&lt;/ul>
&lt;p>A boa notícia é que esta não é uma decisão de um ou outro. Você pode misturar controladores e APIs mínimas no mesmo projeto. Mas quando você se sentir confortável com a abordagem mínima, poderá buscá-la com mais frequência do que esperava.&lt;/p>
&lt;h2 id="primeiros-passos">Primeiros passos&lt;/h2>
&lt;p>Vamos criar uma API Minimal do zero. Se você tiver o SDK do .NET instalado, é tão simples quanto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new web -n MyMinimalApi
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd MyMinimalApi
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso fornece um &lt;code>Program.cs&lt;/code> parecido com isto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, () =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Hello World!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É isso. Essa é uma API funcional. Não &lt;code>Startup.cs&lt;/code>, nenhuma classe de controlador, nenhuma configuração de roteamento. Você executa &lt;code>dotnet run&lt;/code>, pressiona &lt;code>http://localhost:5000&lt;/code> e obtém &amp;ldquo;Olá, mundo!&amp;rdquo; voltar. A simplicidade aqui é o ponto principal.&lt;/p>
&lt;p>Vamos torná-lo um pouco mais útil com uma API de tarefas clássica:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> todos = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;Todo&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, () =&amp;gt; todos);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todos.FirstOrDefault(t =&amp;gt; t.Id == id) &lt;span style="color:#ff7b72">is&lt;/span> { } todo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? Results.Ok(todo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : Results.NotFound());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, (Todo todo) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapDelete(&lt;span style="color:#a5d6ff">&amp;#34;/todos/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = todos.FirstOrDefault(t =&amp;gt; t.Id == id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (todo &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>) &lt;span style="color:#ff7b72">return&lt;/span> Results.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todos.Remove(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.NoContent();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Todo&lt;/span>(&lt;span style="color:#ff7b72">int&lt;/span> Id, &lt;span style="color:#ff7b72">string&lt;/span> Title, &lt;span style="color:#ff7b72">bool&lt;/span> IsComplete);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Temos CRUD completo em menos de 30 linhas. Esse é o poder da abordagem mínima.&lt;/p>
&lt;h2 id="manipuladores-de-rota">Manipuladores de rota&lt;/h2>
&lt;p>Manipuladores de rota são as funções executadas quando uma solicitação corresponde a uma rota. Você tem várias opções para defini-los.&lt;/p>
&lt;h3 id="expressões-lambda">Expressões Lambda&lt;/h3>
&lt;p>A abordagem mais comum e o que você verá na maioria dos exemplos:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/hello&amp;#34;&lt;/span>, () =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Hello!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/hello/{name}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">string&lt;/span> name) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Hello, {name}!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="grupos-de-métodos">Grupos de métodos&lt;/h3>
&lt;p>Para uma lógica mais complexa, você pode apontar para um método nomeado:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>, GetProducts);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>, CreateProduct);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> IResult GetProducts(AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; Results.Ok(db.Products.ToList());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> IResult CreateProduct(Product product, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Products.Add(product);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.SaveChanges();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/products/{product.Id}&amp;#34;&lt;/span>, product);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Esta &lt;span style="color:#f85149">é&lt;/span> minha abordagem preferida para qualquer coisa além de uma linha. Ele mantém sua seção de mapeamento de rotas limpa e legível &lt;span style="color:#f85149">–&lt;/span> você pode ver rapidamente quais pontos de extremidade existem sem se aprofundar nos detalhes de implementação.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> Funções locais
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Você também pode usar funções locais, o que &lt;span style="color:#f85149">é&lt;/span> &lt;span style="color:#f85149">ú&lt;/span>til quando você deseja manter os manipuladores próximos &lt;span style="color:#f85149">à&lt;/span>s suas definições de rota:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/health&amp;#34;&lt;/span>, CheckHealth);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>IResult CheckHealth()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Some health check logic&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(&lt;span style="color:#ff7b72">new&lt;/span> { Status = &lt;span style="color:#a5d6ff">&amp;#34;Healthy&amp;#34;&lt;/span>, Timestamp = DateTime.UtcNow });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="associação-de-parâmetros">Associação de parâmetros&lt;/h2>
&lt;p>Uma das coisas que realmente aprecio nas APIs mínimas é como a vinculação de parâmetros é intuitiva. A estrutura descobre de onde extrair valores com base no contexto e no tipo.&lt;/p>
&lt;h3 id="parâmetros-de-rota">Parâmetros de rota&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/users/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;User {id}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/files/{*path}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">string&lt;/span> path) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;File: {path}&amp;#34;&lt;/span>); &lt;span style="color:#8b949e;font-style:italic">// catch-all&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="parâmetros-de-string-de-consulta">Parâmetros de string de consulta&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/search&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">string?&lt;/span> query, &lt;span style="color:#ff7b72">int&lt;/span> page = &lt;span style="color:#a5d6ff">1&lt;/span>, &lt;span style="color:#ff7b72">int&lt;/span> pageSize = &lt;span style="color:#a5d6ff">20&lt;/span>) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Results.Ok(&lt;span style="color:#ff7b72">new&lt;/span> { query, page, pageSize }));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Tipos anuláveis tornam-se parâmetros opcionais. Os valores padrão funcionam exatamente como você espera.&lt;/p>
&lt;h3 id="corpo-da-solicitação">Corpo da solicitação&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/orders&amp;#34;&lt;/span>, (Order order) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// &amp;#39;order&amp;#39; is automatically deserialized from the JSON body&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/orders/{order.Id}&amp;#34;&lt;/span>, order);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="cabeçalho-e-vinculação-de-serviço">Cabeçalho e vinculação de serviço&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/protected&amp;#34;&lt;/span>, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [FromHeader(Name = &amp;#34;X-Api-Key&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> apiKey,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [FromServices] ILogger&amp;lt;Program&amp;gt; logger) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Request with API key: {Key}&amp;#34;&lt;/span>, apiKey[..&lt;span style="color:#a5d6ff">4&lt;/span>] + &lt;span style="color:#a5d6ff">&amp;#34;****&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(&lt;span style="color:#a5d6ff">&amp;#34;Authorized&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="httpcontext-e-httprequest">HttpContext e HttpRequest&lt;/h3>
&lt;p>Quando você precisar de acesso de nível inferior:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/info&amp;#34;&lt;/span>, (HttpContext context) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> userAgent = context.Request.Headers.UserAgent.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> ip = context.Connection.RemoteIpAddress?.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(&lt;span style="color:#ff7b72">new&lt;/span> { userAgent, ip });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="vinculação-personalizada-com-bindasync">Vinculação personalizada com BindAsync&lt;/h3>
&lt;p>Para tipos complexos, você pode implementar um método estático &lt;code>BindAsync&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">PaginationParams&lt;/span>(&lt;span style="color:#ff7b72">int&lt;/span> Page, &lt;span style="color:#ff7b72">int&lt;/span> PageSize)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> ValueTask&amp;lt;PaginationParams?&amp;gt; BindAsync(HttpContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span>.TryParse(context.Request.Query[&lt;span style="color:#a5d6ff">&amp;#34;page&amp;#34;&lt;/span>], &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> page);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span>.TryParse(context.Request.Query[&lt;span style="color:#a5d6ff">&amp;#34;pageSize&amp;#34;&lt;/span>], &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> pageSize);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">new&lt;/span> PaginationParams(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Page: page &amp;gt; &lt;span style="color:#a5d6ff">0&lt;/span> ? page : &lt;span style="color:#a5d6ff">1&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PageSize: pageSize &amp;gt; &lt;span style="color:#a5d6ff">0&lt;/span> ? Math.Min(pageSize, &lt;span style="color:#a5d6ff">100&lt;/span>) : &lt;span style="color:#a5d6ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ValueTask.FromResult&amp;lt;PaginationParams?&amp;gt;(result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/items&amp;#34;&lt;/span>, (PaginationParams pagination, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> items = db.Items
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Skip((pagination.Page - &lt;span style="color:#a5d6ff">1&lt;/span>) * pagination.PageSize)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Take(pagination.PageSize)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToList();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(items);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso é incrivelmente poderoso. Você define a lógica de ligação uma vez e a reutiliza em todos os seus endpoints.&lt;/p>
&lt;h2 id="validação">Validação&lt;/h2>
&lt;p>Uma área em que as APIs mínimas não oferecem tanta coisa pronta para uso em comparação com os controladores é a validação de modelo. Não há aplicação automática de &lt;code>[Required]&lt;/code> ou &lt;code>[StringLength]&lt;/code>. Mas existem padrões claros para lidar com isso.&lt;/p>
&lt;h3 id="validação-manual">Validação manual&lt;/h3>
&lt;p>A abordagem mais simples — basta validar no manipulador:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/users&amp;#34;&lt;/span>, (CreateUserRequest request) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(request.Email))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Error = &lt;span style="color:#a5d6ff">&amp;#34;Email is required&amp;#34;&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (request.Name?.Length &amp;gt; &lt;span style="color:#a5d6ff">100&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Error = &lt;span style="color:#a5d6ff">&amp;#34;Name must be 100 characters or less&amp;#34;&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create user...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/users/{request.Email}&amp;#34;&lt;/span>, request);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="usando-uma-biblioteca-de-validação">Usando uma biblioteca de validação&lt;/h3>
&lt;p>Para qualquer coisa não trivial, recomendo usar FluentValidation ou uma biblioteca semelhante:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CreateUserValidator&lt;/span> : AbstractValidator&amp;lt;CreateUserRequest&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CreateUserValidator()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuleFor(x =&amp;gt; x.Email).NotEmpty().EmailAddress();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuleFor(x =&amp;gt; x.Name).NotEmpty().MaximumLength(&lt;span style="color:#a5d6ff">100&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuleFor(x =&amp;gt; x.Age).InclusiveBetween(&lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">150&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Você pode conectar isso aos seus endpoints por meio de filtros de endpoint, o que nos leva à próxima seção.&lt;/p>
&lt;h2 id="filtros-de-endpoint">Filtros de endpoint&lt;/h2>
&lt;p>Os filtros de endpoint são um dos melhores recursos das APIs mínimas. Pense neles como middleware, mas com escopo para endpoints específicos, em vez de todo o pipeline. Eles foram introduzidos no .NET 7 e são fantásticos para questões transversais.&lt;/p>
&lt;h3 id="filtro-básico">Filtro Básico&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, (Todo todo) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Handle the request&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.AddEndpointFilter(&lt;span style="color:#ff7b72">async&lt;/span> (context, next) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = context.GetArgument&amp;lt;Todo&amp;gt;(&lt;span style="color:#a5d6ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(todo.Title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Error = &lt;span style="color:#a5d6ff">&amp;#34;Title is required&amp;#34;&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">await&lt;/span> next(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="filtro-de-validação-com-fluentvalidation">Filtro de validação com FluentValidation&lt;/h3>
&lt;p>É aqui que tudo se torna realmente poderoso – um filtro de validação reutilizável:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ValidationFilter&lt;/span>&amp;lt;T&amp;gt; : IEndpointFilter &lt;span style="color:#ff7b72">where&lt;/span> T : &lt;span style="color:#ff7b72">class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IValidator&amp;lt;T&amp;gt; _validator;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ValidationFilter(IValidator&amp;lt;T&amp;gt; validator)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _validator = validator;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> ValueTask&amp;lt;&lt;span style="color:#ff7b72">object?&lt;/span>&amp;gt; InvokeAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EndpointFilterInvocationContext context,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EndpointFilterDelegate next)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> model = context.Arguments
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OfType&amp;lt;T&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (model &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Error = &lt;span style="color:#a5d6ff">&amp;#34;Request body is required&amp;#34;&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> _validator.ValidateAsync(model);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!result.IsValid)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> errors = result.Errors
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GroupBy(e =&amp;gt; e.PropertyName)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToDictionary(g =&amp;gt; g.Key, g =&amp;gt; g.Select(e =&amp;gt; e.ErrorMessage).ToArray());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.ValidationProblem(errors);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">await&lt;/span> next(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Usage&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/users&amp;#34;&lt;/span>, (CreateUserRequest request) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Only reached if validation passes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/users/{request.Email}&amp;#34;&lt;/span>, request);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.AddEndpointFilter&amp;lt;ValidationFilter&amp;lt;CreateUserRequest&amp;gt;&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="filtro-de-registro">Filtro de registro&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>, (AppDbContext db) =&amp;gt; db.Products.ToList())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEndpointFilter(&lt;span style="color:#ff7b72">async&lt;/span> (context, next) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> logger = context.HttpContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .RequestServices.GetRequiredService&amp;lt;ILogger&amp;lt;Program&amp;gt;&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sw = Stopwatch.StartNew();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> next(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sw.Stop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Endpoint executed in {Elapsed}ms&amp;#34;&lt;/span>, sw.ElapsedMilliseconds);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Você pode encadear vários filtros e eles são executados na ordem em que são adicionados — assim como o middleware.&lt;/p>
&lt;h2 id="integração-openapiswagger">Integração OpenAPI/Swagger&lt;/h2>
&lt;p>Uma boa documentação de API não é mais opcional. Felizmente, as APIs Minimal têm suporte de primeira classe para OpenAPI por meio do pacote &lt;code>Microsoft.AspNetCore.OpenApi&lt;/code>.&lt;/p>
&lt;h3 id="configuração-básica">Configuração básica&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddOpenApi();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapOpenApi();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, () =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;Todo&amp;gt;())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithName(&lt;span style="color:#a5d6ff">&amp;#34;GetTodos&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithDescription(&lt;span style="color:#a5d6ff">&amp;#34;Retrieves all todo items&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithTags(&lt;span style="color:#a5d6ff">&amp;#34;Todos&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="metadados-avançados-de-endpoint">Metadados avançados de endpoint&lt;/h3>
&lt;p>Você pode fornecer informações detalhadas sobre seus endpoints:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, (Todo todo) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.WithName(&lt;span style="color:#a5d6ff">&amp;#34;CreateTodo&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.WithDescription(&lt;span style="color:#a5d6ff">&amp;#34;Creates a new todo item&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.WithTags(&lt;span style="color:#a5d6ff">&amp;#34;Todos&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.Accepts&amp;lt;Todo&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;application/json&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.Produces&amp;lt;Todo&amp;gt;(StatusCodes.Status201Created)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.Produces(StatusCodes.Status400BadRequest)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.ProducesValidationProblem();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="usando-withopenapi-para-personalização">Usando WithOpenApi para personalização&lt;/h3>
&lt;p>Para controle refinado sobre o documento OpenAPI gerado:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id) =&amp;gt; Results.Ok(&lt;span style="color:#ff7b72">new&lt;/span> Todo(id, &lt;span style="color:#a5d6ff">&amp;#34;Sample&amp;#34;&lt;/span>, &lt;span style="color:#79c0ff">false&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithOpenApi(operation =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.Summary = &lt;span style="color:#a5d6ff">&amp;#34;Get a specific todo&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.Description = &lt;span style="color:#a5d6ff">&amp;#34;Retrieves a single todo item by its unique identifier.&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.Parameters[&lt;span style="color:#a5d6ff">0&lt;/span>].Description = &lt;span style="color:#a5d6ff">&amp;#34;The unique identifier of the todo item&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> operation;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso oferece o mesmo nível de controle de documentação que você obteria com os comentários XML do Swashbuckle nos controladores, mas de uma forma mais explícita, que prioriza o código.&lt;/p>
&lt;h2 id="autenticação-e-autorização">Autenticação e Autorização&lt;/h2>
&lt;p>A proteção de endpoints mínimos da API segue os mesmos padrões do restante do ASP.NET Core — basta aplicá-los de maneira diferente.&lt;/p>
&lt;h3 id="configuração-básicacsharp">Configuração básica```csharp&lt;/h3>
&lt;p>var builder = WebApplication.CreateBuilder(args);&lt;/p>
&lt;p>builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =&amp;gt;
{
options.Authority = &amp;ldquo;&lt;a href="https://your-identity-provider.com">https://your-identity-provider.com&lt;/a>&amp;rdquo;;
options.Audience = &amp;ldquo;your-api&amp;rdquo;;
});&lt;/p>
&lt;p>builder.Services.AddAuthorizationBuilder()
.AddPolicy(&amp;ldquo;AdminOnly&amp;rdquo;, policy =&amp;gt; policy.RequireRole(&amp;ldquo;Admin&amp;rdquo;))
.AddPolicy(&amp;ldquo;PremiumUser&amp;rdquo;, policy =&amp;gt; policy.RequireClaim(&amp;ldquo;subscription&amp;rdquo;, &amp;ldquo;premium&amp;rdquo;));&lt;/p>
&lt;p>var app = builder.Build();&lt;/p>
&lt;p>app.UseAuthentication();
app.UseAuthorization();&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>### Aplicando autorização a endpoints
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>```csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>// Require any authenticated user
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&amp;#34;/profile&amp;#34;, (ClaimsPrincipal user) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> return Results.Ok(new
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = user.FindFirstValue(ClaimTypes.Name),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Email = user.FindFirstValue(ClaimTypes.Email)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}).RequireAuthorization();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>// Require a specific policy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapDelete(&amp;#34;/admin/users/{id}&amp;#34;, (int id) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> // Delete user logic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> return Results.NoContent();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}).RequireAuthorization(&amp;#34;AdminOnly&amp;#34;);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>// Allow anonymous access
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&amp;#34;/public/health&amp;#34;, () =&amp;gt; Results.Ok(new { Status = &amp;#34;Healthy&amp;#34; }))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AllowAnonymous();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Observe como você pode injetar &lt;code>ClaimsPrincipal&lt;/code> diretamente nos parâmetros do seu manipulador — a estrutura cuida do resto. Essa é uma daquelas pequenas coisas que fazem as APIs Minimal parecerem realmente elegantes.&lt;/p>
&lt;h2 id="organizando-apis-grandes">Organizando APIs grandes&lt;/h2>
&lt;p>O elefante na sala com APIs mínimas é a organização. Quando seu &lt;code>Program.cs&lt;/code> tem 50 endpoints, fica uma bagunça. Aqui estão os padrões que uso para manter as coisas gerenciáveis.&lt;/p>
&lt;h3 id="grupos-de-rotas">Grupos de rotas&lt;/h3>
&lt;p>Os grupos de rotas (introduzidos no .NET 7) permitem compartilhar configurações entre endpoints relacionados:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> todos = app.MapGroup(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithTags(&lt;span style="color:#a5d6ff">&amp;#34;Todos&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .RequireAuthorization();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>todos.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, (AppDbContext db) =&amp;gt; db.Todos.ToListAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>todos.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id, AppDbContext db) =&amp;gt; db.Todos.FindAsync(id));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>todos.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, (Todo todo, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Todos os endpoints do grupo compartilham o prefixo &lt;code>/todos&lt;/code>, a tag &lt;code>Todos&lt;/code> e o requisito de autorização. Limpar.&lt;/p>
&lt;h3 id="métodos-de-extensão">Métodos de extensão&lt;/h3>
&lt;p>Este é o padrão que realmente aumenta. Mova cada grupo de endpoints para sua própria classe estática:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Endpoints/TodoEndpoints.cs&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TodoEndpoints&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> RouteGroupBuilder MapTodoEndpoints(&lt;span style="color:#ff7b72">this&lt;/span> WebApplication app)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> &lt;span style="color:#ff7b72">group&lt;/span> = app.MapGroup(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>).WithTags(&lt;span style="color:#a5d6ff">&amp;#34;Todos&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, GetAll);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/{id:int}&amp;#34;&lt;/span>, GetById);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, Create);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapPut(&lt;span style="color:#a5d6ff">&amp;#34;/{id:int}&amp;#34;&lt;/span>, Update);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapDelete(&lt;span style="color:#a5d6ff">&amp;#34;/{id:int}&amp;#34;&lt;/span>, Delete);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">group&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; GetAll(AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; Results.Ok(&lt;span style="color:#ff7b72">await&lt;/span> db.Todos.ToListAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; GetById(&lt;span style="color:#ff7b72">int&lt;/span> id, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.FindAsync(id) &lt;span style="color:#ff7b72">is&lt;/span> { } todo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? Results.Ok(todo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : Results.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; Create(Todo todo, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; Update(&lt;span style="color:#ff7b72">int&lt;/span> id, Todo updated, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.FindAsync(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (todo &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>) &lt;span style="color:#ff7b72">return&lt;/span> Results.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todo.Title = updated.Title;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todo.IsComplete = updated.IsComplete;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; Delete(&lt;span style="color:#ff7b72">int&lt;/span> id, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.FindAsync(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (todo &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>) &lt;span style="color:#ff7b72">return&lt;/span> Results.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Remove(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.NoContent();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Program.cs — stays clean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapTodoEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapUserEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapOrderEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Seu &lt;code>Program.cs&lt;/code> se torna um índice para sua API. Cada grupo de endpoints reside em seu próprio arquivo. Esta é a abordagem que recomendo para aplicações de produção.&lt;/p>
&lt;h3 id="biblioteca-carter">Biblioteca Carter&lt;/h3>
&lt;p>Se você quiser ainda mais estrutura, a biblioteca Carter oferece uma abordagem baseada em módulos:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TodoModule&lt;/span> : ICarterModule
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> AddRoutes(IEndpointRouteBuilder app)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.ToListAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (Todo todo, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Carter descobre e registra automaticamente todos os módulos. É um bom meio-termo entre a abordagem bruta da API Mínima e os controladores completos.&lt;/p>
&lt;h2 id="typedresults-e-tipos-de-resposta">TypedResults e tipos de resposta&lt;/h2>
&lt;p>A partir do .NET 7, você pode usar &lt;code>TypedResults&lt;/code> em vez de &lt;code>Results&lt;/code> para respostas com segurança de tipo. Isso pode parecer uma pequena mudança, mas traz benefícios reais para a documentação e testabilidade do OpenAPI.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos/{id:int}&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;Results&amp;lt;Ok&amp;lt;Todo&amp;gt;, NotFound&amp;gt;&amp;gt; (&lt;span style="color:#ff7b72">int&lt;/span> id, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.FindAsync(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> todo &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? TypedResults.Ok(todo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : TypedResults.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O tipo de retorno &lt;code>Results&amp;lt;Ok&amp;lt;Todo&amp;gt;, NotFound&amp;gt;&lt;/code> informa explicitamente à estrutura (e aos seus documentos OpenAPI) exatamente quais tipos de resposta este endpoint pode produzir. Chega de suposições, chega de chamadas manuais &lt;code>Produces&amp;lt;&amp;gt;()&lt;/code> para casos básicos.&lt;/p>
&lt;p>Para vários resultados possíveis:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;Results&amp;lt;Created&amp;lt;Todo&amp;gt;, ValidationProblem, Conflict&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Todo todo, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(todo.Title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> TypedResults.ValidationProblem(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>[]&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> { &lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">new&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;Title is required&amp;#34;&lt;/span> } }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">await&lt;/span> db.Todos.AnyAsync(t =&amp;gt; t.Title == todo.Title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> TypedResults.Conflict();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> TypedResults.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Comecei a usar &lt;code>TypedResults&lt;/code> em todos os novos projetos. O compilador detecta incompatibilidades entre os tipos de retorno declarados e o que você realmente retorna, o que elimina uma classe inteira de surpresas em tempo de execução.&lt;/p>
&lt;h2 id="considerações-de-desempenho">Considerações de desempenho&lt;/h2>
&lt;p>Um dos pontos de venda das APIs Minimal é o desempenho, e vale a pena entender &lt;em>por que&lt;/em> elas são mais rápidas.&lt;/p>
&lt;p>&lt;strong>Sobrecarga de inicialização reduzida.&lt;/strong> Os controladores dependem muito da reflexão para descobrir endpoints, vincular modelos e aplicar filtros. APIs mínimas usam geradores de origem (a partir do .NET 7) para gerar código de ligação em tempo de compilação. Isso significa menos trabalho na inicialização e menos alocação de memória por solicitação.&lt;/p>
&lt;p>&lt;strong>Sem pipeline MVC.&lt;/strong> APIs baseadas em controlador passam por todo o pipeline MVC: seleção de ação, vinculação de modelo, filtros de ação, execução de resultados. APIs mínimas ignoram tudo isso e vão direto do roteamento para o seu manipulador.&lt;/p>
&lt;p>&lt;strong>Compilação RequestDelegate.&lt;/strong> A estrutura compila suas expressões lambda em instâncias &lt;code>RequestDelegate&lt;/code> otimizadas. O código resultante é muito próximo do que você escreveria à mão se estivesse trabalhando diretamente com &lt;code>HttpContext&lt;/code>.&lt;/p>
&lt;p>Aqui estão algumas dicas práticas para maximizar o desempenho:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Use AsNoTracking for read-only queries&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.Products.AsNoTracking().ToListAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Return results directly — avoid unnecessary allocations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/count&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.Products.CountAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Use cancellation tokens for long-running operations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/report&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AppDbContext db, CancellationToken ct) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AsNoTracking()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GroupBy(o =&amp;gt; o.Status)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(g =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> { Status = g.Key, Count = g.Count() })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync(ct));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Também vale a pena mencionar que a lacuna de desempenho entre controladores e APIs mínimas continua diminuindo a cada versão &lt;span style="color:#ff7b72">do&lt;/span> .NET. Para a maioria dos aplicativos, a diferença não será o gargalo, mas sim &lt;span style="color:#ff7b72">as&lt;/span> consultas ao banco de dados e &lt;span style="color:#ff7b72">as&lt;/span> chamadas de serviço externas. Escolha com &lt;span style="color:#ff7b72">base&lt;/span> na experiência &lt;span style="color:#ff7b72">do&lt;/span> desenvolvedor e nas necessidades &lt;span style="color:#ff7b72">do&lt;/span> projeto, não em benchmarks.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> Conclusão
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>As APIs mínimas percorreram um longo caminho desde sua introdução no .NET &lt;span style="color:#a5d6ff">6.&lt;/span> O que começou como um recurso de demonstração &lt;span style="color:#ff7b72">do&lt;/span> tipo &lt;span style="color:#a5d6ff">&amp;#34;olá mundo&amp;#34;&lt;/span> amadureceu e se tornou uma escolha legítima para APIs de produção. Com filtros de endpoint, grupos de rotas, resultados digitados e suporte sólido a OpenAPI, você tem tudo o que precisa para criar serviços bem estruturados e de fácil manutenção.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Minha recomendação? Se você estiver iniciando um novo projeto de API &lt;span style="color:#f85149">—&lt;/span> especialmente um microsserviço ou uma API interna focada &lt;span style="color:#f85149">—&lt;/span> experimente seriamente &lt;span style="color:#ff7b72">as&lt;/span> APIs Minimal. Use o padrão de método de extensão para organização, conte com filtros de endpoint para questões transversais e aproveite &lt;span style="color:#f85149">`&lt;/span>TypedResults&lt;span style="color:#f85149">`&lt;/span> para segurança de tipo.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Para projetos existentes baseados em controlador, não há pressa para migrar. Ambas &lt;span style="color:#ff7b72">as&lt;/span> abordagens funcionam bem e você pode até usá-las lado a lado. Mas da próxima vez que você precisar adicionar um pequeno serviço ou uma API interna rápida, ignore os controladores e vá para o mínimo. Você pode não voltar.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Boa codificação!
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content:encoded><category>.NET</category><category>API</category><category>Web Development</category></item><item><title>Usando CSS isolado em componentes do Blazor</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-isolated-css/</link><pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-isolated-css/</guid><description>Defina estilos CSS para componentes individuais do Blazor usando isolamento CSS para evitar conflitos de estilo globais.</description><content:encoded>&lt;p>Uma coisa que sempre me incomodou ao trabalhar com o Blazor foi como era fácil quebrar acidentalmente estilos entre componentes. Você adicionava uma classe em um componente e, de repente, outra coisa em uma página completamente diferente parecia desativada. Acontece que o Blazor tem uma solução integrada para isso: isolamento CSS.&lt;/p>
&lt;h1 id="o-que-é-isolamento-css">O que é isolamento CSS?&lt;/h1>
&lt;p>O isolamento CSS permite definir estilos para um componente específico. O Blazor faz isso gerando um atributo exclusivo para cada componente no momento da construção e anexando-o aos seletores CSS. Portanto, seus estilos se aplicam apenas ao componente ao qual pertencem.&lt;/p>
&lt;p>Não há necessidade de nomenclatura BEM, nem de hacks malucos de especificidade. Apenas CSS limpo e com escopo definido.&lt;/p>
&lt;p>#Como usar&lt;/p>
&lt;p>Digamos que você tenha um componente chamado &lt;code>Card.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span> class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#7ee787">h3&lt;/span> class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-title&amp;#34;&lt;/span>&amp;gt;@Title&amp;lt;/&lt;span style="color:#7ee787">h3&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#7ee787">p&lt;/span> class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-body&amp;#34;&lt;/span>&amp;gt;@ChildContent&amp;lt;/&lt;span style="color:#7ee787">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public string Title { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public RenderFragment ChildContent { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para adicionar estilos isolados, basta criar um arquivo com o mesmo nome, mas com extensão &lt;code>.razor.css&lt;/code>. Neste caso: &lt;code>Card.razor.css&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">card&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">border&lt;/span>: &lt;span style="color:#a5d6ff">1&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span> &lt;span style="color:#79c0ff">solid&lt;/span> &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>border);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">border-radius&lt;/span>: &lt;span style="color:#a5d6ff">8&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">padding&lt;/span>: &lt;span style="color:#a5d6ff">1&lt;/span>&lt;span style="color:#ff7b72">rem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">background&lt;/span>: &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>bg&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>secondary);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">card-title&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">font-size&lt;/span>: &lt;span style="color:#a5d6ff">1.1&lt;/span>&lt;span style="color:#ff7b72">rem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">font-weight&lt;/span>: &lt;span style="color:#a5d6ff">600&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">margin-bottom&lt;/span>: &lt;span style="color:#a5d6ff">0.5&lt;/span>&lt;span style="color:#ff7b72">rem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">card-body&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">color&lt;/span>: &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#79c0ff">text&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>secondary);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">font-size&lt;/span>: &lt;span style="color:#a5d6ff">0.9&lt;/span>&lt;span style="color:#ff7b72">rem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E é isso! O Blazor irá selecioná-lo automaticamente e definir o escopo desses estilos apenas para o componente &lt;code>Card&lt;/code>. Se você tiver outra classe &lt;code>.card&lt;/code> em outro lugar, ela não será afetada.&lt;/p>
&lt;h1 id="como-funciona-nos-bastidores">Como funciona nos bastidores&lt;/h1>
&lt;p>Quando você cria seu projeto, o Blazor reescreve o HTML e o CSS. Seu componente recebe um atributo exclusivo como &lt;code>b-3x8qz7k2f1&lt;/code>, e os seletores CSS recebem esse atributo anexado:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">card&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">[&lt;/span>&lt;span style="color:#7ee787">b-3x8qz7k2f1&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">]&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">border&lt;/span>: &lt;span style="color:#a5d6ff">1&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span> &lt;span style="color:#79c0ff">solid&lt;/span> &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>border);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">/* ... */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O pacote CSS gerado é servido como &lt;code>{ProjectName}.styles.css&lt;/code>. Certifique-se de ter isso em seu &lt;code>index.html&lt;/code> ou &lt;code>_Host.cshtml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">link&lt;/span> href&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;YourApp.styles.css&amp;#34;&lt;/span> rel&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;stylesheet&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Se você perder esse link, seus estilos isolados não aparecerão e você passará 30 minutos se perguntando o que está acontecendo. Confie em mim, eu estive lá.&lt;/p>
&lt;h1 id="visando-elementos-filhos">Visando elementos filhos&lt;/h1>
&lt;p>Uma coisa a ter em mente é que, por padrão, o CSS isolado se aplica apenas aos elementos do componente atual. Se você deseja estilizar elementos dentro de um componente filho, você precisa do combinador &lt;code>::deep&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>::&lt;span style="color:#d2a8ff;font-weight:bold">deep&lt;/span> .&lt;span style="color:#f0883e;font-weight:bold">child-element&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">color&lt;/span>: &lt;span style="color:#79c0ff">red&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso diz ao Blazor para aplicar o estilo aos elementos correspondentes também nos componentes filhos. Muito útil quando você tem um componente wrapper que precisa estilizar seus filhos.&lt;/p>
&lt;h1 id="quando-usar">Quando usar&lt;/h1>
&lt;p>Eu uso isolamento CSS para praticamente todos os componentes agora. Isso mantém as coisas limpas e previsíveis. A única vez que não uso isso é para estilos verdadeiramente globais, como redefinições, tipografia ou variáveis ​​de tema - elas vão em um arquivo CSS compartilhado.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem! Sinta-se à vontade para entrar em contato comigo em qualquer mídia social em &lt;strong>@emimontesdeoca&lt;/strong>.&lt;/p>
&lt;h1 id="recursos">Recursos&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation">Isolamento de CSS do ASP.NET Core Blazor&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>CSS</category></item><item><title>Controlando os gastos de Natal com Kernel Semântico</title><link>https://emimontesdeoca.github.io/pt/posts/keeping-christmas-spending-with-semantic-kernel/</link><pubDate>Sat, 28 Dec 2024 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/keeping-christmas-spending-with-semantic-kernel/</guid><description>Analise receitas e acompanhe os gastos de Natal usando Semantic Kernel, Azure OpenAI e Blazor.</description><content:encoded>&lt;p>﻿## Introdução&lt;/p>
&lt;p>À medida que as festas de fim de ano se aproximam, gerenciar despesas pode se tornar um desafio, principalmente com a enxurrada de compras e compras de presentes. Nesta postagem do blog, exploraremos como aproveitar a inteligência artificial para ajudar a controlar seus gastos de Natal usando tecnologias .NET. Ao analisar recibos com o poder do Kernel Semântico e da IA, podemos extrair com eficiência detalhes importantes, como nomes de lojas, datas, listas de itens e valores totais. Esta solução permite monitorar e gerenciar sem esforço seus gastos de Natal, garantindo que você mantenha o controle do seu orçamento sem o incômodo de revisar manualmente os recibos.&lt;/p>
&lt;h2 id="calendário-de-adviento-de-inteligência-artificial-2024-em-espanhol">Calendário de Adviento de Inteligência Artificial 2024 em espanhol&lt;/h2>
&lt;p alinhar="centro">
&lt;img src="https://media2.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%2Fsdkovc8lzahgm8pvsblk.png"/>
&lt;/p>
&lt;p>Este projeto foi inspirado na minha participação no &lt;strong>Calendario de Adviento de Inteligencia Artificial 2024 en Español&lt;/strong>, um evento online dedicado à IA. Você pode encontrar mais sobre o evento &lt;a href="https://dev.to/roberto_navarro_mate/calendario-de-adviento-de-inteligencia-artificial-2024-en-espanol-bdb">neste link Dev.to&lt;/a>.&lt;/p>
&lt;h2 id="o-projeto">O projeto&lt;/h2>
&lt;p>Para este projeto, usaremos o &lt;strong>Azure OpenAI&lt;/strong>, um serviço que nos permite utilizar modelos de IA poderosos, como o GPT-4, para processar e analisar imagens. O processo envolve várias etapas, desde a configuração do serviço API de back-end até a integração com um front-end do Blazor para uploads de imagens. Também usaremos o &lt;strong>.NET Aspire&lt;/strong>, um componente que ajuda a conectar tudo perfeitamente.&lt;/p>
&lt;h2 id="pré-requisitos">Pré-requisitos&lt;/h2>
&lt;p>Antes de mergulharmos no código, certifique-se de ter os seguintes pré-requisitos:&lt;/p>
&lt;p>-.NET 9&lt;/p>
&lt;ul>
&lt;li>Acesso Azure OpenAI (chave API)&lt;/li>
&lt;li>Visual Studio ou Código do Visual Studio&lt;/li>
&lt;li>Conhecimento básico de Blazor, clientes HTTP e desenvolvimento de API&lt;/li>
&lt;/ul>
&lt;h2 id="a-solução-visual-studio">A solução Visual Studio&lt;/h2>
&lt;p>Acabaremos tendo algo assim, gosto de manter as coisas separadas e com nomes legais então é assim:&lt;/p>
&lt;p alinhar="centro">
&lt;img src="https://imgur.com/gAnGLhM.png">
&lt;/p>
&lt;p>Mas vamos passo a passo criando coisas!&lt;/p>
&lt;h2 id="passo-0-os-modelos">Passo 0: Os modelos&lt;/h2>
&lt;p>O núcleo do aplicativo Receipt Scanner depende de vários modelos principais que facilitam a interação entre os serviços front-end, API e IA. Abaixo estão os principais modelos utilizados neste projeto:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>AnalyzeReceiptRequest&lt;/strong>&lt;br>
Este modelo representa a estrutura de solicitação para análise de um recibo. Ele contém a propriedade &lt;code>ImageBytes&lt;/code>, que contém a matriz de bytes da imagem do recibo que será processada.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">AnalyzeReceiptRequest&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">byte&lt;/span>[] ImageBytes { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>ReceiptAnalyzeResult&lt;/strong>&lt;br>
Este modelo captura o resultado após o processamento de um recibo. Contém os dados estruturados extraídos do recibo, como nome da loja, data, itens e valor total.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ReceiptAnalyzeResult&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime CreatedAt { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ReceiptData Result { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>Dados de Recibo&lt;/strong>&lt;br>
Este é o modelo que contém os dados estruturados do recebimento. Inclui propriedades para nome da loja, data, lista de itens (com nome e preço de cada item) e valor total no recibo.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ReceiptData&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Store { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime? Date { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;ReceiptItem&amp;gt; Items { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal?&lt;/span> Total { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>Item de recibo&lt;/strong>&lt;br>
Cada item do recibo é representado por este modelo. Ele contém o nome do item e seu preço.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ReceiptItem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal?&lt;/span> Price { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Esses modelos servem de &lt;span style="color:#ff7b72">base&lt;/span> para a passagem de dados entre o cliente e o servidor, garantindo um fluxo tranquilo de informações. A API recebe a imagem &lt;span style="color:#ff7b72">do&lt;/span> recibo e, em troca, processa e retorna um objeto JSON estruturado que pode ser facilmente consumido pelo front-end.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h2 id="etapa-1-configurando-o-serviço-api-de-back-end">Etapa 1: Configurando o serviço API de back-end&lt;/h2>
&lt;p>A primeira etapa na construção deste aplicativo é configurar um serviço API para analisar imagens de recibos. Usaremos a API &lt;strong>Azure OpenAI&lt;/strong> para extrair informações das imagens de recibo. Aqui está um resumo de como tudo se encaixa:&lt;/p>
&lt;h3 id="serviço-de-ia--um-mergulho-profundo">Serviço de IA – Um mergulho profundo&lt;/h3>
&lt;p>O serviço de IA está no centro do nosso sistema de análise de recibos. É responsável pela comunicação com a API do Azure OpenAI para processar os dados da imagem e retornar insights significativos. A classe &lt;strong>AiApiClient&lt;/strong> é o cliente que lidará com todas as interações com a API Azure OpenAI.&lt;/p>
&lt;h4 id="implementação-do-cliente-de-ia">Implementação do cliente de IA&lt;/h4>
&lt;p>O &lt;code>AiApiClient&lt;/code> é o principal componente responsável por enviar a imagem do recibo (em formato de matriz de bytes) para a API Azure OpenAI. Ele lida com a comunicação, registro de erros e análise de dados:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">AiApiClient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ILogger&amp;lt;AiApiClient&amp;gt; _logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> AiApiClient(HttpClient httpClient, ILogger&amp;lt;AiApiClient&amp;gt; logger)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger = logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ReceiptAnalyzeResult?&amp;gt; AnalyzeAsync(&lt;span style="color:#ff7b72">byte&lt;/span>[] imageBytes, CancellationToken cancellationToken = &lt;span style="color:#ff7b72">default&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (imageBytes == &lt;span style="color:#79c0ff">null&lt;/span> || imageBytes.Length == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;ImageBytes is null or empty.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Sending analyze request with image bytes of length: {Length}&amp;#34;&lt;/span>, imageBytes.Length);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> request = &lt;span style="color:#ff7b72">new&lt;/span> AnalyzeReceiptRequest
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ImageBytes = imageBytes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.PostAsJsonAsync(&lt;span style="color:#a5d6ff">&amp;#34;/analyze-receipt&amp;#34;&lt;/span>, request, cancellationToken);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!response.IsSuccessStatusCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;Failed to analyze receipt. StatusCode: {StatusCode}&amp;#34;&lt;/span>, response.StatusCode);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> analyzeResult = &lt;span style="color:#ff7b72">await&lt;/span> response.Content.ReadFromJsonAsync&amp;lt;ReceiptAnalyzeResult&amp;gt;(cancellationToken: cancellationToken);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (analyzeResult == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;No content received from AI API service.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Analysis result received: {AnalyzeResult}&amp;#34;&lt;/span>, analyzeResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> analyzeResult;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogError(ex, &lt;span style="color:#a5d6ff">&amp;#34;An error occurred while analyzing the receipt.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nesta parte do código, definimos o método &lt;code>AnalyzeAsync&lt;/code>, que é responsável por:&lt;/p>
&lt;ol>
&lt;li>Envio da matriz de bytes da imagem para a API Azure OpenAI.&lt;/li>
&lt;li>Tratamento de quaisquer erros ou respostas malsucedidas da API.&lt;/li>
&lt;li>Analisar os dados JSON retornados em um resultado estruturado (&lt;code>ReceiptAnalyzeResult&lt;/code>).&lt;/li>
&lt;/ol>
&lt;p>Os benefícios de separar esta funcionalidade num serviço dedicado (AiApiClient) incluem:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Tratamento de erros:&lt;/strong> Tratamento centralizado de erros, como problemas de rede ou respostas inválidas.&lt;/li>
&lt;li>&lt;strong>Registro:&lt;/strong> Registro adequado de solicitações e respostas para monitorar o comportamento do sistema.&lt;/li>
&lt;/ul>
&lt;p alinhar="centro">
&lt;img src="https://imgur.com/q3EpCSy.png"/>
&lt;/p>
&lt;h3 id="serviço-api---tratamento-de-solicitações-e-respostas">Serviço API - Tratamento de solicitações e respostas&lt;/h3>
&lt;p>O &lt;strong>Serviço de API&lt;/strong> atua como intermediário entre o aplicativo front-end Blazor e o serviço de IA. Este serviço é responsável por aceitar os dados da imagem, repassá-los ao serviço de IA e devolver os resultados da análise ao cliente.&lt;/p>
&lt;h4 id="ponto-final-da-api">Ponto final da API&lt;/h4>
&lt;p>Nesta etapa, definimos um endpoint de API simples para aceitar imagens de recebimento, encaminhá-las ao serviço de IA para processamento e retornar os resultados ao cliente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">ReceiptScanner.Shared.Clients&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">ReceiptScanner.Shared.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add service defaults &amp;amp; Aspire client integrations.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddServiceDefaults();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add services to the container.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddProblemDetails();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Register AiApiClient with HttpClient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddHttpClient&amp;lt;AiApiClient&amp;gt;(client =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.BaseAddress = &lt;span style="color:#ff7b72">new&lt;/span> Uri(&lt;span style="color:#a5d6ff">&amp;#34;https+http://aiservice&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddOpenApi();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the HTTP request pipeline.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseExceptionHandler();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (app.Environment.IsDevelopment())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.MapOpenApi();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// POST endpoint to analyze receipt&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/api/analyze-receipt&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AnalyzeReceiptRequest request, AiApiClient aiApiClient, ILogger&amp;lt;Program&amp;gt; logger) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (request.ImageBytes == &lt;span style="color:#79c0ff">null&lt;/span> || request.ImageBytes.Length == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;ImageBytes is null or empty.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#a5d6ff">&amp;#34;ImageBytes is required.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Received analyze receipt request with image bytes of length: {Length}&amp;#34;&lt;/span>, request.ImageBytes.Length);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> aiApiClient.AnalyzeAsync(request.ImageBytes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (result == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;Failed to analyze the receipt.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Problem(&lt;span style="color:#a5d6ff">&amp;#34;Unable to process the receipt at this time. Please try again later.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Analysis completed successfully. Result: {Result}&amp;#34;&lt;/span>, result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogError(ex, &lt;span style="color:#a5d6ff">&amp;#34;An error occurred while processing the receipt.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Problem(&lt;span style="color:#a5d6ff">&amp;#34;An error occurred while processing the receipt. Please try again later.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapDefaultEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Este ponto final:&lt;/p>
&lt;ol>
&lt;li>Aceita a imagem do recibo como parte do corpo da solicitação.&lt;/li>
&lt;li>Chama o método endopint &lt;code>AiService&lt;/code> para enviar a imagem ao Azure OpenAI para processamento.&lt;/li>
&lt;li>Devolve o resultado da análise ao cliente.&lt;/li>
&lt;/ol>
&lt;p alinhar="centro">
&lt;img src="https://imgur.com/u9mQrpq.png"/>
&lt;/p>
&lt;h2 id="etapa-2-configurando-o-front-end-do-blazor">Etapa 2: Configurando o front-end do Blazor&lt;/h2>
&lt;p>Agora que configuramos o backend, vamos voltar nossa atenção para o &lt;strong>frontend do Blazor&lt;/strong>. É aqui que os usuários podem fazer upload de imagens de recibos para análise e ver os resultados.&lt;/p>
&lt;h3 id="implementação-da-página-blazor">Implementação da página Blazor&lt;/h3>
&lt;p>A página Blazor fornece uma interface simples onde os usuários podem fazer upload de várias imagens de recibo e ver os resultados da análise exibidos em uma tabela. Aqui está o código da página:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/analyzer&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using ReceiptScanner&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Shared&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Clients
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using ReceiptScanner&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Shared&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using System&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Globalization
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject ApiServiceClient ApiClient
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject ILogger&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Program&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> Logger
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>attribute [StreamRendering]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>rendermode InteractiveServer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>PageTitle&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Receipt Analyzer&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>PageTitle&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1 &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;text-center my-4&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Receipt Analyzer&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;container&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;lead text-center mb-4&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Upload receipt images below to extract their data&lt;span style="color:#ff7b72;font-weight:bold">.&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!--&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">File&lt;/span> Upload Section &lt;span style="color:#ff7b72;font-weight:bold">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card mb-4&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-body&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>InputFile OnChange&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;HandleFileSelected&amp;#34;&lt;/span> multiple &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;form-control mb-3&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary w-100&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;ProcessReceipts&amp;#34;&lt;/span> disabled&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@(!hasFiles)&amp;#34;&lt;/span> type&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;button&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>span &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@(!processing ? &amp;#34;&lt;/span>d&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>none&lt;span style="color:#a5d6ff">&amp;#34; : &amp;#34;&amp;#34;) spinner-border spinner-border-sm&amp;#34;&lt;/span> role&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;status&amp;#34;&lt;/span> aria&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>hidden&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;lt;/&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (processing)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Processing&lt;span style="color:#ff7b72;font-weight:bold">...&amp;lt;/&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Process Receipts&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!--&lt;/span> Uploaded Images Preview &lt;span style="color:#ff7b72;font-weight:bold">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (fileBytesList&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card mb-4&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-header&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h5 &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;mb-0&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Uploaded Receipt Images&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h5&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-body&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;row&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> fileBytes &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> fileBytesList)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;col-12 col-md-4 mb-3&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>img src&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@($&amp;#34;&lt;/span>data:image&lt;span style="color:#ff7b72;font-weight:bold">/&lt;/span>jpeg;base64,{Convert&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToBase64String(fileBytes)}&lt;span style="color:#a5d6ff">&amp;#34;)&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;img-fluid rounded&amp;#34;&lt;/span> alt&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Uploaded receipt&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!--&lt;/span> Processing Indicator &lt;span style="color:#ff7b72;font-weight:bold">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (processing)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;alert alert-info text-center&amp;#34;&lt;/span> role&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Processing receipts&lt;span style="color:#ff7b72;font-weight:bold">...&amp;lt;/&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> Please wait &lt;span style="color:#ff7b72">while&lt;/span> we analyze the uploaded files&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!--&lt;/span> Analysis Results Section &lt;span style="color:#ff7b72;font-weight:bold">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (analyzedReceipts &lt;span style="color:#ff7b72;font-weight:bold">!=&lt;/span> null &lt;span style="color:#ff7b72;font-weight:bold">&amp;amp;&amp;amp;&lt;/span> analyzedReceipts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-header&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h5 &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;mb-0&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Analysis Results&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h5&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-body&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>table &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;table table-striped table-bordered&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Store&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Total&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Items&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> receipt &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> analyzedReceipts)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>(receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Store &lt;span style="color:#f85149">??&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Unknown&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>(receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Date&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString() &lt;span style="color:#f85149">??&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Unknown&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>(receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Total&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>, ci) &lt;span style="color:#f85149">??&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Unknown&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ul &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;list-unstyled&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Items is &lt;span style="color:#ff7b72;font-weight:bold">not&lt;/span> null)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> item &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Items&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>li&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;lt;&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>item&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Name&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span> &lt;span style="color:#f85149">@&lt;/span>item&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>, ci)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>li&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>ul&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>table&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (processed &lt;span style="color:#ff7b72;font-weight:bold">&amp;amp;&amp;amp;&lt;/span> (analyzedReceipts &lt;span style="color:#ff7b72;font-weight:bold">==&lt;/span> null &lt;span style="color:#ff7b72;font-weight:bold">||&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>analyzedReceipts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;alert alert-warning text-center&amp;#34;&lt;/span> role&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>No results found&lt;span style="color:#ff7b72;font-weight:bold">.&amp;lt;/&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> Please try again with different images &lt;span style="color:#ff7b72;font-weight:bold">or&lt;/span> ensure they are clear &lt;span style="color:#ff7b72;font-weight:bold">and&lt;/span> legible&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">bool&lt;/span> hasFiles;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">bool&lt;/span> processing;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">bool&lt;/span> processed;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>byte[]&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> fileBytesList &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ReceiptAnalyzeResult&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> analyzedReceipts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CultureInfo ci &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new CultureInfo(&lt;span style="color:#a5d6ff">&amp;#34;es-es&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task HandleFileSelected(InputFileChangeEventArgs e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> try
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fileBytesList&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> foreach (&lt;span style="color:#ff7b72">var&lt;/span> file &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> e&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetMultipleFiles())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> memoryStream &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new MemoryStream();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> await file&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>OpenReadStream()&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>CopyToAsync(memoryStream);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fileBytesList&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Add(memoryStream&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToArray());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hasFiles &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> fileBytesList&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> catch (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Logger&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>LogError(ex, &lt;span style="color:#a5d6ff">&amp;#34;Error while handling file upload.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task ProcessReceipts()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>hasFiles)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> processing &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> true;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analyzedReceipts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> try
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> foreach (&lt;span style="color:#ff7b72">var&lt;/span> fileBytes &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> fileBytesList)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ApiClient&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>AnalyzeReceiptAsync(fileBytes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (result &lt;span style="color:#ff7b72;font-weight:bold">!=&lt;/span> null)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analyzedReceipts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Add(result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> catch (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Logger&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>LogError(ex, &lt;span style="color:#a5d6ff">&amp;#34;Error while processing receipts.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> finally
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> processing &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> false;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> processed &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> true;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>Esta página permite aos usuários fazer upload de recibos e mostra os resultados da análise em uma tabela com nomes de lojas, datas, valores totais e lista de itens&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p alinhar&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;centro&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>img src&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;https://imgur.com/BLswKhm.png&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">## Etapa 3: .NET Aspire&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p alinhar&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;centro&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>img src&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;https://imgur.com/ja56RWN.png&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">### O que é o .NET Aspire?&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>O &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NET Aspire &lt;span style="color:#f85149">é&lt;/span> um conjunto de ferramentas, modelos e pacotes poderosos para criar aplicativos observáveis &lt;span style="color:#f85149">​​&lt;/span>e prontos para produção&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> O &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NET Aspire &lt;span style="color:#f85149">é&lt;/span> fornecido por meio de uma coleção de pacotes NuGet que tratam de questões específicas nativas da nuvem&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> Os aplicativos nativos da nuvem geralmente consistem em peças ou microsserviços pequenos e interconectados, em vez de uma base de código &lt;span style="color:#f85149">ú&lt;/span>nica e monolítica&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> Os aplicativos nativos da nuvem geralmente consomem um grande número de serviços, como bancos de dados, mensagens e cache&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> Para obter informações sobre suporte, consulte a Política de Suporte &lt;span style="color:#ff7b72">do&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NET Aspire&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Um aplicativo distribuído &lt;span style="color:#f85149">é&lt;/span> aquele que utiliza recursos computacionais em vários nós, como contêineres executados em hosts diferentes&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> Esses nós devem comunicar&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>se através dos limites da rede para entregar respostas aos usuários&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> Um aplicativo nativo da nuvem &lt;span style="color:#f85149">é&lt;/span> um tipo específico de aplicativo distribuído que aproveita ao máximo a escalabilidade, a resiliência e a capacidade de gerenciamento das infraestruturas em nuvem&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Usar o &lt;span style="color:#ff7b72;font-weight:bold">**.&lt;/span>NET Aspire&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span> para este projeto oferece vários benefícios que melhoram a qualidade geral &lt;span style="color:#ff7b72">do&lt;/span> sistema, como:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">### 1. **Registro centralizado**&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>O &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NET Aspire integra automaticamente o log em todo o aplicativo, o que significa que você não precisa configurar manualmente o log para cada serviço&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> Isso garante que os logs sejam consistentes e armazenados em um local centralizado, facilitando muito a depuração e o monitoramento&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Por exemplo, a classe &lt;span style="color:#f85149">`&lt;/span>AiApiClient&lt;span style="color:#f85149">`&lt;/span> usa log para registrar os bytes da imagem enviados ao serviço de IA, as respostas da API e quaisquer erros que ocorram durante o processo de análise&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>_logger&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Sending analyze request with image bytes of length: {Length}&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> imageBytes&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Length);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p alinhar="centro">
&lt;img src="https://imgur.com/5NS416X.png">
&lt;/p>
&lt;h3 id="2-coleta-automática-de-métricas">2. &lt;strong>Coleta automática de métricas&lt;/strong>&lt;/h3>
&lt;p>O .NET Aspire também rastreia e relata automaticamente métricas importantes do aplicativo, como tempos de resposta, contagens de solicitações e taxas de erro. Isso ajuda você a entender o desempenho do aplicativo e detectar rapidamente quaisquer gargalos ou problemas.&lt;/p>
&lt;p alinhar="centro">
&lt;img src="https://imgur.com/SGawOY3.png">
&lt;/p>
&lt;h3 id="3-desempenho-melhorado">3. &lt;strong>Desempenho melhorado&lt;/strong>&lt;/h3>
&lt;p>O .NET Aspire otimiza chamadas HTTP, o que ajuda a manter baixos os tempos de resposta e a reduzir o consumo desnecessário de recursos. Ele fornece recursos como pool de conexões, novas tentativas de solicitação e roteamento inteligente.&lt;/p>
&lt;h3 id="4-integração-perfeita">4. &lt;strong>Integração Perfeita&lt;/strong>&lt;/h3>
&lt;p>O .NET Aspire simplifica a integração de vários serviços (como os serviços de IA e API neste projeto) e agiliza o processo de implantação. Você não precisa se preocupar com configurações de baixo nível, pois o Aspire cuida das tarefas relacionadas à infraestrutura para você.&lt;/p>
&lt;p alinhar="centro">
&lt;img src="https://imgur.com/OSHhWVb.png">
&lt;/p>
&lt;h3 id="conclusãoia-não-é-mais-apenas-uma-palavra-da-moda-ou-algo-que-vemos-em-filmes-de-ficção-científica-atualmente-ele-está-resolvendo-ativamente-problemas-do-mundo-real-como-aquele-que-abordamos-neste-projeto-extrair-dados-estruturados-de-recibos-com-a-ajuda-do-azure-openai-do-net-aspire-e-do-blazor-podemos-automatizar-o-que-de-outra-forma-seria-uma-tarefa-manual-demorada-e-propensa-a-erros-a-ia-não-apenas-conversa-ou-responde-a-solicitações-como-o-chatgpt-ele-interpreta-imagens-extrai-informações-valiosas-e-nos-fornece-insights-acionáveis-em-segundos">ConclusãoIA não é mais apenas uma palavra da moda ou algo que vemos em filmes de ficção científica. Atualmente, ele está resolvendo ativamente problemas do mundo real, como aquele que abordamos neste projeto: extrair dados estruturados de recibos. Com a ajuda do &lt;strong>Azure OpenAI&lt;/strong>, do &lt;strong>.NET Aspire&lt;/strong> e do &lt;strong>Blazor&lt;/strong>, podemos automatizar o que de outra forma seria uma tarefa manual demorada e propensa a erros. A IA não apenas conversa ou responde a solicitações como o ChatGPT; ele interpreta imagens, extrai informações valiosas e nos fornece insights acionáveis ​​em segundos.&lt;/h3>
&lt;p>Ao usar o &lt;strong>Azure OpenAI&lt;/strong> para análise de recibos e o &lt;strong>.NET Aspire&lt;/strong> para integração perfeita com registros e métricas, criamos uma solução poderosa e escalonável. O potencial da IA ​​para agilizar processos de negócios, automatizar tarefas tediosas e melhorar a precisão é enorme, e este é apenas um exemplo de como pode ser aplicada.&lt;/p>
&lt;p>Esta postagem faz parte do &lt;strong>Calendario de Adviento de Inteligencia Artificial 2024 en Español&lt;/strong>, um evento que apresenta aplicações de IA do mundo real e educa a comunidade tecnológica de língua espanhola sobre as últimas tendências. Se você deseja se aprofundar na IA e em suas possibilidades, este evento é um ótimo lugar para começar.&lt;/p>
&lt;p>A IA está transformando a forma como trabalhamos e este projeto é apenas um vislumbre do que é possível. O verdadeiro poder da IA ​​está na sua capacidade de resolver problemas reais, seja processando recibos, analisando imagens ou prevendo tendências. Estamos apenas arranhando a superfície.&lt;/p>
&lt;h3 id="código-fonte">Código Fonte&lt;/h3>
&lt;p>O código-fonte completo deste projeto está disponível no &lt;a href="https://github.com/emimontesdeoca/ReceiptScannerPoc">GitHub&lt;/a>. Sinta-se à vontade para baixá-lo, explorar como os serviços de IA e API funcionam juntos e adaptá-lo para seus próprios casos de uso. Se você encontrar algum problema ou tiver ideias para melhorias, não hesite em criar um problema ou enviar uma solicitação pull. Contribuições são sempre bem-vindas e seus comentários ajudarão a tornar este projeto ainda melhor!&lt;/p>
&lt;p>Boa codificação!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category><category>Azure</category><category>NuGet</category><category>Docker</category><category>AI</category></item><item><title>Usando contêineres para... acompanhar os preços dos presentes de Natal?</title><link>https://emimontesdeoca.github.io/pt/posts/monitoring-prices-containers/</link><pubDate>Thu, 12 Dec 2024 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/monitoring-prices-containers/</guid><description>Automatize o monitoramento de preços de presentes de Natal usando contêineres Docker, Blazor e um back-end de API .NET.</description><content:encoded>&lt;p>O Natal está chegando e com ele vem a alegre tarefa de encontrar os presentes perfeitos para seus entes queridos. Se você for como eu, provavelmente adora fazer um bom negócio, mas navegar pelos preços exorbitantes de itens populares durante as festas de fim de ano pode parecer um passeio de trenó na vida real - emocionante, mas um pouco cansativo! Este ano, em vez de enlouquecer com verificações diárias de preços, decidi usar bem minhas habilidades tecnológicas e automatizar o processo.&lt;/p>
&lt;p alinhar="centro">
&lt;img src="https://imgur.com/7K7QC08.png" />
&lt;/p>
&lt;p>Como desenvolvedor de software e apaixonado por aproveitar a tecnologia para resolver problemas do dia a dia, encontrei uma maneira de automatizar o processo de verificação de preços. Em vez de visitar manualmente várias lojas online todos os dias para comparar preços, decidi criar um sistema que possa fazer isso por mim. Isso não apenas economiza tempo, mas também garante que eu obtenha as melhores ofertas sem o estresse das verificações diárias.&lt;/p>
&lt;h2 id="por-que-automatizar-o-monitoramento-de-preços">Por que automatizar o monitoramento de preços?&lt;/h2>
&lt;p>Automatizar o monitoramento de preços é como ter sua própria equipe de duendes do Papai Noel trabalhando 24 horas por dia para garantir que você obtenha as melhores ofertas sem mexer um dedo. Veja por que você vai adorar:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Itens caros&lt;/strong>: Todos nós sabemos que os presentes de Natal podem ficar muito caros, especialmente quando gadgets e brinquedos estão em sua lista. Economizar dinheiro com isso pode ser tão satisfatório quanto encontrar a última árvore em estoque.&lt;/li>
&lt;li>&lt;strong>Verificações Diárias&lt;/strong>: Quem tem tempo (ou paciência) para verificar preços todos os dias? Eu não!&lt;/li>
&lt;li>&lt;strong>Automação&lt;/strong>: abrace o espírito natalino de doação – dê a si mesmo o presente da eficiência.&lt;/li>
&lt;li>&lt;strong>Precisão&lt;/strong>: A automação garante que suas verificações de preços sejam tão precisas quanto o nariz de Rudolph é brilhante.&lt;/li>
&lt;/ol>
&lt;h2 id="tecnologias-principais">Tecnologias principais&lt;/h2>
&lt;p>Para construir meu próprio ajudante do Papai Noel, decidi usar várias tecnologias importantes:&lt;/p>
&lt;h3 id="contêineres">Contêineres&lt;/h3>
&lt;p>Os contêineres são como o embrulho de Natal do software. Eles agrupam seus aplicativos com todas as suas vantagens (dependências) para que tudo funcione tão bem quanto um passeio de trenó na neve fresca. Docker é nossa escolha para criar esses contêineres.&lt;/p>
&lt;h3 id="blazor">Blazor&lt;/h3>
&lt;p>Blazor é uma estrutura interessante para construir aplicativos da web interativos usando .NET. É como substituir canções de Natal genéricas pela sua própria lista de reprodução de férias - personalizada, eficiente e muito divertida.&lt;/p>
&lt;h3 id="docker-compose">Docker-Compose&lt;/h3>
&lt;p>Docker-Compose é o gestor da operação do nosso Pólo Norte. Isso nos ajuda a manter todos os nossos serviços – como a API e o front-end do Blazor – funcionando juntos em perfeita harmonia. Pense nele como o maestro da nossa sinfonia natalina.&lt;/p>
&lt;h2 id="guia-passo-a-passo">Guia passo a passo&lt;/h2>
&lt;p>Agora vamos mergulhar na oficina do Papai Noel e dar vida a esse projeto!&lt;/p>
&lt;h3 id="etapa-1-criando-a-api">Etapa 1: Criando a API&lt;/h3>
&lt;h4 id="11-configurando-o-projeto">1.1 Configurando o Projeto&lt;/h4>
&lt;p>Coloque seu chapéu de Papai Noel de codificação e configure um novo projeto de API Web ASP.NET Core. Abra seu terminal (é como abrir seu calendário do advento) e execute:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new webapi -o PriceMonitorApi
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd PriceMonitorApi
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Este comando cria um novo diretório chamado &lt;code>PriceMonitorApi&lt;/code> e configura um projeto básico de API da Web. Imagine que é como criar uma base robusta para a sua casa de pão de gengibre.&lt;/p>
&lt;h4 id="12-adicionando-bibliotecas-httpclient-e-scrapingem-seguida-adicione-httpclient-e-uma-biblioteca-para-analisar-html-este-será-nosso-trenó-confiável-para-buscar-e-ler-dados-de-preços">1.2 Adicionando bibliotecas HttpClient e ScrapingEm seguida, adicione &lt;code>HttpClient&lt;/code> e uma biblioteca para analisar HTML. Este será nosso trenó confiável para buscar e ler dados de preços.&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package HtmlAgilityPack
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>HtmlAgilityPack é o elfo que nos ajuda a analisar documentos HTML.&lt;/p>
&lt;h4 id="13-criando-modelos">1.3 Criando Modelos&lt;/h4>
&lt;p>Os modelos são como o modelo dos nossos brinquedos. Vamos criar um modelo para representar as informações do produto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Models&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ProductInfo&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Title { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> Price { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime Date { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Acabamos de criar o modelo perfeito para nossos dados.&lt;/p>
&lt;h4 id="14-implementando-o-serviço-scraper">1.4 Implementando o serviço Scraper&lt;/h4>
&lt;p>Nosso serviço de scraper é como o ajudante do Papai Noel – buscando e processando informações para nós:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">HtmlAgilityPack&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Globalization&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ScraperService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ScraperService(HttpClient httpClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ProductInfo&amp;gt; ScrapeProductInfoAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.GetStringAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> htmlDoc = &lt;span style="color:#ff7b72">new&lt;/span> HtmlDocument();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> htmlDoc.LoadHtml(response);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> title = htmlDoc.DocumentNode.SelectSingleNode(&lt;span style="color:#a5d6ff">&amp;#34;//h1[@class=&amp;#39;product-title&amp;#39;]&amp;#34;&lt;/span>).InnerText.Trim();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> priceString = htmlDoc.DocumentNode.SelectSingleNode(&lt;span style="color:#a5d6ff">&amp;#34;//span[@class=&amp;#39;product-price&amp;#39;]&amp;#34;&lt;/span>).InnerText.Trim();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">decimal&lt;/span>.TryParse(priceString, NumberStyles.Currency, CultureInfo.InvariantCulture, &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> price))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ProductInfo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Title = title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Price = price,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Date = DateTime.UtcNow
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Unable to parse price&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Este trecho transforma nosso &lt;code>HtmlAgilityPack&lt;/code> em uma varinha mágica de férias.&lt;/p>
&lt;h4 id="15-criando-o-controlador-de-api">1.5 Criando o controlador de API&lt;/h4>
&lt;p>Vamos criar um controlador que atuará como o guardião de nossos dados:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Services&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Controllers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ApiController]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;api/[controller]&lt;span style="color:#a5d6ff">&amp;#34;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ScraperController&lt;/span> : ControllerBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ScraperService _scraperService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ScraperController(ScraperService scraperService)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _scraperService = scraperService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet(&amp;#34;productinfo&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ActionResult&amp;lt;ProductInfo&amp;gt;&amp;gt; GetProductInfo(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> productInfo = &lt;span style="color:#ff7b72">await&lt;/span> _scraperService.ScrapeProductInfoAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Ok(productInfo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Message = ex.Message });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nosso controlador garante que os dados sejam entregues mais rápido do que o Papai Noel pela chaminé.&lt;/p>
&lt;h4 id="16-registrando-serviços-no-contêiner-di">1.6 Registrando serviços no contêiner DI&lt;/h4>
&lt;p>Por último, registre seu &lt;code>ScraperService&lt;/code> para garantir que ele esteja disponível quando necessário.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>services.AddHttpClient&amp;lt;ScraperService&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora sua API está pronta, como o trenó do Papai Noel na véspera de Natal!&lt;/p>
&lt;hr>
&lt;h3 id="etapa-2-criando-o-aplicativo-blazor">Etapa 2: Criando o aplicativo Blazor&lt;/h3>
&lt;p>Blazor nos ajuda a decorar nosso projeto como uma árvore de Natal – tornando-o visualmente atraente e interativo.&lt;/p>
&lt;h4 id="21-configurando-o-projeto-blazor">2.1 Configurando o projeto Blazor&lt;/h4>
&lt;p>A seguir, criaremos um projeto Blazor que atua como interface para nosso passeio de trenó de monitoramento de preços.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new blazor -o PriceMonitorBlazor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd PriceMonitorBlazor
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Este comando espalha um pouco da magia do feriado para configurar um projeto básico do Blazor WebAssembly.&lt;/p>
&lt;h4 id="22-adicionando-modelos">2.2 Adicionando modelos&lt;/h4>
&lt;p>Assim como configurar enfeites, adicione um modelo &lt;code>ProductInfo&lt;/code> no projeto Blazor:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Models&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ProductInfo&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Title { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> Price { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime Date { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="23-criando-o-serviço-para-chamadas-de-api">2.3 Criando o serviço para chamadas de API&lt;/h4>
&lt;p>Crie um serviço para buscar dados de nossa API – pense nele como nosso companheiro de compras online:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net.Http&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net.Http.Json&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Threading.Tasks&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ScraperService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ScraperService(HttpClient httpClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ProductInfo&amp;gt; GetProductInfoAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.GetFromJsonAsync&amp;lt;ProductInfo&amp;gt;(&lt;span style="color:#a5d6ff">$&amp;#34;https://localhost:5001/api/scraper/productinfo?url={url}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="24-registrando-serviços-no-di-container">2.4 Registrando serviços no DI Container&lt;/h4>
&lt;p>Certifique-se de que &lt;code>ScraperService&lt;/code> esteja registrado para que possamos injetá-lo em nossos componentes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;ScraperService&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="25-criando-o-componente-blazor">2.5 Criando o componente Blazor&lt;/h3>
&lt;p>Atualize &lt;code>Pages/Index.razor&lt;/code> para incluir uma interface divertida e interativa:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using PriceMonitorBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using PriceMonitorBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Services
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject ScraperService ScraperService
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Price Monitor&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>label &lt;span style="color:#ff7b72">for&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;urlInput&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Product URL:&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>label&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>input id&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;urlInput&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>bind&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;productUrl&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;FetchProductInfo&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Fetch Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>table &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;table&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Title&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> product &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> productInfos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Title&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>table&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private string productUrl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ProductInfo&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> productInfos &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ProductInfo&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task FetchProductInfo()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>string&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>IsNullOrEmpty(productUrl))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> productInfo &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ScraperService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetProductInfoAsync(productUrl);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Add(productInfo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É como montar uma exibição festiva de feriado – tornando nosso aplicativo interativo e agradável.&lt;/p>
&lt;hr>
&lt;h3 id="etapa-3-conectando-tudo-usando-docker-compose">Etapa 3: Conectando tudo usando Docker-Compose&lt;/h3>
&lt;p>Agora vamos conectar tudo usando Docker-Compose, transformando nosso projeto em um passeio de trenó bem lubrificado.&lt;/p>
&lt;h4 id="31-criando-arquivos-docker">3.1 Criando arquivos Docker&lt;/h4>
&lt;p>Crie Dockerfiles para os projetos API e Blazor:&lt;/p>
&lt;p>&lt;strong>PriceMonitorApi/Dockerfile&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> mcr.microsoft.com/dotnet/aspnet:6.0 AS base&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /app&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">EXPOSE&lt;/span>&lt;span style="color:#a5d6ff"> 80&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">EXPOSE&lt;/span>&lt;span style="color:#a5d6ff"> 443&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> mcr.microsoft.com/dotnet/sdk:6.0 AS build&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /src&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">[&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi/PriceMonitorApi.csproj&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi/&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">]&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet restore &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi/PriceMonitorApi.csproj&amp;#34;&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> . .&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> &amp;#34;/src/PriceMonitorApi&amp;#34;&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet build &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi.csproj&amp;#34;&lt;/span> -c Release -o /app/build&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> build AS publish&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet publish &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi.csproj&amp;#34;&lt;/span> -c Release -o /app/publish&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> base AS final&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /app&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> --from&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>publish /app/publish .&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">ENTRYPOINT&lt;/span> [&lt;span style="color:#a5d6ff">&amp;#34;dotnet&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi.dll&amp;#34;&lt;/span>]&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>PriceMonitorBlazor/Dockerfile&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> mcr.microsoft.com/dotnet/aspnet:6.0 AS base&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /app&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">EXPOSE&lt;/span>&lt;span style="color:#a5d6ff"> 80&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">EXPOSE&lt;/span>&lt;span style="color:#a5d6ff"> 443&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> mcr.microsoft.com/dotnet/sdk:6.0 AS build&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /src&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">[&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor/PriceMonitorBlazor.csproj&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor/&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">]&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet restore &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor/PriceMonitorBlazor.csproj&amp;#34;&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> . .&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> &amp;#34;/src/PriceMonitorBlazor&amp;#34;&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet build &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor.csproj&amp;#34;&lt;/span> -c Release -o /app/build&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> build AS publish&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet publish &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor.csproj&amp;#34;&lt;/span> -c Release -o /app/publish&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> base AS final&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /app&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> --from&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>publish /app/publish .&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">ENTRYPOINT&lt;/span> [&lt;span style="color:#a5d6ff">&amp;#34;dotnet&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor.dll&amp;#34;&lt;/span>]&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="32-criando-docker-composeyml">3.2 Criando docker-compose.yml&lt;/h4>
&lt;p>Conecte todos os pontos (luzes de Natal) com um único arquivo &lt;code>docker-compose.yml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">version&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#39;3.4&amp;#39;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">services&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">api&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">image&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">pricemonitorapi&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">build&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">context&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">./PriceMonitorApi&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">dockerfile&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Dockerfile&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">ports&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">&amp;#34;5000:80&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">networks&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">price-monitor-network&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">blazor&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">image&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">pricemonitorblazor&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">build&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">context&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">./PriceMonitorBlazor&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">dockerfile&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Dockerfile&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">ports&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">&amp;#34;5001:80&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">depends_on&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">api&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">networks&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">price-monitor-network&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">networks&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">price-monitor-network&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">driver&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">bridge&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="diagramas">Diagramas&lt;/h3>
&lt;p>Abaixo estão os diagramas para ajudar a visualizar os diferentes componentes e o fluxo de dados, isso nos ajudará a entender o que realmente está acontecendo em nosso mundo papai noel:&lt;/p>
&lt;h4 id="diagrama-de-arquitetura-do-sistema">Diagrama de Arquitetura do Sistema&lt;/h4>
&lt;p>&lt;img src="https://imgur.com/nE0hSJ4.png" alt="Imgur">&lt;/p>
&lt;h4 id="diagrama-de-fluxo-de-dados">Diagrama de Fluxo de Dados&lt;/h4>
&lt;p>&lt;img src="https://imgur.com/hJXv4Jw.png" alt="Imgur">&lt;/p>
&lt;h4 id="diagrama-de-sequência">Diagrama de sequência&lt;/h4>
&lt;p>&lt;img src="https://imgur.com/FH6VzuH.png" alt="Imgur">&lt;/p>
&lt;h4 id="diagrama-de-componentes-para-configuração-do-docker">Diagrama de componentes para configuração do Docker&lt;/h4>
&lt;p>&lt;img src="https://imgur.com/efQ9WuT.png" alt="Imgur">&lt;/p>
&lt;h3 id="etapa-4-gerenciamento-de-url-atualização-e-atualização-periódica-automatizadanesta-demonstração-iremos-apenas-armazená-lo-na-memória-mas-em-uma-aplicação-real-você-usaria-um-banco-de-dados-para-este-projeto-divertido-vamos-nos-limitar-ao-armazenamento-na-memória">Etapa 4: gerenciamento de URL, atualização e atualização periódica automatizadaNesta demonstração iremos apenas armazená-lo na memória, mas em uma aplicação real, você usaria um banco de dados. Para este projeto divertido, vamos nos limitar ao armazenamento na memória.&lt;/h3>
&lt;h4 id="41-modificando-o-serviço-para-gerenciar-urls">4.1 Modificando o serviço para gerenciar URLs&lt;/h4>
&lt;p>Primeiro, garantiremos que nosso serviço possa adicionar e recuperar URLs, bem como buscar informações de produtos:&lt;/p>
&lt;p>Atualização &lt;code>Services/ScraperService.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Collections.Generic&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net.Http&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net.Http.Json&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Threading.Tasks&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ScraperService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; urls = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ScraperService(HttpClient httpClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ProductInfo&amp;gt; GetProductInfoAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.GetFromJsonAsync&amp;lt;ProductInfo&amp;gt;(&lt;span style="color:#a5d6ff">$&amp;#34;http://localhost:5000/api/scraper/productinfo?url={url}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> AddUrl(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!urls.Contains(url))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> urls.Add(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetUrls()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> urls;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ClearUrls()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> urls.Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="42-atualizando-o-componente-blazor-para-gerenciamento-e-atualização-de-url">4.2 Atualizando o componente Blazor para gerenciamento e atualização de URL&lt;/h4>
&lt;p>Em seguida, atualize &lt;code>Pages/Index.razor&lt;/code> para adicionar gerenciamento de URL, atualização e atualização periódica automatizada:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using PriceMonitorBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using PriceMonitorBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Services
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject ScraperService ScraperService
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Price Monitor&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>label &lt;span style="color:#ff7b72">for&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;urlInput&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Product URL:&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>label&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>input id&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;urlInput&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>bind&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;productUrl&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;AddUrl&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Add URL&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;RefreshPrices&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Refresh All Prices&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>table &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;table&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Title&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> product &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> productInfos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Title&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>table&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private string productUrl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ProductInfo&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> productInfos &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ProductInfo&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">Timer&lt;/span> _timer;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override void OnInitialized()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StartTimer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void StartTimer()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">//&lt;/span> Set up the timer to call RefreshPrices every minute (&lt;span style="color:#a5d6ff">60000&lt;/span> ms)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _timer &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new &lt;span style="color:#f0883e;font-weight:bold">Timer&lt;/span>(async _ &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> await InvokeAsync(RefreshPrices), null, TimeSpan&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Zero, TimeSpan&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>FromMinutes(&lt;span style="color:#a5d6ff">1&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void AddUrl()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>string&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>IsNullOrEmpty(productUrl))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ScraperService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>AddUrl(productUrl);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> productUrl &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> string&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task RefreshPrices()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> urls &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> ScraperService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetUrls();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> foreach (&lt;span style="color:#ff7b72">var&lt;/span> url &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> urls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> productInfo &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ScraperService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetProductInfoAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Add(productInfo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Neste componente atualizado:&lt;/p>
&lt;ul>
&lt;li>Usamos um &lt;code>Timer&lt;/code> para chamar periodicamente o método &lt;code>RefreshPrices&lt;/code> a cada minuto.&lt;/li>
&lt;li>O método &lt;code>StartTimer&lt;/code> inicializa o cronômetro para iniciar imediatamente e dispara a cada 60 segundos.&lt;/li>
&lt;li>O método de ciclo de vida &lt;code>OnInitialized&lt;/code> chama &lt;code>StartTimer&lt;/code> quando o componente é inicializado para iniciar a atualização periódica.&lt;/li>
&lt;/ul>
&lt;h4 id="43-executando-a-solução">4.3 Executando a solução&lt;/h4>
&lt;p>Para executar o aplicativo Blazor atualizado com os novos recursos, recompile e reinicie os contêineres do Docker:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>docker-compose build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker-compose up
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois de carregar &lt;code>http://localhost:5001&lt;/code> em seu navegador, o aplicativo Blazor agora deve atualizar automaticamente os preços dos produtos a cada minuto, além de permitir atualizações manuais e gerenciamento de URL.&lt;/p>
&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>Construir este sistema de monitoramento de preços foi uma festa festiva! Isso não apenas me salvou do estresse das verificações diárias de preços, mas também mostrou a magia das modernas tecnologias da web.&lt;/p>
&lt;h1 id="calendário-festivo-de-tecnologia-2024">Calendário Festivo de Tecnologia 2024&lt;/h1>
&lt;p alinhar="centro">
&lt;img src="https://festivetechcalendar.com/assets/images/Heading.png" />
&lt;/p>
&lt;p>Criei esta postagem como parte do evento &lt;strong>Festive Tech Calendar 2024&lt;/strong>, que reúne entusiastas da tecnologia, inovadores e sonhadores digitais para compartilhar conhecimento e celebrar a fusão do espírito festivo e das maravilhas tecnológicas. Esta iniciativa não visa apenas aprender e conectar-se, mas também retribuir.&lt;/p>
&lt;p>&lt;strong>Festive Tech Calendar 2024&lt;/strong> está apoiando a Beatson Cancer Charity este ano. A Beatson Cancer Charity dedica-se a apoiar as pessoas afetadas pelo câncer, suas famílias e os profissionais de saúde que cuidam delas. Mais informações sobre seu trabalho incrível podem ser encontradas em &lt;a href="https://www.beatsoncancercharity.org/">https://www.beatsoncancercharity.org/&lt;/a>.&lt;/p>
&lt;p>Confira o site do Festive Tech Calendar em &lt;a href="https://festivetechcalendar.com">https://festivetechcalendar.com&lt;/a> para perguntas frequentes e mais detalhes sobre o evento.&lt;/p>
&lt;h1 id="ho-ho-ho">&lt;strong>HO HO HO!&lt;/strong>&lt;/h1>
&lt;p>Criar este projeto foi uma maneira maravilhosa de contribuir para a festiva comunidade tecnológica e, ao mesmo tempo, apoiar uma grande causa.&lt;/p>
&lt;p>Espero que você tenha achado este guia útil e que o inspire a explorar mais maneiras de usar a tecnologia para simplificar as tarefas diárias.&lt;/p>
&lt;p>Se você tiver alguma dúvida ou precisar de mais assistência, não hesite em entrar em contato.&lt;/p>
&lt;p>Boa codificação!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category><category>Docker</category><category>API</category></item><item><title>ValidationAttribute personalizado e validação Blazor</title><link>https://emimontesdeoca.github.io/pt/posts/custom-validationattribute-blazor/</link><pubDate>Fri, 29 Mar 2024 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/custom-validationattribute-blazor/</guid><description>Crie classes ValidationAttribute personalizadas reutilizáveis ​​para validação de formulário Blazor com anotações de dados.</description><content:encoded>&lt;p>﻿# Personalize tudo&lt;/p>
&lt;p>Como você provavelmente já viu em todos os meus posts, eu realmente tento manter tudo o mais limpo possível, pois já escrevi posts sobre atributos customizados, tratamento de exceções customizadas, injeção de coleção de serviços, etc.&lt;/p>
&lt;p>Com o tempo, percebi que esse tipo de codificação dá a mim e à minha equipe uma maneira de melhorar as horas extras, encontrar problemas com mais facilidade e separar o código o máximo possível.&lt;/p>
&lt;p>Sim, depois dessa história legal que acabei de contar, tenho trabalhado no Blazor ultimamente como de costume e descobri que, depois de anos e anos de desenvolvimento, você pode criar atributos de validação personalizados.&lt;/p>
&lt;p>Sim, é engraçado, depois de todos esses anos&amp;hellip;&lt;/p>
&lt;h1 id="atributos-de-validação-personalizados">Atributos de validação personalizados&lt;/h1>
&lt;p>A ideia veio do trabalho, na verdade, sempre fazemos validação em todos os lugares, mas eu tinha alguns campos que exigiam o mesmo processo de validação, então pensei que poderia haver algo lá&amp;hellip; como atributos de validação customizados!&lt;/p>
&lt;p>Então, abri a documentação da Microsoft para isso e descobri que sim, você pode criar atributos de validação personalizados e atribuí-los às propriedades, assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">StringLengthRangeAttribute&lt;/span> : ValidationAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Minimum { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Maximum { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> StringLengthRangeAttribute()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.Minimum = &lt;span style="color:#a5d6ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.Maximum = &lt;span style="color:#ff7b72">int&lt;/span>.MaxValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> IsValid(&lt;span style="color:#ff7b72">object&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> strValue = &lt;span style="color:#ff7b72">value&lt;/span> &lt;span style="color:#ff7b72">as&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(strValue))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span> len = strValue.Length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> len &amp;gt;= &lt;span style="color:#ff7b72">this&lt;/span>.Minimum &amp;amp;&amp;amp; len &amp;lt;= &lt;span style="color:#ff7b72">this&lt;/span>.Maximum;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>e use-o em uma classe simples como esta:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Required]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[StringLengthRange(Minimum = 10, ErrorMessage = &amp;#34;Must be &amp;gt;10 characters.&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[StringLengthRange(Maximum = 20)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Required]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[StringLengthRange(Minimum = 10, Maximum = 20)]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="validador-personalizado">Validador personalizado&lt;/h1>
&lt;p>Então eu tenho esse validador que preciso para algum caso de negócio específico que contará 20 primeiros caracteres que serão 9 números e um hífen, e terminarão com 2 caracteres que normalmente serão o código do país, então algo assim: &lt;strong>123456789-123456789-ES&lt;/strong>&lt;/p>
&lt;p>Acabei chegando com algo assim, é muito simples, mas funciona:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.ComponentModel.DataAnnotations&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text.RegularExpressions&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">SpecialStringValidatorAttribute&lt;/span> : ValidationAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">const&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> TotalLength = &lt;span style="color:#a5d6ff">22&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">const&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Pattern = &lt;span style="color:#a5d6ff">@&amp;#34;^(\d{10})-(\d{10})-([A-Za-z]{2})$&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> ValidationResult IsValid(&lt;span style="color:#ff7b72">object&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>, ValidationContext validationContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> strValue = &lt;span style="color:#ff7b72">value&lt;/span> &lt;span style="color:#ff7b72">as&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(strValue))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (strValue.Length != TotalLength)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ValidationResult(&lt;span style="color:#a5d6ff">$&amp;#34;The string must be {TotalLength} characters long.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!Regex.IsMatch(strValue, Pattern))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ValidationResult(&lt;span style="color:#a5d6ff">&amp;#34;The string must follow the pattern: 1234567890-1234567890-AB&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ValidationResult.Success;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ValidationResult(&lt;span style="color:#a5d6ff">&amp;#34;The string cannot be null or empty.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="testes">Testes&lt;/h2>
&lt;p>Eu escrevi alguns testes para eles também, só para garantir:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">SpecialStringValidatorTests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Theory]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-AB&amp;#34;, true)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-XY&amp;#34;, true)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-A1&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-A&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-123456789-AB&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-ABCDE&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> SpecialStringValidatorTest(&lt;span style="color:#ff7b72">string&lt;/span> input, &lt;span style="color:#ff7b72">bool&lt;/span> expectedResult)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Arrange&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> validator = &lt;span style="color:#ff7b72">new&lt;/span> SpecialStringValidatorAttribute();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Act&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = validator.IsValid(input);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.Equal(expectedResult, result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E quando executei tive estes resultados:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>Microsoft &lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>R&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span> Test Execution Command Line Tool Version 16.9.1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Copyright &lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>c&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span> Microsoft Corporation. All rights reserved.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Starting test execution, please wait...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>A total of &lt;span style="color:#a5d6ff">1&lt;/span> test files matched the specified pattern.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Test run in progress...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-AB&amp;#34;&lt;/span>, expectedResult: True&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-XY&amp;#34;&lt;/span>, expectedResult: True&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-A1&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-A&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-123456789-AB&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-ABCDE&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Test Run Successful.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Total tests: &lt;span style="color:#a5d6ff">7&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Passed: &lt;span style="color:#a5d6ff">7&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Total time: 1.7296 Seconds
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="validação-personalizada-e-blazor">Validação personalizada e Blazor&lt;/h1>
&lt;p>Então agora que sei que o dele pode ser usado, é claramente uma boa ideia implementá-lo no Blazor, certo?&lt;/p>
&lt;p>Vamos supor que eu tenha este formulário, que usará o modelo &lt;code>Person&lt;/code> que mostrei antes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@using Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;PageTitle&amp;gt;Home&amp;lt;/PageTitle&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;EditForm Model=@Person FormName=&lt;span style="color:#a5d6ff">&amp;#34;PersonForm&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;DataAnnotationsValidator/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationSummary/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-group&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Name&amp;#34;&lt;/span>&amp;gt;Name&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputText @bind-Value=Person.Name class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;Name&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&lt;span style="color:#a5d6ff">&amp;#34;() =&amp;gt; Person.Name&amp;#34;&lt;/span>/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-group&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;MySpecialString&amp;#34;&lt;/span>&amp;gt;My special &lt;span style="color:#ff7b72">string&lt;/span>&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputText @bind-Value=Person.MySpecialString class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;Name&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&lt;span style="color:#a5d6ff">&amp;#34;() =&amp;gt; Person.MySpecialString&amp;#34;&lt;/span>/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-group&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Age&amp;#34;&lt;/span>&amp;gt;Age&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputNumber @bind-Value=Person.Age class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;Age&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&lt;span style="color:#f85149">@&lt;/span>(() =&amp;gt; Person.Age) /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Save&amp;#34;&lt;/span>/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/EditForm&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Person Person = &lt;span style="color:#ff7b72">new&lt;/span> Person();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Assim que executamos isso, obtemos os seguintes erros:&lt;/p>
&lt;img src="https://i.imgur.com/psfpzdr.png">
&lt;p>E se colocarmos apenas o que queremos, obtemos o seguinte, tudo claro:&lt;/p>
&lt;img src="https://i.imgur.com/RPoUq02.png">
&lt;p>Para ser honesto, está bastante claro que devemos mover a lógica pelo menos para validar esses formulários em atributos de validação personalizados, isso nos dá liberdade para armazenar o código desse login em um único local e podemos usá-lo posteriormente para uma API ou outro aplicativo.&lt;/p>
&lt;p>Espero que tenham gostado, se tiver alguma dúvida ou quiser entrar em contato, não hesite e entre em contato comigo!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category></item><item><title>Tratamento de exceções personalizado na API .NET</title><link>https://emimontesdeoca.github.io/pt/posts/custom-exception-handler-api/</link><pubDate>Sun, 01 Oct 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/custom-exception-handler-api/</guid><description>Crie um middleware personalizado de tratamento de exceções para retornar respostas de erro limpas de APIs .NET.</description><content:encoded>&lt;p>﻿Exceções são ruins, nós sabemos, certo? Mas e se tivermos que lidar com eles?&lt;/p>
&lt;p>O que acontece quando temos uma exceção, por exemplo, em uma API, ela exibe uma mensagem na pilha que inclui muitas informações que podemos querer remover da resposta que nossos usuários recebem.&lt;/p>
&lt;p>Para a demonstração, criei uma API dotnet e adicionei um método que lançará uma exceção:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Route(&amp;#34;GetWithoutExceptionHandler&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> Task GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;This is a custom exception!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Se lançarmos uma exceção, ficará assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>System.Exception: This &lt;span style="color:#ff7b72">is&lt;/span> a custom exception!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at CustomExceptionHandleDemo.Controllers.WeatherForecastController.GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at lambda_method16(Closure , Object , Object[] )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&amp;lt;InvokeActionMethodAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">12_1&lt;/span>(ControllerActionInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&amp;lt;InvokeNextActionFilterAsync&amp;gt;g__Awaited|&lt;span style="color:#a5d6ff">10_0&lt;/span>(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State&amp;amp; next, Scope&amp;amp; scope, Object&amp;amp; state, Boolean&amp;amp; isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--- End of stack trace &lt;span style="color:#ff7b72">from&lt;/span> previous location ---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeFilterPipelineAsync&amp;gt;g__Awaited|&lt;span style="color:#a5d6ff">20_0&lt;/span>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">17_1&lt;/span>(ResourceInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">17_1&lt;/span>(ResourceInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Routing.EndpointMiddleware.&amp;lt;Invoke&amp;gt;g__AwaitRequestTask|&lt;span style="color:#a5d6ff">6_0&lt;/span>(Endpoint endpoint, Task requestTask, ILogger logger)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso parece normal, é isso que você obtém de uma exceção, mas para mim está exibindo muitas informações, se você tiver bibliotecas e outras coisas, pode mostrar informações sensatas sobre seu cliente, seu projeto ou outras coisas para alguém que possa estar tentando ver as coisas.&lt;/p>
&lt;p>Isto é o que parece no Swagger:&lt;/p>
&lt;img src="https://imgur.com/fUujR3s.png"/>
&lt;h3 id="criando-uma-exceção-personalizada">Criando uma exceção personalizada&lt;/h3>
&lt;p>Esta etapa poderia ser evitada, pois sabemos qual exceção será lançada, neste caso um &lt;code>Exception&lt;/code>, mas para mim, ter suas exceções personalizadas é melhor, pois você tem mais controle do que está lançando.&lt;/p>
&lt;p>Neste caso, acabei de criar um objeto &lt;code>CustomException&lt;/code> que herda de &lt;code>Exception&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.Exceptions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CustomException&lt;/span> : Exception
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;CustomException&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CustomException() { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;CustomException&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;string&amp;#34; name=&amp;#34;message&amp;#34;&amp;gt;Parameter for message&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CustomException(&lt;span style="color:#ff7b72">string&lt;/span> message) : &lt;span style="color:#ff7b72">base&lt;/span>(message) { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;CustomException&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;string&amp;#34; name=&amp;#34;message&amp;#34;&amp;gt;Parameter for message&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;Exception&amp;#34; name=&amp;#34;inner&amp;#34;&amp;gt;Parameter for inner&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CustomException(&lt;span style="color:#ff7b72">string&lt;/span> message, Exception inner) : &lt;span style="color:#ff7b72">base&lt;/span>(message, inner) { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois de criarmos nossa exceção personalizada, vamos atualizar nosso método para lançar &lt;code>CustomException&lt;/code> em vez de &lt;code>Exception&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Route(&amp;#34;GetWithoutExceptionHandler&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> Task GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> CustomException(&lt;span style="color:#a5d6ff">&amp;#34;This is a custom exception!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso por enquanto não mudará nada, mas o stack trace mostrará que o objeto que foi lançado é um &lt;code>CustomException&lt;/code> em vez de &lt;code>Exception&lt;/code>, dê uma olhada no início do stack trace:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>CustomExceptionHandleDemo.Exceptions.CustomException: This &lt;span style="color:#ff7b72">is&lt;/span> a custom exception!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at CustomExceptionHandleDemo.Controllers.WeatherForecastController.GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at lambda_method24(Closure , Object , Object[] )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&amp;lt;InvokeActionMethodAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">12_1&lt;/span>(ControllerActionInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&amp;lt;InvokeNextActionFilterAsync&amp;gt;g__Awaited|&lt;span style="color:#a5d6ff">10_0&lt;/span>(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State&amp;amp; next, Scope&amp;amp; scope, Object&amp;amp; state, Boolean&amp;amp; isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--- End of stack trace &lt;span style="color:#ff7b72">from&lt;/span> previous location ---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeFilterPipelineAsync&amp;gt;g__Awaited|&lt;span style="color:#a5d6ff">20_0&lt;/span>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">17_1&lt;/span>(ResourceInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">17_1&lt;/span>(ResourceInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Routing.EndpointMiddleware.&amp;lt;Invoke&amp;gt;g__AwaitRequestTask|&lt;span style="color:#a5d6ff">6_0&lt;/span>(Endpoint endpoint, Task requestTask, ILogger logger)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="criando-um-exceptionfilterattribute">Criando um ExceptionFilterAttribute&lt;/h3>
&lt;p>A Microsoft nos deu uma maneira de lidar com exceções depois que elas foram lançadas. Você pode verificar mais informações &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters.exceptionfilterattribute?view=aspnetcore-7.0">aqui&lt;/a>.&lt;/p>
&lt;p>Mas até agora a documentação que eles nos fornecem é:&lt;/p>
&lt;blockquote>
&lt;p>Um filtro abstrato que é executado de forma assíncrona após uma ação lançar uma exceção. As subclasses devem substituir OnException(ExceptionContext) ou OnExceptionAsync(ExceptionContext) mas não ambos.&lt;/p>&lt;/blockquote>
&lt;p>Então vamos criar um, vamos criar &lt;code>CustomExceptionFilterAttribute&lt;/code> no qual vamos substituir &lt;code>OnException&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc.Filters&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.Exceptions&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.ExceptionFilterAttributes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CustomExceptionFilterAttribute&lt;/span> : ExceptionFilterAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// OnException&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;ExceptionContext&amp;#34; name=&amp;#34;context&amp;#34;&amp;gt;Parameter for context&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnException(ExceptionContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (context.Exception &lt;span style="color:#ff7b72">is&lt;/span> CustomException)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.HttpContext.Response.StatusCode = &lt;span style="color:#a5d6ff">500&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.Result = &lt;span style="color:#ff7b72">new&lt;/span> ObjectResult(context.Exception.Message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Como você pode ver, estamos dando uma olhada no &lt;code>ExceptionContext&lt;/code>, quando a exceção é um tipo de &lt;code>CustomException&lt;/code>, fazemos algo.&lt;/p>
&lt;p>Esse algo está atualizando a resposta e o código de status do que iremos retornar.&lt;/p>
&lt;p>Para atualizar o código de status, devemos atualizar &lt;code>context.HttpContext.Response.StatusCode&lt;/code> e para retornar o resultado, temos que atualizar o &lt;code>context.Result&lt;/code> fornecendo a ele um objeto que é herdado de &lt;code>ActionResult&lt;/code>.&lt;/p>
&lt;p>Este é um filtro, então significa que temos que adicionar algo adicionando &lt;code>[CustomExceptionFilter]&lt;/code>.&lt;/p>
&lt;h3 id="usando-o-filtro">Usando o filtro&lt;/h3>
&lt;p>Agora, vamos replicar o método que temos e adicionar este filtro para que ele entre em ação, nosso endpoint da API ficará assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.ExceptionFilterAttributes&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.Exceptions&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.Controllers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ApiController]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;[controller]&lt;span style="color:#a5d6ff">&amp;#34;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WeatherForecastController&lt;/span> : ControllerBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> WeatherForecastController()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;GetWithoutExceptionHandler&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Task GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> CustomException(&lt;span style="color:#a5d6ff">&amp;#34;This is a custom exception!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;GetWithExceptionHandler&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [CustomExceptionFilter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Task GetWithExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> CustomException(&lt;span style="color:#a5d6ff">&amp;#34;This is a custom exception!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Como você pode ver, temos um novo método chamado &lt;code>GetWithExceptionHandler&lt;/code>, que tem a mesma lógica que &lt;code>GetWithoutExceptionHandler&lt;/code> tem, mas neste caso, adicionamos o filtro &lt;code>[CustomExceptionFilter]&lt;/code> ao método.&lt;/p>
&lt;p>O resultado é o seguinte após executarmos o método, vou exibir uma imagem, pois ela não está mais mostrando o stack trace:&lt;/p>
&lt;p>&lt;img src="https://imgur.com/a8TSAy2.png"/>Com isso criamos uma exceção personalizada, um filtro para substituir o que acontece quando lançamos uma exceção e a usamos em um método.&lt;/p>
&lt;p>Isso pode ser usado para muitas coisas, como registrar e saber o que, quando e onde o erro acontece.&lt;/p></content:encoded><category>.NET</category><category>API</category></item><item><title>Uma maneira melhor de injetar coisas</title><link>https://emimontesdeoca.github.io/pt/posts/custom-iservicecollection-services/</link><pubDate>Mon, 04 Sep 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/custom-iservicecollection-services/</guid><description>Organize registros de injeção de dependência .NET usando métodos de extensão IServiceCollection limpos.</description><content:encoded>&lt;p>﻿Sempre que estou construindo coisas como serviços, repositórios, atributos ou qualquer outra coisa para injetar em minhas aplicações, há uma etapa que devemos realizar, que na verdade é adicionar os serviços à aplicação.&lt;/p>
&lt;p>É sempre a mesma coisa, você vai para &lt;code>Program.cs&lt;/code> e, em alguma parte do arquivo, adiciona &lt;code>builder.Services.AddScoped&amp;lt;MyService&amp;gt;();&lt;/code> para injetar o serviço.&lt;/p>
&lt;p>Algo assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add services to the container.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Repositories&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;ARepository&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;BRepository&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;AService&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;BService&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;CService&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;DService&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the HTTP request pipeline.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (!app.Environment.IsDevelopment())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseExceptionHandler(&lt;span style="color:#a5d6ff">&amp;#34;/Error&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseHsts();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseHttpsRedirection();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseStaticFiles();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseRouting();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseAuthorization();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Quer dizer, funciona, mas sou exigente e não gosto muito disso.&lt;/p>
&lt;p>Digamos que temos múltiplas injeções de dependência que queremos fazer, e elas não são realmente a mesma coisa, no meu caso isso poderia ser algo como ter uma biblioteca que contém todos os repositórios, outra biblioteca que tem todos os serviços e por último, outra biblioteca que contém atributos.&lt;/p>
&lt;p>Nesse caso, você pode imaginar a quantidade de linhas que temos que adicionar ao &lt;code>Program.cs&lt;/code>.&lt;/p>
&lt;p>Digamos que temos uma biblioteca que contém alguns serviços, se quisermos incluir todos os nossos serviços, temos que trabalhar com &lt;code>IServiceCollection&lt;/code>.&lt;/p>
&lt;p>Então vamos criar um &lt;code>static class&lt;/code> que terá um método &lt;code>static&lt;/code> chamado &lt;code>AddServices&lt;/code> que retorna um &lt;code>IServiceCollection&lt;/code>.&lt;/p>
&lt;p>Neste caso, será denominado &lt;code>IServiceCollectionServicesExtensions&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// IServiceCollectionServicesExtensions class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">IServiceCollectionServicesExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// AddCoreServices&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;IServiceCollection&amp;#34; name=&amp;#34;services&amp;#34;&amp;gt;Parameter for &amp;lt;see cref=&amp;#34;IServiceCollection&amp;#34;/&amp;gt;&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;returns&amp;gt;An object of type &amp;lt;see cref=&amp;#34;IServiceCollection&amp;#34;/&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IServiceCollection AddServices(&lt;span style="color:#ff7b72">this&lt;/span> IServiceCollection services)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> services
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;AService&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;BService&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;CService&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;DService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> services;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Além disso, também temos outra biblioteca que inclui alguns repositórios que estão sendo utilizados por estes serviços, então vamos fazer o mesmo.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// IServiceCollectionServicesExtensions class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">IServiceCollectionServicesExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// AddCoreServices&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;IServiceCollection&amp;#34; name=&amp;#34;services&amp;#34;&amp;gt;Parameter for &amp;lt;see cref=&amp;#34;IServiceCollection&amp;#34;/&amp;gt;&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;returns&amp;gt;An object of type &amp;lt;see cref=&amp;#34;IServiceCollection&amp;#34;/&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IServiceCollection AddRepositories(&lt;span style="color:#ff7b72">this&lt;/span> IServiceCollection services)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> services
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;ARepository&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;BRepository&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> services;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora, temos nossos repositórios e métodos de serviços para injetar criados, mas como os usamos?&lt;/p>
&lt;p>Vamos voltar ao nosso &lt;code>Program.cs&lt;/code> e adicionar o seguinte:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add services to the container.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Repositories&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddRepositories(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddServices(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the HTTP request pipeline.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (!app.Environment.IsDevelopment())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseExceptionHandler(&lt;span style="color:#a5d6ff">&amp;#34;/Error&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseHsts();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseHttpsRedirection();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseStaticFiles();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseRouting();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseAuthorization();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Isso parece muito mais limpo, não é? Bem, com isso, injetamos com sucesso alguns serviços e repositórios, mas agora parece melhor e na verdade temos o que injetamos na biblioteca externa.&lt;/p></content:encoded><category>.NET</category><category>Docker</category></item><item><title>Injeção de dependência com atributos na API .NET</title><link>https://emimontesdeoca.github.io/pt/posts/api-di-attributes/</link><pubDate>Tue, 22 Aug 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/api-di-attributes/</guid><description>Habilite a injeção de dependência em filtros de ação da API .NET usando TypeFilterAttribute em vez de ActionAttribute.</description><content:encoded>&lt;p>A injeção de dependência é provavelmente um dos melhores recursos que temos no .NET neste momento. Não há nenhuma maneira possível de você não estar usando-o, então se você é como eu, você deseja adicioná-lo a todas as implementações que fizer.&lt;/p>
&lt;p>Filtros, conforme [documentação](&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.1">https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.1&lt;/a> oficial da Microsoft):&lt;/p>
&lt;blockquote>
&lt;p>Os filtros no ASP.NET Core permitem que o código seja executado antes ou depois de estágios específicos no pipeline de processamento de solicitações.&lt;/p>
&lt;p>Filtros integrados lidam com tarefas como:&lt;/p>
&lt;ul>
&lt;li>Autorização, impedindo o acesso a recursos para os quais um usuário não está autorizado.&lt;/li>
&lt;li>Cache de resposta, causando curto-circuito no pipeline de solicitação para retornar uma resposta armazenada em cache.&lt;/li>
&lt;/ul>
&lt;p>Filtros personalizados podem ser criados para lidar com questões transversais. Exemplos de preocupações transversais incluem tratamento de erros, armazenamento em cache, configuração, autorização e registro. Os filtros evitam a duplicação de código.&lt;/p>&lt;/blockquote>
&lt;p>Eu trabalho muito com APIs e há algumas coisas que devem executar cada solicitação, ou praticamente todas elas, então, idealmente, o que queremos fazer é trabalhar com isso mais&amp;hellip;. injeção de dependência!&lt;/p>
&lt;p>Mas às vezes é um pouco complicado, não funciona como queremos se quisermos herdar de &lt;code>ActionAttribute&lt;/code> então precisamos trabalhar com &lt;code>TypeFilterAttribute&lt;/code>, que nos permite fazer coisas ao substituir &lt;code>OnActionExecutionAsync&lt;/code>.&lt;/p>
&lt;p>Eu costumo criar esses filtros para fazer alguns registros, então vamos usar isso como exemplo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// LoggedQueryAttribute class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">LoggedQueryTypeFilterAttribute&lt;/span> : TypeFilterAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;LoggedQueryTypeFilterAttribute&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> LoggedQueryTypeFilterAttribute() : &lt;span style="color:#ff7b72">base&lt;/span>(&lt;span style="color:#ff7b72">typeof&lt;/span>(LoggedQueryFilter))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// LoggedQueryFilter class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">LoggedQueryFilter&lt;/span> : IAsyncActionFilter
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;see cref=&amp;#34;_loggingService&amp;#34;/&amp;gt; object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> LoggingService _loggingService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;LoggedQueryFilter&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;LoggingService&amp;#34; name=&amp;#34;loggingService&amp;#34;&amp;gt;Parameter for loggingService&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> LoggedQueryFilter(LoggingService loggingService)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loggingService = loggingService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// OnActionExecutionAsync&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;ActionExecutingContext&amp;#34; name=&amp;#34;context&amp;#34;&amp;gt;Parameter for context&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;ActionExecutionDelegate&amp;#34; name=&amp;#34;next&amp;#34;&amp;gt;Parameter for next&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Get properties&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> properties = (Request)context.ActionArguments.First().Value!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Get call from context&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> call = context.HttpContext.Request.Path.Value!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Logging&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loggingService.LogCustomEvent(call);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Continue call&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> next();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A lógica é bem simples, obtemos o corpo acessando o objeto &lt;code>context&lt;/code> com &lt;code>context.ActionArguments.First().Value&lt;/code>, também obtemos a chamada do método com &lt;code>context.HttpContext.Request.Path.Value&lt;/code>.&lt;/p>
&lt;p>Então apenas chamamos nosso método do nosso serviço, neste caso é &lt;code> _loggingService.LogCustomEvent(call)&lt;/code>.&lt;/p>
&lt;p>Então, devemos chamar &lt;code>await next();&lt;/code>, porque o pipeline deve continuar.&lt;/p>
&lt;p>Isto é para o atributo, agora devemos incluir esse atributo em um método.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[LoggedQueryTypeFilterAttribute]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> ActionResult&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; TestFilter()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Ok(&lt;span style="color:#a5d6ff">&amp;#34;Hello world!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Espero que tenham gostado, se tiver alguma dúvida ou quiser entrar em contato, não hesite e entre em contato comigo!&lt;/p></content:encoded><category>.NET</category></item><item><title>Documentação do Swagger com bibliotecas externas</title><link>https://emimontesdeoca.github.io/pt/posts/swagger-libraries-documentation/</link><pubDate>Fri, 17 Feb 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/swagger-libraries-documentation/</guid><description>Habilite o Swagger para exibir documentação XML de modelos definidos em bibliotecas de classes .NET externas.</description><content:encoded>&lt;p>﻿Usar várias bibliotecas para dividir seu código para casos futuros é uma coisa boa. Pessoalmente adoro fazer isso, se consigo dividir minha lógica para que ela possa ser reutilizada em outros projetos ou partes dos projetos.&lt;/p>
&lt;p>Para mim, pelo menos, é obrigatório dividir os modelos de dados, o que inclui o &lt;code>Entity Framework&lt;/code>, para que possa ser referenciado e usado em um &lt;code>Console application&lt;/code>, um &lt;code>Blazor application&lt;/code> ou um &lt;code>API&lt;/code>.&lt;/p>
&lt;p>Vamos dar uma olhada em um exemplo de como faço as coisas, este é um screenshot de um projeto que possui alguns aplicativos e uma biblioteca compartilhada que possui todos os modelos.&lt;/p>
&lt;img src="https://imgur.com/gdg2nn3.png">
&lt;p>Este é um exemplo de classe que movi do projeto &lt;code>API&lt;/code> com documentação com &lt;code>summary&lt;/code> em cada propriedade e classe.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CustomLibrariesDocumentation.Models&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// WeatherForecast class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WeatherForecast&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Gets or sets the Date&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateOnly Date { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Gets or sets the TemperatureC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> TemperatureC { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Gets or sets the TemperatureF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> TemperatureF =&amp;gt; &lt;span style="color:#a5d6ff">32&lt;/span> + (&lt;span style="color:#ff7b72">int&lt;/span>)(TemperatureC / &lt;span style="color:#a5d6ff">0.5556&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Gets or sets the Summary&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> Summary { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Se fizermos referência ao &lt;code>Console application&lt;/code> e começarmos a usar o modelo, podemos ver a documentação, que é um recurso padrão no Visual Studio, então funciona bem, como você pode ver aqui:&lt;/p>
&lt;img src="https://imgur.com/Djf1MeO.png">
&lt;p>Mas então, se fizermos referência ao &lt;code>API&lt;/code> e formos para &lt;code>Swagger&lt;/code>, não haverá documentação resumida.&lt;/p>
&lt;img src="https://imgur.com/3Dg2Xpm.png">
&lt;p>Como podemos consertar isso?&lt;/p>
&lt;p>Primeiro, temos que habilitar os comentários XML na biblioteca, para fazer isso, é necessário atualizar as configurações do projeto e habilitar ``:&lt;/p>
&lt;img src="https://imgur.com/R1j8SfX.png">
&lt;p>Isso irá gerar alguns arquivos na pasta build que terminam com a extensão de arquivo &lt;code>xml&lt;/code>, como você pode ver aqui:&lt;/p>
&lt;img src="https://imgur.com/O8ywjr2.png">
&lt;p>Agora que fizemos isso, temos que adicionar algumas coisas no &lt;code>Program.cs&lt;/code>, para podermos ler esses arquivos, isso porque por padrão ele carrega apenas a definição XML do projeto em que está.&lt;/p>
&lt;p>Ele usa um método que temos dentro do &lt;code>AddSwaggerGen&lt;/code> que se chama &lt;code>IncludeXmlComments&lt;/code>.&lt;/p>
&lt;p>A ideia é que se tivermos todos os arquivos &lt;code>xml&lt;/code>, forçaremos o carregamento deles no Swagger.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddSwaggerGen(s =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Comments&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> allXmlFiles = Directory.GetFiles(AppContext.BaseDirectory, &lt;span style="color:#a5d6ff">&amp;#34;*.xml&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span> xmlFiles &lt;span style="color:#ff7b72">in&lt;/span> allXmlFiles)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> s.IncludeXmlComments(xmlFiles);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É direto, pegamos os arquivos &lt;code>xml&lt;/code> do diretório de construção e os adicionamos com o método &lt;code>IncludeXmlComments&lt;/code>.&lt;/p>
&lt;p>Agora carregamos novamente o &lt;code>API&lt;/code> e verificamos se conseguimos ver a documentação.&lt;/p>
&lt;img src="https://imgur.com/uNVNswn.png">
&lt;p>E você pode ver que podemos ver a documentação!&lt;/p>
&lt;p>Espero que tenha ajudado você, qualquer dúvida não hesite em entrar em contato comigo!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category><category>API</category></item><item><title>Limpar filiais locais no Git</title><link>https://emimontesdeoca.github.io/pt/posts/cleanup-local-branches/</link><pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/cleanup-local-branches/</guid><description>Exclua todas as ramificações locais do Git, exceto a ramificação principal, usando um único comando do PowerShell.</description><content:encoded>&lt;p>﻿Você já chegou a um ponto em que tinha muitas filiais locais? Isso acontece muito comigo, pois usamos ramificações para cada recurso, bug ou tarefa.&lt;/p>
&lt;p>Acabo com algo assim e fica super sujo depois de um tempo&lt;/p>
&lt;img src="https://imgur.com/W3OGJE7.png">
&lt;p>Isso realmente me irrita, então vou compartilhar um comando rápido que você pode executar em seu console para fazer essa limpeza!&lt;/p>
&lt;p>Encontrei este comando no Stack Overflow na seguinte &lt;a href="https://stackoverflow.com/a/56671336/7823470">resposta&lt;/a> de &lt;a href="https://stackoverflow.com/users/529612/robert-corvus">Robert Corvus&lt;/a>, que é uma versão que roda em Powerhsell.&lt;/p>
&lt;p>&lt;strong>Tenha cuidado ao executar este comando, pois você pode perder suas alterações&lt;/strong>&lt;/p>
&lt;p>Antes de executá-lo, lembre-se de atualizar o &lt;code>MY_MASTER_BRANCH_NAME&lt;/code> para o seu branch principal, que pode ser o &lt;code>master&lt;/code> como eu uso ou os novos que vêm por padrão chamados &lt;code>main&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-powershell" data-lang="powershell">&lt;span style="display:flex;">&lt;span>git branch | %{ &lt;span style="color:#79c0ff">$_&lt;/span>.&lt;span style="color:#79c0ff">Trim&lt;/span>() } | ?{ &lt;span style="color:#79c0ff">$_&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">-ne&lt;/span> &lt;span style="color:#a5d6ff">&amp;#39;MY_MASTER_BRANCH_NAME&amp;#39;&lt;/span> } | %{ git branch -D &lt;span style="color:#79c0ff">$_&lt;/span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois de executar este comando, você obterá uma saída como esta&lt;/p>
&lt;img src="https://imgur.com/VJn89OZ.png">
&lt;p>Espero que seja útil para você!&lt;/p></content:encoded><category>Git</category></item><item><title>Atualizando rotas de identidade no ASP.NET Core 7</title><link>https://emimontesdeoca.github.io/pt/posts/identity-url-change/</link><pubDate>Mon, 23 Jan 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/identity-url-change/</guid><description>Personalize o login de identidade padrão do ASP.NET Core e registre URLs por meio do scaffolding de páginas de identidade.</description><content:encoded>&lt;p>﻿Mesmo se perguntando como a Microsoft nomeia coisas tão raras? Eu sempre pensei que eles não fazem isso tão bem, mas, bem, é isso que é!&lt;/p>
&lt;p>O bom disso é que você pode mudar praticamente tudo enquanto está desenvolvendo!&lt;/p>
&lt;p>Você já entrou em uma página e percebeu instantaneamente que se trata de um projeto Web ASP.NET apenas fazendo o processo de registro ou login?&lt;/p>
&lt;img src="https://imgur.com/8NMNHGp.png">
&lt;p>Já aconteceu muito comigo porque por padrão no projeto que você cria, você tem essas urls para fazer o processo de login e cadastro (também tem muitas outras páginas).&lt;/p>
&lt;p>Portanto, este tutorial mostra uma maneira de atualizar essas URLs para que seu projeto fique mais bonito!!&lt;/p>
&lt;h2 id="comportamento-padrão">Comportamento padrão&lt;/h2>
&lt;p>Quando estamos criando um projeto Blazor e decidimos utilizá-lo com o Identity, ele exibe algo assim:&lt;/p>
&lt;img alinhar="center" src="https://i.imgur.com/2W8Oou9.png">
&lt;p>E quando tentamos fazer o processo de login ou registro, vamos &lt;code>/Identity/Account/Login&lt;/code> ou &lt;code>/Identity/Account/Register&lt;/code>.&lt;/p>
&lt;p>Mas e se eu disser que você pode atualizar efetivamente esses URLs para serem diferentes?&lt;/p>
&lt;h2 id="estruturando-as-páginas-login--e-register">Estruturando as páginas &lt;code>Login &lt;/code> e &lt;code>Register&lt;/code>&lt;/h2>
&lt;p>Para atualizar essas páginas, a Microsoft as oculta, mas você pode armazená-las rapidamente e fazer as alterações desejadas!&lt;/p>
&lt;p>Para fazer isso você precisa ir e &lt;code>Add Scaffolded Item&lt;/code> no menu de contexto do projeto, assim:&lt;/p>
&lt;img src="https://imgur.com/F3C4C9b.png">
&lt;p>Então irá aparecer um modal e você deve selecionar &lt;code>Identity&lt;/code> duas vezes e clicar em &lt;code>Add&lt;/code>:&lt;/p>
&lt;img src="https://imgur.com/tZUqUlY.png">
&lt;p>Após este, aparecerá mais um modal, onde você poderá selecionar quais páginas de toda a Identidade deseja atualizar. Há muitas páginas que você pode atualizar, mas vamos nos concentrar em &lt;code>Account/Login&lt;/code> e &lt;code>Account/Register&lt;/code>:&lt;/p>
&lt;img src="https://imgur.com/BS6ZLas.png">
&lt;p>Agora deixe funcionar um pouco e depois verifique o Solution Explorer, você encontrará alguns arquivos novos:&lt;/p>
&lt;img src="https://imgur.com/5lWHLyI.png">
&lt;p>Esses novos arquivos são a página de login e registro que o ASP.NET adiciona ao seu projeto quando você o seleciona para adicionar Identidade!&lt;/p>
&lt;h2 id="atualizando-urls">Atualizando URLs&lt;/h2>
&lt;p>Como você provavelmente notou, esses arquivos são arquivos razor, já que sua extensão é &lt;code>cshtml&lt;/code>, então vamos apenas usar uma diretiva para atualizar a url da página:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/login&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@model LoginModel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ViewData[&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>] = &lt;span style="color:#a5d6ff">&amp;#34;Log in&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;@ViewData[&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>]&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;row&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;col-md-4&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form id=&lt;span style="color:#a5d6ff">&amp;#34;account&amp;#34;&lt;/span> method=&lt;span style="color:#a5d6ff">&amp;#34;post&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h2&amp;gt;Use a local account to log &lt;span style="color:#ff7b72">in&lt;/span>.&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;hr /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div asp-validation-summary=&lt;span style="color:#a5d6ff">&amp;#34;ModelOnly&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span> role=&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;username&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;name@example.com&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt;Email&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;current-password&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;password&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt;Password&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;checkbox mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.RememberMe&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input class=&lt;span style="color:#a5d6ff">&amp;#34;form-check-input&amp;#34;&lt;/span> asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.RememberMe&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @Html.DisplayNameFor(m =&amp;gt; m.Input.RememberMe)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button id=&lt;span style="color:#a5d6ff">&amp;#34;login-submit&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;w-100 btn btn-lg btn-primary&amp;#34;&lt;/span>&amp;gt;Log &lt;span style="color:#ff7b72">in&lt;/span>&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a id=&lt;span style="color:#a5d6ff">&amp;#34;forgot-password&amp;#34;&lt;/span> asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./ForgotPassword&amp;#34;&lt;/span>&amp;gt;Forgot your password?&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./Register&amp;#34;&lt;/span> asp-route-returnUrl=&lt;span style="color:#a5d6ff">&amp;#34;@Model.ReturnUrl&amp;#34;&lt;/span>&amp;gt;Register &lt;span style="color:#ff7b72">as&lt;/span> a &lt;span style="color:#ff7b72">new&lt;/span> user&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a id=&lt;span style="color:#a5d6ff">&amp;#34;resend-confirmation&amp;#34;&lt;/span> asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./ResendEmailConfirmation&amp;#34;&lt;/span>&amp;gt;Resend email confirmation&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;col-md-6 col-md-offset-2&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h3&amp;gt;Use another service to log &lt;span style="color:#ff7b72">in&lt;/span>.&amp;lt;/h3&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;hr /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> ((Model.ExternalLogins?.Count ?? &lt;span style="color:#a5d6ff">0&lt;/span>) == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> There are no external authentication services configured. See &lt;span style="color:#ff7b72">this&lt;/span> &amp;lt;a href=&lt;span style="color:#a5d6ff">&amp;#34;https://go.microsoft.com/fwlink/?LinkID=532715&amp;#34;&lt;/span>&amp;gt;article
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> about setting up &lt;span style="color:#ff7b72">this&lt;/span> ASP.NET application to support logging &lt;span style="color:#ff7b72">in&lt;/span> via external services&amp;lt;/a&amp;gt;.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form id=&lt;span style="color:#a5d6ff">&amp;#34;external-account&amp;#34;&lt;/span> asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./ExternalLogin&amp;#34;&lt;/span> asp-route-returnUrl=&lt;span style="color:#a5d6ff">&amp;#34;@Model.ReturnUrl&amp;#34;&lt;/span> method=&lt;span style="color:#a5d6ff">&amp;#34;post&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-horizontal&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @foreach (&lt;span style="color:#ff7b72">var&lt;/span> provider &lt;span style="color:#ff7b72">in&lt;/span> Model.ExternalLogins!)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;provider&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;@provider.Name&amp;#34;&lt;/span> title=&lt;span style="color:#a5d6ff">&amp;#34;Log in using your @provider.DisplayName account&amp;#34;&lt;/span>&amp;gt;@provider.DisplayName&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@section Scripts {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#ff7b72">partial&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;_ValidationScriptsPartial&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Como você pode ver, a maior parte das coisas é a mesma, mas se você der uma olhada na primeira linha da aula, atualizei o que tínhamos antes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>para&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/login&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Bem, isso foi fácil, não foi? Agora vamos fazer um teste rápido para ver se funciona.&lt;/p>
&lt;p>Em primeiro lugar, vamos para a página padrão que temos inicialmente, para verificar se ainda funciona:&lt;/p>
&lt;img src="https://imgur.com/ngbRNaG.png">
&lt;p>O que não acontece! Então agora vamos testar nosso novo URL que é &lt;code>/login&lt;/code>:&lt;/p>
&lt;img src="https://imgur.com/R067PnF.png">
&lt;p>E funciona!!&lt;/p>
&lt;p>Agora vamos fazer o mesmo para o registrador, atualizamos sua página e adicionamos o caminho que queremos na diretiva &lt;code>@page&lt;/code> e vamos fazer um teste!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/register&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@model RegisterModel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ViewData[&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>] = &lt;span style="color:#a5d6ff">&amp;#34;Register&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;@ViewData[&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>]&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;row&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;col-md-4&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form id=&lt;span style="color:#a5d6ff">&amp;#34;registerForm&amp;#34;&lt;/span> asp-route-returnUrl=&lt;span style="color:#a5d6ff">&amp;#34;@Model.ReturnUrl&amp;#34;&lt;/span> method=&lt;span style="color:#a5d6ff">&amp;#34;post&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h2&amp;gt;Create a &lt;span style="color:#ff7b72">new&lt;/span> account.&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;hr /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div asp-validation-summary=&lt;span style="color:#a5d6ff">&amp;#34;ModelOnly&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span> role=&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;username&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;name@example.com&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span>&amp;gt;Email&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;new-password&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;password&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span>&amp;gt;Password&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.ConfirmPassword&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;new-password&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;password&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.ConfirmPassword&amp;#34;&lt;/span>&amp;gt;Confirm Password&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.ConfirmPassword&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button id=&lt;span style="color:#a5d6ff">&amp;#34;registerSubmit&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;w-100 btn btn-lg btn-primary&amp;#34;&lt;/span>&amp;gt;Register&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;col-md-6 col-md-offset-2&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h3&amp;gt;Use another service to register.&amp;lt;/h3&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;hr /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> ((Model.ExternalLogins?.Count ?? &lt;span style="color:#a5d6ff">0&lt;/span>) == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> There are no external authentication services configured. See &lt;span style="color:#ff7b72">this&lt;/span> &amp;lt;a href=&lt;span style="color:#a5d6ff">&amp;#34;https://go.microsoft.com/fwlink/?LinkID=532715&amp;#34;&lt;/span>&amp;gt;article
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> about setting up &lt;span style="color:#ff7b72">this&lt;/span> ASP.NET application to support logging &lt;span style="color:#ff7b72">in&lt;/span> via external services&amp;lt;/a&amp;gt;.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form id=&lt;span style="color:#a5d6ff">&amp;#34;external-account&amp;#34;&lt;/span> asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./ExternalLogin&amp;#34;&lt;/span> asp-route-returnUrl=&lt;span style="color:#a5d6ff">&amp;#34;@Model.ReturnUrl&amp;#34;&lt;/span> method=&lt;span style="color:#a5d6ff">&amp;#34;post&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-horizontal&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @foreach (&lt;span style="color:#ff7b72">var&lt;/span> provider &lt;span style="color:#ff7b72">in&lt;/span> Model.ExternalLogins!)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;provider&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;@provider.Name&amp;#34;&lt;/span> title=&lt;span style="color:#a5d6ff">&amp;#34;Log in using your @provider.DisplayName account&amp;#34;&lt;/span>&amp;gt;@provider.DisplayName&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@section Scripts {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#ff7b72">partial&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;_ValidationScriptsPartial&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Atualizei a parte superior com &lt;code>@page &amp;quot;/login&amp;quot;&lt;/code> e agora testamos se funciona:&lt;/p>
&lt;img src="https://imgur.com/M7KakaF.png">
&lt;p>Também funciona!!&lt;/p>
&lt;h2 id="é-issoespero-que-você-tenha-aprendido-como-atualizar-essas-urls-principalmente-porque-em-alguns-projetos-quando-você-está-fazendo-as-urls-de-uma-certa-maneira-e-aí-a-identidade-fica-diferente-é-uma-merda-haha">É issoEspero que você tenha aprendido como atualizar essas urls, principalmente porque em alguns projetos quando você está fazendo as urls de uma certa maneira e aí a Identidade fica diferente, é uma merda haha!&lt;/h2>
&lt;p>Se precisar de alguma coisa é só twittar para mim ou me enviar um e-mail e tentarei ajudar!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category></item><item><title>Atributos personalizados na API .NET 6 Core</title><link>https://emimontesdeoca.github.io/pt/posts/custom-attributes-net-6-core-api/</link><pubDate>Fri, 09 Dec 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/custom-attributes-net-6-core-api/</guid><description>Crie classes ActionFilterAttribute personalizadas para validar cabeçalhos de solicitação em APIs do .NET 6 Core.</description><content:encoded>&lt;p>﻿Atributos personalizados são realmente uma boa coisa para usar, comecei a usá-los muito recentemente, porque eles me permitem criar um único deles e reutilizá-los no controlador, na classe ou no próprio método.&lt;/p>
&lt;p>Eles realmente ajudam quando você deseja fazer algumas tarefas de segurança, como verificar cabeçalhos ou verificar o valor de um parâmetro que você definitivamente precisa.&lt;/p>
&lt;p>No meu caso vamos utilizá-lo em um projeto de API .NET Core, onde vamos verificar se todas as solicitações contêm um determinado cabeçalho.&lt;/p>
&lt;h1 id="headercheckattribute">HeaderCheckAttribute&lt;/h1>
&lt;p>Então, depois de criarmos nossa API .NET Core, vamos criar uma pasta para armazenar nossas coisas, porque gostamos de usar pastas.&lt;/p>
&lt;img src="https://i.imgur.com/i2VKbZN.png"/>
&lt;p>E então vamos adicionar a lógica à nossa classe &lt;code>HeaderCheckAttribute&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc.Filters&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">DotNet6CustomAttribute.Attributes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">HeaderCheckAttribute&lt;/span> : ActionFilterAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnActionExecuting(ActionExecutingContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Get all headers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> headers = context.HttpContext.Request.Headers;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Check if headers has x-dotnet-6-custom-attribute&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!headers.ContainsKey(&lt;span style="color:#a5d6ff">&amp;#34;x-dotnet-6-custom-attribute&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.Result = &lt;span style="color:#ff7b72">new&lt;/span> BadRequestObjectResult(&lt;span style="color:#a5d6ff">&amp;#34;The header x-dotnet-6-custom-attribute is missing&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(headers[&lt;span style="color:#a5d6ff">&amp;#34;x-dotnet-6-custom-attribute&amp;#34;&lt;/span>]))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.Result = &lt;span style="color:#ff7b72">new&lt;/span> BadRequestObjectResult(&lt;span style="color:#a5d6ff">&amp;#34;The header x-dotnet-6-custom-attribute can&amp;#39;t be null or empty&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">base&lt;/span>.OnActionExecuting(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Basicamente a lógica é: primeiro ele verifica se há um cabeçalho com a chave &lt;code>x-dotnet-6-custom-attribute&lt;/code> e se estiver lá, verifica se possui valores.&lt;/p>
&lt;p>Se ambas as expressões forem verdadeiras, ele retornará um &lt;code>BadRequestObjectResult&lt;/code> com uma determinada mensagem.&lt;/p>
&lt;h1 id="adicionando-o-ao-controlador">Adicionando-o ao controlador&lt;/h1>
&lt;p>Podemos adicionar essa lógica a vários lugares, podemos adicioná-la diretamente a todo o controlador, ou podemos adicioná-la a alguns dos métodos, vamos adicioná-la primeiro aos métodos e depois a todo o controlador.&lt;/p>
&lt;p>Então vamos decorar a classe &lt;code>WeatherForecastController&lt;/code> com eles.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">DotNet6CustomAttribute.Attributes&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">DotNet6CustomAttribute.Controllers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ApiController]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;[controller]&lt;span style="color:#a5d6ff">&amp;#34;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WeatherForecastController&lt;/span> : ControllerBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] Summaries = &lt;span style="color:#ff7b72">new&lt;/span>[]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Freezing&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Bracing&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Chilly&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Cool&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Mild&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Warm&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Balmy&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Hot&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Sweltering&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Scorching&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ILogger&amp;lt;WeatherForecastController&amp;gt; _logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> WeatherForecastController(ILogger&amp;lt;WeatherForecastController&amp;gt; logger)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger = logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;GetWeatherForecastWithCheck&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HeaderCheckAttribute]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> IEnumerable&amp;lt;WeatherForecast&amp;gt; GetWithCheck()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Enumerable.Range(&lt;span style="color:#a5d6ff">1&lt;/span>, &lt;span style="color:#a5d6ff">5&lt;/span>).Select(index =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> WeatherForecast
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Date = DateTime.Now.AddDays(index),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TemperatureC = Random.Shared.Next(-&lt;span style="color:#a5d6ff">20&lt;/span>, &lt;span style="color:#a5d6ff">55&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Summary = Summaries[Random.Shared.Next(Summaries.Length)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToArray();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;GetWeatherForecastWithoutCheck&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> IEnumerable&amp;lt;WeatherForecast&amp;gt; GetWithoutCheck()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Enumerable.Range(&lt;span style="color:#a5d6ff">1&lt;/span>, &lt;span style="color:#a5d6ff">5&lt;/span>).Select(index =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> WeatherForecast
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Date = DateTime.Now.AddDays(index),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TemperatureC = Random.Shared.Next(-&lt;span style="color:#a5d6ff">20&lt;/span>, &lt;span style="color:#a5d6ff">55&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Summary = Summaries[Random.Shared.Next(Summaries.Length)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToArray();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Vamos executar o projeto!&lt;/p>
&lt;img src="https://i.imgur.com/ZvO4LnE.png"/>
&lt;p>Temos 2 funções lá: &lt;code>GetWeatherForecastWithCheck&lt;/code> e &lt;code>GetWeatherForecastWithoutCheck&lt;/code>, uma delas irá falhar e a outra não, mas vamos dar uma olhada no Swagger!&lt;/p>
&lt;img src="https://i.imgur.com/x1yRb9j.png"/>
&lt;img src="https://i.imgur.com/XiO4GC9.png">
&lt;p>Como você pode ver um deles retorna um erro 400 com nossa mensagem, e o outro retorna os valores, agora para testar isso completamente, vamos executar o Postman e adicionar um cabeçalho para que também vejamos os dados usando &lt;code>GetWeatherForecastWithCheck&lt;/code>.&lt;/p>
&lt;p>#Carteiro&lt;/p>
&lt;p>Agora rodando no Postman, adicionamos o cabeçalho e vemos que a mensagem de erro mudou, pois agora fornecemos o cabeçalho, mas não há valor para ele&lt;/p>
&lt;img src="https://i.imgur.com/JHP8ZXZ.png"/>
&lt;p>Se adicionarmos um valor a ele, finalmente obteremos os valores!&lt;/p>
&lt;img src="https://i.imgur.com/jxt6xFe.png"/>
&lt;p>#É isso&lt;/p>
&lt;p>Bem, é isso! Muito simples, certo? Bem, agora você sabe como criar um atributo e atribuí-lo a métodos e controladores!&lt;/p>
&lt;p>Divirta-se com eles!&lt;/p>
&lt;h1 id="código">Código&lt;/h1>
&lt;p>Todo esse projeto está no Github e você pode encontrá-lo &lt;a href="https://github.com/emimontesdeoca/dotnet-6-attribute-post">aqui&lt;/a>!&lt;/p>
&lt;p>Se você tiver qualquer problema ou dúvida, sinta-se à vontade para entrar em contato comigo em qualquer mídia social em @emimontesdeoca (no Twitter é na verdade &lt;code>@emimontesdeocaa&lt;/code> com dois &lt;code>aa&lt;/code> no final). Você também pode encontrar a maioria das minhas redes sociais no cabeçalho do blog.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem! Cia!&lt;/p></content:encoded><category>.NET</category><category>API</category></item><item><title>Lidar com o carregamento de componentes no Blazor</title><link>https://emimontesdeoca.github.io/pt/posts/loading-component-blazor/</link><pubDate>Tue, 19 Jul 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/loading-component-blazor/</guid><description>Crie um componente wrapper giratório de carregamento reutilizável no Blazor usando RenderFragment e ChildContent.</description><content:encoded>&lt;p>O Blazor é incrível, realmente incrível, especialmente quando fazemos coisas assíncronas, como carregar caixas, e fica bom.&lt;/p>
&lt;p>Tenho tentado fazer várias maneiras de lidar com o carregamento de páginas, estados, componentes, etc. E acho que finalmente encontrei a maneira perfeita de fazer isso da maneira que desejo.&lt;/p>
&lt;h1 id="idéia">Idéia&lt;/h1>
&lt;p>Em vez de reescrever a lógica de carregamento em cada página ou componente, construímos o componente pai com um &lt;code>ChildComponent&lt;/code>, isso nos dará a mudança para apenas reutilizar várias vezes.&lt;/p>
&lt;h1 id="carregando-código-do-componente">Carregando código do Componente&lt;/h1>
&lt;p>O código é bem simples, não há muito o que fazer, um &lt;code>if&lt;/code> básico com uma propriedade de carregamento, uma função de alternância interna e pronto!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@if (_loaded)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;text-center&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;spinner-border&amp;#34;&lt;/span> role=&lt;span style="color:#a5d6ff">&amp;#34;status&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span class=&lt;span style="color:#a5d6ff">&amp;#34;sr-only&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @ChildContent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> _loaded = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> RenderFragment? ChildContent { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ToggleLoad(&lt;span style="color:#ff7b72">bool&lt;/span> state)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loaded = state;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="uso">Uso&lt;/h1>
&lt;p>O uso é bem simples, para fins de teste vamos usar uma nova página e colocar nosso conteúdo dentro do componente &lt;code>LoadingComponent&lt;/code> criado que acabamos de criar.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/loading&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using LoadingBoxes.Components
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;LoadingBoxes.Components.LoadingComponent @ref=loadingComponent&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> This &lt;span style="color:#ff7b72">is&lt;/span> some content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/LoadingBoxes.Components.LoadingComponent&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> LoadingComponent loadingComponent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// We are going to simulate some load&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(&lt;span style="color:#a5d6ff">5000&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loadingComponent.ToggleLoad(&lt;span style="color:#79c0ff">false&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É assim que parece&lt;/p>
&lt;img src="https://i.gyazo.com/cb892d796f396d43d5c54e30e1e87568.gif"/>
&lt;h1 id="exemplo-mais-engraçado">Exemplo mais engraçado&lt;/h1>
&lt;p>Digamos que temos vários componentes e cada um deles tem seus tempos de carregamento, podemos construir algo que fique bem com isso!&lt;/p>
&lt;p>Vamos criar um componente de carregamento falso que podemos reutilizar chamado &lt;code>FakeLoadingComponent&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@using LoadingBoxes.Components
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;LoadingBoxes.Components.LoadingComponent @ref=loadingComponent&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> It took @ellapsedTime seconds!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/LoadingBoxes.Components.LoadingComponent&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> LoadingComponent loadingComponent = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> ellapsedTime = &lt;span style="color:#a5d6ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> Random rnd = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> random = rnd.Next(&lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">5000&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// We are going to simulate some load&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(random);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ellapsedTime = random/ &lt;span style="color:#a5d6ff">1000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loadingComponent.ToggleLoad(&lt;span style="color:#79c0ff">false&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois é só atualizar a página &lt;code>Loading&lt;/code> com vários componentes &lt;code>FakeLoadingComponent&lt;/code> e verificar o resultado!!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/loading&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using LoadingBoxes.Components
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora isso parece muito melhor&lt;/p>
&lt;img src="https://i.gyazo.com/e427ca31af410314762446979d1c3739.gif">
&lt;p>E é isso!&lt;/p>
&lt;p>Se você tiver qualquer problema ou dúvida, sinta-se à vontade para entrar em contato comigo em qualquer mídia social em @emimontesdeoca (no Twitter na verdade é &lt;code>@emimontesdeocaa&lt;/code> com dois &lt;code>aa&lt;/code> no final). Você também pode encontrar a maioria das minhas redes sociais no cabeçalho do blog.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category></item><item><title>Obtendo a data de validade de um certificado</title><link>https://emimontesdeoca.github.io/pt/posts/expiration-date-certificate/</link><pubDate>Fri, 27 May 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/expiration-date-certificate/</guid><description>Recupere programaticamente as datas de expiração do certificado SSL usando C# HttpClient e X509Certificate2.</description><content:encoded>&lt;p>﻿Existem alguns aplicativos da web que ajudam você a verificar o status atual dos certificados dos domínios que usamos, e temos alguns deles.&lt;/p>
&lt;p>Criei uma Azure Function que roda uma vez por dia e verifica alguns domínios que preciso dar uma olhada, é uma aplicação de console super simples que se a data de validade do certificado for menor que 30 dias, ele enviará um email.&lt;/p>
&lt;p>A lógica de funcionamento da função em si não será mostrada, gostaria de mostrar a função que faz a verificação básica do certificado e quais dados obtemos.&lt;/p>
&lt;h2 id="a-função">A função&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;X509Certificate2&amp;gt; CheckCertificateAsync(&lt;span style="color:#ff7b72">string&lt;/span> urlPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> certificate = &lt;span style="color:#ff7b72">new&lt;/span> X509Certificate2();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> httpClientHandler = &lt;span style="color:#ff7b72">new&lt;/span> HttpClientHandler
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ServerCertificateCustomValidationCallback = (request, cert, chain, policyErrors) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> certificate = &lt;span style="color:#ff7b72">new&lt;/span> X509Certificate2(cert);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">HttpClient&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient(httpClientHandler);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> httpClient.SendAsync(&lt;span style="color:#ff7b72">new&lt;/span> HttpRequestMessage(HttpMethod.Head, urlPath));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> certificate;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Este método &lt;code>CheckCertificateAsync&lt;/code> nos retornará um certificado &lt;code>X509Certificate2&lt;/code> que nos permitirá fazer um monte de coisas, incluindo dar uma olhada na data de validade.&lt;/p>
&lt;h2 id="resultado-serializado">Resultado serializado&lt;/h2>
&lt;p>Este é o valor serializado do objeto &lt;code>certificate&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Archived&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Extensions&amp;#34;&lt;/span>:[
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeyUsages&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">160&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.15&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Key Usage&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;AwIFoA==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;EnhancedKeyUsages&amp;#34;&lt;/span>:[
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.3.6.1.5.5.7.3.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Server Authentication&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.3.6.1.5.5.7.3.2&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Client Authentication&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.37&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Enhanced Key Usage&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MBQGCCsGAQUFBwMBBggrBgEFBQcDAg==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;CertificateAuthority&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;HasPathLengthConstraint&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;PathLengthConstraint&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.19&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Basic Constraints&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MAA=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SubjectKeyIdentifier&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;634E1585565AA49402C21642A4A5979A38025797&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.14&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Subject Key Identifier&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;BBRjThWFVlqklALCFkKkpZeaOAJXlw==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.35&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Authority Key Identifier&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MBaAFBQusxe3WFbLrlAJQOYfr52LFMLG&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.3.6.1.5.5.7.1.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Authority Information Access&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MEcwIQYIKwYBBQUHMAGGFWh0dHA6Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iub3JnLw==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.17&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Subject Alternative Name&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MB6CHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.32&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Certificate Policies&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3Jn&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.3.6.1.4.1.11129.2.4.2&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;SCT List&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;BIH0APIAdwBByMqx3yJGShDGoToJQodeTjGLGwPr60vHaPCQYpYG9gAAAYCeJIwYAAAEAwBIMEYCIQCG8sf4iBitUjNCc1dsxVd5mdRQCKapRqqnTHKxSKHjHgIhAJFGNXEZkCHKygT1T7bE4orpd6p2l1+GmifMEIuRsgHbAHcARqVV63X6kSAwtaKJafTzfREsQXS+/Um4havy/HD+bUcAAAGAniSMNgAABAMASDBGAiEAoxv1LBn/vfyR7s67kRLB/n1tq3eicuA/8/V0S2YzQCYCIQDXaS3FZbdIVNxQvKxPFxM1awBO/sGxBXafz0lspOoWSA==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;HasPrivateKey&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;PrivateKey&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IssuerName&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Name&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;CN=R3, O=Let&amp;#39;s Encrypt, C=US&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJSMw==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;NotAfter&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2022-08-05T10:50:35+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;NotBefore&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2022-05-07T10:50:36+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;PublicKey&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;EncodedKeyValue&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.2.840.113549.1.1.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MIIBCgKCAQEAq8cbDO3GAfjqqbPPCBdPost8NMRmEubv85gXecll7mZMH5qSfTPuB/ouFWL3tPMf1U8usWeoSUK/48yatzBGwmj1KKlkaW9MS2QkydztRp+kH8LvbzbQvGknuOLWGHBALLT17o/3DYxuA5LnXdY+vLvJWygQoFr2N/XhnhUjcm6OaQEJpIykydfbBQGQSEuQIIw4egpgdHkYJjCOYAsXuSSggN8/FADTCec0RzVjfFTSoJ3hV9HLE9M8MCSXjuo0AJ/MbAxq91S8XmDcRjHCCd7Zw+NjHo8cxZCQ6NqGvn3xwx8ahmmbC+CyDEcIyJJZK2Yv+qE4oS8QZfaX/RaHMwIDAQAB&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;EncodedParameters&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.2.840.113549.1.1.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;BQA=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Key&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Key&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Algorithm&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Algorithm&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;AlgorithmGroup&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;AlgorithmGroup&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;ExportPolicy&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Handle&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsInvalid&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsClosed&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsEphemeral&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsMachineKey&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeyName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeySize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">2048&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeyUsage&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">16777215&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;ParentWindowHandle&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Provider&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Provider&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Microsoft Software Key Storage Provider&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;ProviderHandle&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsInvalid&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsClosed&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;UIPolicy&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;ProtectionLevel&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Description&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;UseContext&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;CreationTitle&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;UniqueName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;LegalKeySizes&amp;#34;&lt;/span>:[
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;MinSize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">512&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;MaxSize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">16384&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SkipSize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">64&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeyExchangeAlgorithm&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SignatureAlgorithm&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeySize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">2048&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.2.840.113549.1.1.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MIIFQDCCBCigAwIBAgISBNwTmwP/RTcrEeIgAdMrpaFtMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJSMzAeFw0yMjA1MDcwOTUwMzZaFw0yMjA4MDUwOTUwMzVaMCcxJTAjBgNVBAMTHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrxxsM7cYB+Oqps88IF0+iy3w0xGYS5u/zmBd5yWXuZkwfmpJ9M+4H+i4VYve08x/VTy6xZ6hJQr/jzJq3MEbCaPUoqWRpb0xLZCTJ3O1Gn6Qfwu9vNtC8aSe44tYYcEAstPXuj/cNjG4Dkudd1j68u8lbKBCgWvY39eGeFSNybo5pAQmkjKTJ19sFAZBIS5AgjDh6CmB0eRgmMI5gCxe5JKCA3z8UANMJ5zRHNWN8VNKgneFX0csT0zwwJJeO6jQAn8xsDGr3VLxeYNxGMcIJ3tnD42MejxzFkJDo2oa+ffHDHxqGaZsL4LIMRwjIklkrZi/6oTihLxBl9pf9FoczAgMBAAGjggJZMIICVTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFGNOFYVWWqSUAsIWQqSll5o4AleXMB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMCcGA1UdEQQgMB6CHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEGBgorBgEEAdZ5AgQCBIH3BIH0APIAdwBByMqx3yJGShDGoToJQodeTjGLGwPr60vHaPCQYpYG9gAAAYCeJIwYAAAEAwBIMEYCIQCG8sf4iBitUjNCc1dsxVd5mdRQCKapRqqnTHKxSKHjHgIhAJFGNXEZkCHKygT1T7bE4orpd6p2l1+GmifMEIuRsgHbAHcARqVV63X6kSAwtaKJafTzfREsQXS+/Um4havy/HD+bUcAAAGAniSMNgAABAMASDBGAiEAoxv1LBn/vfyR7s67kRLB/n1tq3eicuA/8/V0S2YzQCYCIQDXaS3FZbdIVNxQvKxPFxM1awBO/sGxBXafz0lspOoWSDANBgkqhkiG9w0BAQsFAAOCAQEAjSEID5MWonbSiyHbmPYWO8ImCCOjkLGxgY8WJODbrWxFy+xU44UwrWOCkqYZUlv2LRmPqSyZDrIeeHK9VMbGh71oXX+XovikgAr6PpI0Mp897nPWj0XvOBaSYG0s+f+CXMtyt0tWCsQOcl+iT82+Ja71f8gbVL6l7xESewEE78pTKEH8EqD22r8VSD7FNICD8EYQr13v3AuVWObSU/R8Td6SrSVEknw1HgJS4e9nvmrMxBGKOJ+aWrAGiUydehg8M9o2gbGckMhz6D7cwB5l618cYaXKkW1dEOYZHl++qUj1/VPK+FNkiDZOPVNN//PbZuOLwAUIlZvhqGWX5/9PBg==&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SerialNumber&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;04DC139B03FF45372B11E22001D32BA5A16D&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SignatureAlgorithm&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.2.840.113549.1.1.11&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;sha256RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SubjectName&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Name&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;CN=blog.emilianomontesdeoca.com&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MCcxJTAjBgNVBAMTHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Thumbprint&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;28CF960F772ABFF22AA193C291492C27F8E13D4D&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Version&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">3&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Handle&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">2658150705632&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Issuer&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;CN=R3, O=Let&amp;#39;s Encrypt, C=US&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Subject&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;CN=blog.emilianomontesdeoca.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="data-de-validade">Data de validade&lt;/h2>
&lt;p>Para saber o tempo de expiração, precisamos dar uma olhada no &lt;code>NotAfter&lt;/code> e &lt;code>NotBefore&lt;/code>, que estão dentro deste objeto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;NotAfter&amp;#34;&lt;/span>&lt;span style="color:#f85149">:&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;2022-08-05T10:50:35+01:00&amp;#34;&lt;/span>&lt;span style="color:#f85149">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;NotBefore&amp;#34;&lt;/span>&lt;span style="color:#f85149">:&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;2022-05-07T10:50:36+01:00&amp;#34;&lt;/span>&lt;span style="color:#f85149">,&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="aplicativo-de-console">Aplicativo de console&lt;/h2>
&lt;p>O trecho a seguir é um aplicativo de console simples desenvolvido em .NET 6, que produzirá o seguinte resultado, no qual você pode verificar qualquer uma das certificações desejadas:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Newtonsoft.Json&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Security.Cryptography.X509Certificates&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text.Json&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> url = &lt;span style="color:#a5d6ff">&amp;#34;https://blog.emilianomontesdeoca.com/&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;X509Certificate2&amp;gt; CheckCertificateAsync(&lt;span style="color:#ff7b72">string&lt;/span> urlPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> certificate = &lt;span style="color:#ff7b72">new&lt;/span> X509Certificate2();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> httpClientHandler = &lt;span style="color:#ff7b72">new&lt;/span> HttpClientHandler
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ServerCertificateCustomValidationCallback = (request, cert, chain, policyErrors) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> certificate = &lt;span style="color:#ff7b72">new&lt;/span> X509Certificate2(cert);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">HttpClient&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient(httpClientHandler);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> httpClient.SendAsync(&lt;span style="color:#ff7b72">new&lt;/span> HttpRequestMessage(HttpMethod.Head, urlPath));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> certificate;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> cert = &lt;span style="color:#ff7b72">await&lt;/span> CheckCertificateAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> serializedValue = JsonConvert.SerializeObject(cert);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(serializedValue);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.ReadLine();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="projeto-de-demonstração">Projeto de demonstração&lt;/h2>
&lt;p>Você pode encontrar o aplicativo de console em meu Github, no repositório chamado &lt;a href="https://github.com/emimontesdeoca/expiration-date-certificate">expiration-date-certificate&lt;/a>.&lt;/p></content:encoded><category>.NET</category><category>Azure</category></item><item><title>Focando um elemento no Blazor</title><link>https://emimontesdeoca.github.io/pt/posts/focus-element-blazor/</link><pubDate>Thu, 05 May 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/focus-element-blazor/</guid><description>Defina o foco em elementos HTML em componentes do Blazor usando interoperabilidade JavaScript e referências de elementos.</description><content:encoded>&lt;p>﻿Depois de trabalhar no jogo Wordlzor que fiz recentemente, precisei adicionar uma funcionalidade bastante simples: focar o jogo inteiro ao entrar.&lt;/p>
&lt;p>Isso precisava ser feito porque o usuário poderia realmente digitar no jogo e não apenas usar o teclado na tela.&lt;/p>
&lt;p>##Arquivo Javascript&lt;/p>
&lt;p>Para fazer isso, temos que criar um arquivo Javascript chamado &lt;code>app.js&lt;/code> que conterá uma função&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>window.FocusElement &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> (element) =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> element.focus();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois de fazer isso, precisamos incluir o script no arquivo &lt;code>index.html&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">script&lt;/span> src&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;js/app.js&amp;#34;&lt;/span>&amp;gt;&amp;lt;/&lt;span style="color:#7ee787">script&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="componente-blazor">Componente Blazor&lt;/h2>
&lt;p>Depois de inicializarmos o script, precisamos encontrar um elemento no qual focar, portanto, em qualquer um de nossos componentes, precisamos referenciar esse elemento a um objeto.&lt;/p>
&lt;p>Então, em nosso componente blazor, vamos adicionar uma div com &lt;code> Propriedade @ref&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span> &lt;span style="color:#f85149">@&lt;/span>ref&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">@elementToFocus&lt;/span> tabindex&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> &amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> This is my focus component
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Então na lógica do componente você pode ter a propriedade como um &lt;code>ElementReference&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Reference to element&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> ElementReference elementToFocus;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="injetando-jsinterop">Injetando JSInterop&lt;/h2>
&lt;p>Agora, para chamar a função que temos em nosso arquivo Javascript, precisamos usar &lt;code>JSInterop&lt;/code>.&lt;/p>
&lt;p>Primeiramente devemos injetá-lo no componente com a seguinte sintaxe:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Inject]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> IJSRuntime JSRuntime { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Com o serviço injetado, agora podemos chamar qualquer um dos métodos que ele possui, como &lt;code>InvokeVoidAsync&lt;/code>, que chamará a função:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task Focus()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Focus when initializing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JSRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;window.FocusElement&amp;#34;&lt;/span>, elementToFocus);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E quando você chamar a função &lt;code>Focus()&lt;/code>, ela focará o elemento que criamos, obviamente você pode refatorar e passar o próprio elemento como parâmetro.&lt;/p>
&lt;p>Se quiser dar uma olhada em como eu implementei, dê uma olhada no código fonte do &lt;a href="https://github.com/emimontesdeoca/Wordlzor">Wordlzor&lt;/a>, usei ele para alertas e foco ao fechar o modal de instrução.&lt;/p></content:encoded><category>.NET</category><category>Blazor</category></item><item><title>Gere arquivos *.dacpac do projeto VS Database em GitHub Actions</title><link>https://emimontesdeoca.github.io/pt/posts/generate-dacpacs-github-actions/</link><pubDate>Mon, 18 Apr 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/generate-dacpacs-github-actions/</guid><description>Automatize a geração de arquivos dacpac a partir de projetos de banco de dados do Visual Studio usando pipelines do GitHub Actions.</description><content:encoded>&lt;p>﻿Se você precisa trabalhar com banco de dados e quer fazer desenvolvimento ágil, você tem que ter um fluxo onde sempre que você adicionar ou modificar uma tabela, SP, função, ele irá compilar e gerar o arquivo de implantação do banco de dados.&lt;/p>
&lt;p>Já faço essa abordagem há muito tempo, fazemos as alterações no local, comparamos com nosso projeto de banco de dados e então quando fazemos commit e push de nossas alterações, elas geram um arquivo bacpac que irá para o banco de dados.&lt;/p>
&lt;p>Elimina todo o trabalho de fazer isso manualmente, o que pode causar um erro humano e em um banco de dados, o que é uma péssima notícia.&lt;/p>
&lt;h1 id="projeto-de-banco-de-dados">Projeto de banco de dados&lt;/h1>
&lt;p>Quando criamos nosso projeto de banco de dados no Visual Studio e importamos um banco de dados, ficará assim&lt;/p>
&lt;img src="https://i.gyazo.com/c0e11b14c707db66b8dbb591031cc527.png" />
&lt;p>Quando rodarmos uma compilação neste projeto, ele irá gerar um arquivo &lt;code>dacpac&lt;/code> que incluirá toda a nossa estrutura&lt;/p>
&lt;img src="https://i.gyazo.com/c883f3e329c7deb033564f7b5e9be7d4.png" />
&lt;h1 id="pipeline-do-github">Pipeline do Github&lt;/h1>
&lt;p>Agora que temos nosso código publicado no repositório, precisamos criar uma ação que irá compilar esse projeto, gerar esse arquivo &lt;code>dacpac&lt;/code> e colocá-lo em algum lugar onde possamos baixar ou usar em outra etapa.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">DacpacGithubActions project build&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">on&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">push&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">branches&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">main&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">jobs&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">build&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">runs-on&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">windows-latest&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">steps&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">actions/checkout@v2&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Setup MSBuild&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">microsoft/setup-msbuild@v1&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Navigate to Workspace&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">cd $GITHUB_WORKSPACE&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Create Build Directory&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">mkdir artifacts&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Build Solution&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>|&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> msbuild.exe /t:DacpacGithubActions /p:DebugSymbols=false /p:DebugType=None /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:OutDir=&amp;#34;../artifacts&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> &lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Upload artifact&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">actions/upload-artifact@v1.0.0&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">with&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">DacpacGithubActionsArtifacts&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">path&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;../artifacts&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esta ação fará um monte de coisas, construirá a solução e colocará o resultado na pasta &lt;code>artifacts&lt;/code>, criada previamente, e então fará o upload dos arquivos dessa pasta para os artefatos.&lt;/p>
&lt;h1 id="executando-o-pipeline">Executando o pipeline&lt;/h1>
&lt;p>Agora vá em frente e acione uma compilação, o resultado deve ser o seguinte&lt;/p>
&lt;img src="https://i.gyazo.com/9fe0b7a44be0f07bcc41fe0862183a54.png" />
&lt;p>Se realmente baixarmos o artefato e dermos uma olhada no conteúdo, é o que precisamos para o futuro, quando implementarmos uma etapa de implantação contínua, o arquivo &lt;code>dacpac&lt;/code>!&lt;/p>
&lt;img src="https://i.gyazo.com/287a392df0c5e966958262644e335149.png" />
&lt;h1 id="código">Código&lt;/h1>
&lt;p>Todo esse projeto está no Github e você pode encontrá-lo &lt;a href="https://github.com/emimontesdeoca/dacpac-github-actions">aqui&lt;/a>!&lt;/p>
&lt;p>Se você tiver qualquer problema ou dúvida, sinta-se à vontade para entrar em contato comigo em qualquer mídia social em @emimontesdeoca (no Twitter na verdade é &lt;code>@emimontesdeocaa&lt;/code> com dois &lt;code>aa&lt;/code> no final). Você também pode encontrar a maioria das minhas redes sociais no cabeçalho do blog.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem! Cia!&lt;/p></content:encoded><category>CI/CD</category></item><item><title>Alternar temas com Javascript Interop no Blazor</title><link>https://emimontesdeoca.github.io/pt/posts/blazor-toggle-darkmode/</link><pubDate>Fri, 01 Apr 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/blazor-toggle-darkmode/</guid><description>Implemente a alternância de temas claros e escuros no Blazor usando JavaScript Interop e atributos de dados CSS.</description><content:encoded>&lt;p>﻿Para pessoas como eu, que sofrem com flashbacks toda vez que abro uma página da web, criei uma solução super simples sobre como alternar entre o modo claro e escuro usando Javascript e chamá-lo do Blazor usando Javascript Interop.&lt;/p>
&lt;img src="https://media-exp1.licdn.com/dms/image/C4D22AQEYLTFA1e7i9A/feedshare-shrink_800/0/1641493691714?e=1651708800&amp;v=beta&amp;t=j6RxTrY--qUwxOcbt8Xh4QE9nYYF1zlJBMZP9dDLuC8" />
&lt;p>#Definindo o atributo pai&lt;/p>
&lt;p>Em primeiro lugar, temos que identificar o elemento pai com um identificador, isto porque mudamos as cores dependendo do valor desse identificador.&lt;/p>
&lt;p>Para fazer isso em Javascript, teríamos que executar este comando&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>document.documentElement.setAttribute(&lt;span style="color:#a5d6ff">&amp;#39;data-theme&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#39;YOUR_IDFENTIFIER&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>No nosso caso, queremos ter dois identificadores para cada tipo de estilo, que são &lt;code>light&lt;/code> e &lt;code>dark&lt;/code>.&lt;/p>
&lt;h2 id="criando-arquivo-javascript-para-lidar-com-a-lógica">Criando arquivo Javascript para lidar com a lógica&lt;/h2>
&lt;p>Agora temos a função de atualizar o identificador, agora vamos criar um arquivo Javscript com uma função que execute essa lógica. Nosso arquivo se chamará &lt;code>app.js&lt;/code>&lt;/p>
&lt;img src="https://i.gyazo.com/c481aa0ed8329e8592832e9da2921cea.png" />
&lt;p>Nesse arquivo teremos uma função que chamará o código que mencionamos anteriormente.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> toggleTheme &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> &lt;span style="color:#ff7b72">function&lt;/span> (identifier) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> document.documentElement.setAttribute(&lt;span style="color:#a5d6ff">&amp;#39;data-theme&amp;#39;&lt;/span>, identifier);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="adicionando-arquivo-javascript-ao-blazor">Adicionando arquivo Javascript ao Blazor&lt;/h2>
&lt;p>Adicione o script ao aplicativo Blazor adicionando-o onde os scripts estão localizados.&lt;/p>
&lt;p>&lt;code>&amp;lt;script src=&amp;quot;~/js/app.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code>&lt;/p>
&lt;h1 id="criando-o-serviço">Criando o serviço&lt;/h1>
&lt;p>Agora que temos o código Javascript, precisamos criar um serviço que se chamará &lt;code>ThemeToggleService&lt;/code>.&lt;/p>
&lt;img src="https://i.gyazo.com/e6480acaf46eef68f65b16a0738683ce.png" />
&lt;p>Este serviço tratará da lógica para alternar entre o tema &lt;code>light&lt;/code> e &lt;code>dark&lt;/code>.&lt;/p>
&lt;h2 id="injetando-jsinterop">Injetando JSInterop&lt;/h2>
&lt;p>Para chamar qualquer Javascipt, temos que chamar o JSInterop, então temos que criar uma propriedade que irá injetar.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.JSInterop&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">BlazorDarkmodeToggle.Data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IJSRuntime jSRuntime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ThemeToggleService(IJSRuntime jSRuntime)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.jSRuntime = jSRuntime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="função-para-definir-o-identificador">Função para definir o identificador&lt;/h2>
&lt;p>Agora que criamos o serviço, vamos criar a função para definir o identificador, esta função irá apenas atualizar o &lt;code>data-theme&lt;/code> na página.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.JSInterop&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">BlazorDarkmodeToggle.Data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ThemeToggleService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IJSRuntime jSRuntime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ThemeToggleService(IJSRuntime jSRuntime)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.jSRuntime = jSRuntime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> IsLightTheme { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetIdentifier =&amp;gt; IsLightTheme ? &lt;span style="color:#a5d6ff">&amp;#34;light&amp;#34;&lt;/span> : &lt;span style="color:#a5d6ff">&amp;#34;dark&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task ToggleTheme() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IsLightTheme = !IsLightTheme;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> jSRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;toggleTheme&amp;#34;&lt;/span>, GetIdentifier);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="adicionar-serviço-à-inicialização">Adicionar serviço à inicialização&lt;/h2>
&lt;p>Para disponibilizar o serviço para todos os componentes e páginas, temos que adicioná-lo ao arquivo &lt;code>Program.cs&lt;/code>.&lt;/p>
&lt;p>&lt;code>builder.Services.AddSingleton&amp;lt;ThemeToggleService&amp;gt;();&lt;/code>&lt;/p>
&lt;p>Então o arquivo vai ficar assim&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">BlazorDarkmodeToggle.Data&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components.Web&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add services to the container.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddServerSideBlazor();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddSingleton&amp;lt;WeatherForecastService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddSingleton&amp;lt;ThemeToggleService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the HTTP request pipeline.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (!app.Environment.IsDevelopment())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseExceptionHandler(&lt;span style="color:#a5d6ff">&amp;#34;/Error&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseHsts();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseHttpsRedirection();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseStaticFiles();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseRouting();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapBlazorHub();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapFallbackToPage(&lt;span style="color:#a5d6ff">&amp;#34;/_Host&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lembre-se de que isso pode mudar dependendo se você usa o Blazor &lt;code>Server&lt;/code> ou o Blazor &lt;code>WebAssembly&lt;/code>.&lt;/p>
&lt;h1 id="preparando-o-css">Preparando o CSS&lt;/h1>
&lt;p>Agora que temos parte do código pronto, temos que começar a trabalhar no CSS. O que precisamos fazer é definir todas as propriedades CSS que tratam das cores CSS, plano de fundo, etc.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>:&lt;span style="color:#d2a8ff;font-weight:bold">root&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#79c0ff">--background-color&lt;/span>:&lt;span style="color:#a5d6ff">#ffffff&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#79c0ff">--text-color&lt;/span>:&lt;span style="color:#a5d6ff">#000000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depois de fazer isso, usando o identificador, podemos facilmente alternar entre os identificadores, e ele usará qualquer um dos outros valores.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">[&lt;/span>&lt;span style="color:#7ee787">data-theme&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;dark&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">]&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#79c0ff">--background-color&lt;/span>: &lt;span style="color:#a5d6ff">#000000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#79c0ff">--text-color&lt;/span>: &lt;span style="color:#a5d6ff">#ffffff&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para o desvanecimento legal da animação, vamos apenas adicionar uma propriedade &lt;code>transition&lt;/code> a todas, para que fique bem.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">*&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">transition&lt;/span>: &lt;span style="color:#79c0ff">all&lt;/span> &lt;span style="color:#a5d6ff">250&lt;/span>&lt;span style="color:#ff7b72">ms&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora iremos atribuir essas variáveis a algumas classes css, para que possamos vê-las na página do Blazor.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">app-background&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">background-color&lt;/span>: &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>background&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">width&lt;/span>: &lt;span style="color:#a5d6ff">200&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">height&lt;/span>: &lt;span style="color:#a5d6ff">200&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">app-text&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">color&lt;/span>: &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">text&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lembre-se de adicionar o arquivo ao aplicativo Blazor adicionando-o no &lt;code>head&lt;/code>.&lt;/p>
&lt;p>&lt;code>&amp;lt;link href=&amp;quot;css/app.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; /&amp;gt;&lt;/code>&lt;/p>
&lt;h1 id="chamando-do-blazor">Chamando do Blazor&lt;/h1>
&lt;p>Então, como vamos testar isso? Para simplificar, vamos apenas adicionar uma div com a classe &lt;code>app-background&lt;/code>, e dentro dela terá um &lt;code>p&lt;/code> com a classe &lt;code>app-text&lt;/code>.Então, vamos criar uma página chamada &lt;code>Theme.razor&lt;/code> na pasta &lt;code>Pages&lt;/code> e adicionar algum código para isso&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/theme&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using BlazorDarkmodeToggle.Data
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inject ThemeToggleService ThemeToggleService
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span> @onclick=Toggle&amp;gt;Toggle theme&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;br /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;app-background&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p class=&lt;span style="color:#a5d6ff">&amp;#34;app-text&amp;#34;&lt;/span>&amp;gt;Hello world!&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task Toggle()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">this&lt;/span>.ThemeToggleService.ToggleTheme();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Poderíamos testar isso acessando &lt;code>/theme&lt;/code>, mas também vamos adicioná-lo à barra de navegação&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;nav-item px-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NavLink class=&lt;span style="color:#a5d6ff">&amp;#34;nav-link&amp;#34;&lt;/span> href=&lt;span style="color:#a5d6ff">&amp;#34;theme&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span class=&lt;span style="color:#a5d6ff">&amp;#34;oi oi-list-rich&amp;#34;&lt;/span> aria-hidden=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt; Theme togle
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NavLink&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="teste">Teste&lt;/h1>
&lt;img src="https://i.gyazo.com/a4894edb345cfd8c9f0cf2de869dba32.gif" />
&lt;p>Como você pode ver, assim que alternamos as mudanças de tema, fundo e cor da fonte, agora você pode ver o quão poderoso isso é, se você realmente desenvolver a página inteira usando essas variáveis ​​em CSS, você poderá ter temas diferentes e apenas alterná-los!&lt;/p>
&lt;p>Que incrível!!&lt;/p>
&lt;h1 id="código">Código&lt;/h1>
&lt;p>Todo esse projeto está no Github e você pode encontrá-lo &lt;a href="https://github.com/emimontesdeoca/BlazorDarkmodeToggle">aqui&lt;/a>!&lt;/p>
&lt;p>Se você tiver qualquer problema ou dúvida, sinta-se à vontade para entrar em contato comigo em qualquer mídia social em @emimontesdeoca (no Twitter na verdade é &lt;code>@emimontesdeocaa&lt;/code> com dois &lt;code>aa&lt;/code> no final). Você também pode encontrar a maioria das minhas redes sociais no cabeçalho do blog.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem!&lt;/p>
&lt;h1 id="recursos">Recursos&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-6.0">Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>Docker</category></item><item><title>Carregando arquivos para o Azure Blob Storage no Blazor</title><link>https://emimontesdeoca.github.io/pt/posts/uploading-files-az-blob-blazor/</link><pubDate>Wed, 23 Mar 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/uploading-files-az-blob-blazor/</guid><description>Carregue arquivos no Armazenamento de Blobs do Azure de um aplicativo Blazor usando a entrada de arquivo HTML5 nativa.</description><content:encoded>&lt;p>O serviço de armazenamento Azure Blob é um dos serviços mais utilizados no ecossistema Azure, permite fazer upload de muitos arquivos para a nuvem e é super barato. Também possui um site intuitivo onde você pode acessá-los ou baixá-los por um link direto.&lt;/p>
&lt;p>No geral, é um serviço muito bom, tenho usado tanto para projetos profissionais quanto pessoais e, honestamente, o melhor é a simplicidade de como fazê-lo funcionar.&lt;/p>
&lt;p>A maioria dos casos que usei o Azure Blob Storage está no backend, carregando diretamente algo que é resultado de uma operação ou algo assim. Então, eu realmente não fiz nenhum tipo de upload de um arquivo de um site ou algo assim. Azarado!&lt;/p>
&lt;p>Como sou fã do Blazor, estou apresentando uma maneira de fazer upload de arquivos para um armazenamento de Blob do Azure usando a entrada de arquivo nativa do HTML5.&lt;/p>
&lt;h1 id="pré-requisitos">Pré-requisitos&lt;/h1>
&lt;ul>
&lt;li>Conta Azure (se você não tiver, vá &lt;a href="aka.ms/free">aqui&lt;/a> com um monte de $$$).&lt;/li>
&lt;li>Um IDE (VS, Código, qualquer um funcionará)&lt;/li>
&lt;li>.NET Core 3.0 ou superior&lt;/li>
&lt;/ul>
&lt;h1 id="criando-um-armazenamento-de-blobs-do-azure">Criando um Armazenamento de Blobs do Azure&lt;/h1>
&lt;p>Vá para o seu portal do Azure e vamos criar uma conta de armazenamento.&lt;/p>
&lt;p>Primeiro pesquise por &lt;code>Storage accounts&lt;/code> e clique no resultado.&lt;/p>
&lt;img src="https://i.gyazo.com/dfc7db88129cd5e2a5015a7bfd846685.png" />
&lt;p>Após carregar, clique em &lt;code>Create&lt;/code>.&lt;/p>
&lt;p>Um formulário aparecerá com várias etapas, então vá em frente e preencha-as.&lt;/p>
&lt;img src="https://i.gyazo.com/2293db6fea38aa8b9c61980d088c56c4.png" />
&lt;p>Após preencher tudo com as configurações desejadas, passe na validação e crie o recurso.&lt;/p>
&lt;img src="https://i.gyazo.com/f416db52c958744f8e9ca1dbe904a790.png" />
&lt;p>Depois de criado, clique em &lt;code>Go to resource&lt;/code>. Obteremos uma string de conexão que nos permitirá brincar com a API.&lt;/p>
&lt;img src="https://i.gyazo.com/713702e57e8c18db16ec214ea999507f.png" />
&lt;h1 id="obtenha-as-chaves-de-recursos">Obtenha as chaves de recursos&lt;/h1>
&lt;p>Depois de chegarmos ao nosso recurso recém-criado, vá para &lt;code>Acccess keys&lt;/code> em &lt;code>Security + networking&lt;/code>. Após carregar você verá um &lt;code>Connection string&lt;/code> e &lt;code>Key&lt;/code>, copie-os porque precisaremos deles mais tarde!&lt;/p>
&lt;img src="https://i.gyazo.com/87af79ff72e5973f3fbcdf22547d2c54.png" />
&lt;h1 id="criando-um-projeto-legal-do-blazor-server">Criando um projeto legal do Blazor Server&lt;/h1>
&lt;p>Vou usar o Visual Studio 2022 para este tutorial, mas como disse antes, você pode usar qualquer outro IDE e apenas criar o projeto usando a CLI &lt;code>dotnet&lt;/code>.&lt;/p>
&lt;p>Então, vamos criar um projeto Blazor bem rápido usando o Visual Studio em apenas algumas etapas.&lt;/p>
&lt;img src="https://i.gyazo.com/fab7bbabc9601132c8554c0b83ff4f58.png" />
&lt;img src="https://i.gyazo.com/dd015b5c8f1a46c2df0fa5af7cfc08e4.png" />
&lt;img src="https://i.gyazo.com/e7225a77fb3a91c2fb5d39877165e9b8.png" />
&lt;p>Se executarmos o que acabamos de criar, ficará assim, apenas um aplicativo Blazor normal e simples.&lt;/p>
&lt;img src="https://i.gyazo.com/95785a6c6cc68050d9989c489df0f599.png" />
&lt;p>Agora que criamos o projeto, vamos fazer algumas coisas de UI para termos uma nova página com um monte de entradas para que possamos preencher com as chaves do recurso, um &lt;code>InputFile&lt;/code> para o(s) arquivo(s), um botão para fazer algo e uma mensagem que resultará da ação.&lt;/p>
&lt;h2 id="criando-o-modelo">Criando o modelo&lt;/h2>
&lt;p>Precisaremos de alguns modelos para fazer isso corretamente. Primeiro teremos a classe &lt;code>BlobRequest&lt;/code> que tratará a string de conexão, o nome do contêiner e os arquivos.&lt;/p>
&lt;p>Além disso, para manter tudo organizado, criaremos uma classe chamada &lt;code>BlobFile&lt;/code> na qual armazenaremos o &lt;code>Name&lt;/code> e o &lt;code>Data&lt;/code>.&lt;/p>
&lt;p>Criei uma pasta chamada &lt;code>Models&lt;/code> então temos tudo separado.&lt;/p>
&lt;p>As aulas são muito simples.```csharp
using System.ComponentModel.DataAnnotations;&lt;/p>
&lt;p>namespace UploadingFilesAzBlobBlazor.Models {
public class BlobRequest
{
[Required(ErrorMessage = &amp;ldquo;Connection string is required&amp;rdquo;)]
public string? ConnectionString { get; set; }
[Required(ErrorMessage = &amp;ldquo;Container name is required&amp;rdquo;)]
public string? Container { get; set; }
public List&lt;BlobFile>? Files { get; set; }
}
}&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>namespace UploadingFilesAzBlobBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public &lt;span style="color:#ff7b72">class&lt;/span> BlobFile
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public string&lt;span style="color:#f85149">?&lt;/span> Name { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public byte[]&lt;span style="color:#f85149">?&lt;/span> Data { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="modificando-a-iu">Modificando a IU&lt;/h2>
&lt;p>Agora que criamos nosso modelo, vamos em frente e fazer mágica na UI com ele!&lt;/p>
&lt;p>Primeiro de tudo, crie uma página na pasta &lt;code>Pages&lt;/code> chamada &lt;code>Upload.razor&lt;/code>.&lt;/p>
&lt;p>Nesta página vamos adicionar um pequeno formulário que preencherá nossa classe &lt;code>BlobRequest&lt;/code>, então temos que adicionar entradas para &lt;code>ConnectionString&lt;/code>, &lt;code>Container&lt;/code> e &lt;code>Files&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/upload&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using UploadingFilesAzBlobBlazor.Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inject IJSRuntime JsRuntime
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;PageTitle&amp;gt; Upload&amp;lt;/PageTitle&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt; Azure blob storage uploader!&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;EditForm Model=&lt;span style="color:#a5d6ff">&amp;#34;@modal&amp;#34;&lt;/span> OnValidSubmit=&lt;span style="color:#a5d6ff">&amp;#34;@HandleValidSubmit&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;DataAnnotationsValidator /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;ValidationSummary /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;connectionString&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt; Connection &lt;span style="color:#ff7b72">string&lt;/span>&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;InputText class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;connectionString&amp;#34;&lt;/span> @bind-Value=&lt;span style="color:#a5d6ff">&amp;#34;modal.ConnectionString&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;container&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt; Container name&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;InputText class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;container&amp;#34;&lt;/span> @bind-Value=&lt;span style="color:#a5d6ff">&amp;#34;modal.Container&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;formFileMultiple&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt; Files&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;InputFile OnChange=&lt;span style="color:#a5d6ff">&amp;#34;OnInputFileChange&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;formFileMultiple&amp;#34;&lt;/span> multiple /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span>&amp;gt; Submit&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/EditForm&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> BlobRequest modal = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> EditContext editContext;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IReadOnlyList &amp;lt;IBrowserFile&amp;gt; selectedFiles;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnInputFileChange(InputFileChangeEventArgs e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> selectedFiles = e.GetMultipleFiles();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> .StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task HandleValidSubmit() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// This is where we are going to upload stuff ! &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JsRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Files uploaded!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>É um pouco de código, mas basicamente renderizará o formulário e tratará da validação.&lt;/p>
&lt;img src="https://i.gyazo.com/00d0ad6d23fa93bdb0fb354218018c5f.png"/>
&lt;p>Se você pressionar o botão para fazer upload e estiver tudo bem, será exibido um alerta.&lt;/p>
&lt;img src="https://i.gyazo.com/27a3ff831fa9936bac21d8aa8ff60936.gif"/>
&lt;h2 id="fazendo-upload-para-o-armazenamento-de-blobs-do-azure">Fazendo upload para o Armazenamento de Blobs do Azure&lt;/h2>
&lt;p>Agora que concluímos a maior parte da interface do usuário, vamos instalar o pacote que manipulará a API do Azure Blob Storage e nos permitirá fazer upload de arquivos. Muito simples.&lt;/p>
&lt;h3 id="adicione-o-pacote-nuget">Adicione o pacote NuGet&lt;/h3>
&lt;p>Vamos gerenciar os pacotes NuGet do projeto e adicionar &lt;code>Azure.Storage.Blob&lt;/code>.&lt;/p>
&lt;img src="https://i.gyazo.com/c6ecd7b13c0a63a50f38342407acee49.png"/>
&lt;h3 id="carregar-para-azure-blob-stoare">Carregar para Azure Blob Stoare&lt;/h3>
&lt;p>Agora vamos fazer a lógica para realmente fazer o upload do arquivo, normalmente usaríamos a string de conexão e a chave do app.config, mas para o propósito deste tutorial, usamos entradas e fornecemos os dados lá.&lt;/p>
&lt;p>Primeiro, vamos adicionar o &lt;code>using&lt;/code> para a classe Azure Blob Storage&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@using Azure.Storage.Blobs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using Azure.Storage.Blobs.Models
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Então vamos trabalhar no método &lt;code>HandleValidSubmit&lt;/code>, que vai se conectar ao armazenamento de blob, criar o contêiner se ele não existir e depois fazer upload dos arquivos.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task HandleValidSubmit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Instantiate container&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> container = &lt;span style="color:#ff7b72">new&lt;/span> BlobContainerClient(modal.ConnectionString, modal.Container);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create container if not exists&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> createResponse = &lt;span style="color:#ff7b72">await&lt;/span> container.CreateIfNotExistsAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Set access policy&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (createResponse != &lt;span style="color:#79c0ff">null&lt;/span> &amp;amp;&amp;amp; createResponse.GetRawResponse().Status == &lt;span style="color:#a5d6ff">201&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> container.SetAccessPolicyAsync(Azure.Storage.Blobs.Models.PublicAccessType.Blob);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// For each file that we have uploaded&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> file &lt;span style="color:#ff7b72">in&lt;/span> selectedFiles)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// New blob&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> blob = container.GetBlobClient(file.Name);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Delete any blob with the same name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> blob.DeleteIfExistsAsync(Azure.Storage.Blobs.Models.DeleteSnapshotsOption.IncludeSnapshots);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create a file stream and use the UploadSync method to upload the Blob.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> fileStream = file.OpenReadStream())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> blob.UploadAsync(fileStream, &lt;span style="color:#ff7b72">new&lt;/span> BlobHttpHeaders { ContentType = file.ContentType });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Display success message&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JsRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Files uploaded!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Display error message&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JsRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;An error ocurred!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="testando-o-código">Testando o código&lt;/h3>
&lt;p>Agora é hora de testar o código, para isso basta preencher o formulário e clicar em enviar.&lt;/p>
&lt;img src="https://i.gyazo.com/4ce26ec6487038f3bc70c8e977b16215.png"/>
&lt;p>Iremos exibir um alerta e o arquivo deverá ser carregado no Azure Blob Storage, acesse o Portal do Azure e dê uma olhada.&lt;/p>
&lt;img src="https://i.gyazo.com/8c070489d4aa6a9f917d685a2ba670f3.png"/>
&lt;p>Como você pode ver, o container também foi criado, agora se entrarmos no container, vemos o arquivo que carregamos.&lt;/p>
&lt;img src="https://i.gyazo.com/166076b94e065578a3aaa4012d3b5c37.png"/>
&lt;p>Bom trabalho!&lt;/p>
&lt;h3 id="refatorar">Refatorar&lt;/h3>
&lt;p>Agora que tudo funcionou, vamos mover o código da página &lt;code>.razor&lt;/code> para uma classe &lt;code>.razor.cs&lt;/code> para que fique melhor.&lt;/p>
&lt;p>Se você estiver usando o Visual Studio 2022, há uma lâmpada quando você passa o mouse no &lt;code>@code&lt;/code>, ele mostrará uma opção para &lt;code>Extract block to code behind&lt;/code>, e fará o que diz!&lt;/p>
&lt;img src="https://i.gyazo.com/1a4b52c9adf1728860b1965bc9b11bdd.png"/>
&lt;p>Agora você vai ter tudo separado e pronto!&lt;/p>
&lt;h1 id="código">Código&lt;/h1>
&lt;p>Todo esse projeto está no Github e você pode encontrá-lo &lt;a href="https://github.com/emimontesdeoca/UploadingFilesAzBlobBlazor">aqui&lt;/a>!&lt;/p>
&lt;p>Se você tiver qualquer problema ou dúvida, sinta-se à vontade para entrar em contato comigo em qualquer mídia social em @emimontesdeoca (no Twitter na verdade é &lt;code>@emimontesdeocaa&lt;/code> com dois &lt;code>aa&lt;/code> no final). Você também pode encontrar a maioria das minhas redes sociais no cabeçalho do blog.&lt;/p>
&lt;p>Espero que você tenha gostado da postagem! Cia!&lt;/p>
&lt;h1 id="recursos">Recursos&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.nuget.org/packages/Azure.Storage.Blobs">https://www.nuget.org/packages/Azure.Storage.Blobs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet">https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>Azure</category><category>NuGet</category><category>Docker</category></item><item><title>Substituindo um controlador principal do Pimcore</title><link>https://emimontesdeoca.github.io/pt/posts/override-method-pimcore-core-bundles/</link><pubDate>Thu, 10 Dec 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/override-method-pimcore-core-bundles/</guid><description>Substitua os principais controladores Pimcore criando pacotes Symfony personalizados com configuração de serviço.</description><content:encoded>&lt;p>﻿Estou trabalhando em um projeto Pimcore há algum tempo, nunca toquei em PHP e Symfony na minha vida, então tem sido um grande desafio.&lt;/p>
&lt;p>A documentação é ótima, quero dizer, sério, é ótima, mas parece que não foi feita para iniciantes na linguagem/framework, então tudo o que fiz tive que anotar.&lt;/p>
&lt;p>Depois de aprender que você pode estender o Pimcore usando pacotes, passei horas e horas tentando substituir controladores, métodos javascript e muito mais.&lt;/p>
&lt;p>Então neste post vou explicar como consegui sobrescrever um controlador. Como substituir arquivos javascript virá mais tarde 😎.&lt;/p>
&lt;h1 id="criando-o-pacote">Criando o pacote&lt;/h1>
&lt;p>Primeiro de tudo precisamos criar um pacote para ele, é bem simples como está documentado na &lt;a href="https://pimcore.com/docs/pimcore/current/Development_Documentation/Extending_Pimcore/Bundle_Developers_Guide/index.html">documentação do Pimcore&lt;/a>, só precisamos executar &lt;code>bin/console pimcore:generate:bundle --namespace=EmiDemo/EmiDemoBundle&lt;/code> na pasta do projeto.&lt;/p>
&lt;p>Isso levantará algumas perguntas, mas nada com que se preocupar.&lt;/p>
&lt;div style="text-align:center">&lt;img src="https://i.gyazo.com/9aa04169e668506d18638388d0061910.png" />&lt;/div>
&lt;p>Irá criar uma nova pasta na pasta &lt;code>src&lt;/code> com o namespace que declaramos antes, e dentro teremos todos os arquivos necessários para o pacote.&lt;/p>
&lt;div style="text-align:center">&lt;img src="https://i.gyazo.com/4d3329c303bb43012faaae43290c613b.png" />&lt;/div>
&lt;p>Além disso, o plugin será detectado pelo site de administração do Pimcore, mas será desabilitado, você deve habilitá-lo para começar a usá-lo.&lt;/p>
&lt;div style="text-align:center">&lt;img src="https://i.gyazo.com/e0170db9111df69a00bdb3f10473c9a7.png" />&lt;/div>
&lt;p>#Substituindo um método de um controlador&lt;/p>
&lt;p>Para sobrescrever um método em um controlador, primeiro obviamente você precisa encontrar a ação que deseja atualizar, isso parece fácil, mas vou lhe mostrar como costumo fazer (aprendi com meu colega de equipe &lt;a href="https://twitter.com/cesabreu">Cesar&lt;/a>).&lt;/p>
&lt;p>Primeiro entre na página que você acha que o controlador entra em ação, no meu caso quero verificar aquela que carrega quando abrimos um ativo&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/df3833858806b14a39f50a0707a19dcd">&lt;img src="https://i.gyazo.com/df3833858806b14a39f50a0707a19dcd.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Depois é só abrir o console e ir na aba rede, fazer os mesmos passos novamente e tentar encontrar a ação&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/5fbd9496d585d145bea3f9a3b950de73">&lt;img src="https://i.gyazo.com/5fbd9496d585d145bea3f9a3b950de73.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Com essas informações você pode ver que a ação que carrega o ativo é &lt;code>http://localhost/admin/asset/get-data-by-id?_dc=1607601778450&amp;amp;id=2&amp;amp;type=image&lt;/code>. A partir disso podemos ver que a ação do controlador é &lt;code>get-data-by-id&lt;/code>.&lt;/p>
&lt;h2 id="encontre-a-ação-no-controlador-principal">Encontre a ação no controlador principal&lt;/h2>
&lt;p>Para mim, a maneira mais simples de fazer isso é encontrar em todos os arquivos do Visual Studio Code&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/96cbeea01a1174d45e5a263a997c882a">&lt;img src="https://i.gyazo.com/96cbeea01a1174d45e5a263a997c882a.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Provavelmente encontrará mais de um então você deve dar uma olhada e decidir qual é, no nosso caso estamos trabalhando com assets então fica claro que queremos usar o &lt;code>AssetController.php&lt;/code>.&lt;/p>
&lt;h2 id="modifique-o-controlador-principal">Modifique o controlador principal&lt;/h2>
&lt;p>Depende do desenvolvedor, normalmente não faço isso porque é mais rápido simplesmente substituir o controlador e desenvolver a partir daí. Mas eu recomendo primeiro atualizar o controlador no pacote principal para ver se suas alterações funcionam.&lt;/p>
&lt;p>No nosso caso retornarei apenas uma mensagem no início do método para verificar se funciona.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">$this&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&amp;gt;&lt;/span>adminJson([&lt;span style="color:#a5d6ff">&amp;#39;success&amp;#39;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#ff7b72">false&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#39;message&amp;#39;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Overriding the getDataByIdAction in the core!!&amp;#34;&lt;/span>]);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://gyazo.com/32139d67b9e5369d5ab38daed1b229ea">&lt;img src="https://i.gyazo.com/32139d67b9e5369d5ab38daed1b229ea.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Então vamos recarregar nosso ativo e verificar a aba rede&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/d1653f36e7bece9da6232ede0a431c05">&lt;img src="https://i.gyazo.com/d1653f36e7bece9da6232ede0a431c05.png" alt="Imagem de Gyazo">&lt;/a>Vamos manter o que temos lá, para mais tarde sabermos que estamos usando a mensagem do pacote em vez da mensagem principal.&lt;/p>
&lt;p>Agora vamos apenas copiar isso para um arquivo temporário para acompanhar o que fizemos.&lt;/p>
&lt;h2 id="mova-essas-alterações-para-o-pacote">Mova essas alterações para o pacote&lt;/h2>
&lt;p>Agora, para mover essas alterações para o nosso pacote, primeiro precisamos de algumas coisas do nosso controlador principal:&lt;/p>
&lt;ul>
&lt;li>Espaço para nome&lt;/li>
&lt;li>Importações&lt;/li>
&lt;li>Nome do controlador&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://gyazo.com/416b3a527be397aaf6d9f89073c02428">&lt;img src="https://i.gyazo.com/416b3a527be397aaf6d9f89073c02428.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>E obviamente o método &lt;code>getDataByIdAction&lt;/code> que é aquele que queremos substituir.&lt;/p>
&lt;h2 id="exponha-o-controlador">Exponha o controlador&lt;/h2>
&lt;p>Precisamos expor o controlador, para fazer isso você precisa entrar no arquivo &lt;code>/src/EmiDemo/EmiDemoBundle/Resources/config/pimcore/routing.yml&lt;/code> e adicionar&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">options&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">expose&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#79c0ff">true&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Também precisamos mudar o &lt;code>prefix&lt;/code> para aquele que iremos substituir, no nosso caso o controlador &lt;code>admin&lt;/code>.&lt;/p>
&lt;p>Então no final você vai ficar assim&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">emi_demo_emi_demo&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">resource&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@EmiDemoEmiDemoBundle/Controller/&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">type&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">annotation&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">prefix&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">/admin&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">options&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">expose&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#79c0ff">true&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="defaultcontrollerphp">DefaultController.php&lt;/h2>
&lt;p>Em nosso arquivo, adicionaremos as importações e estenderemos o controlador com aquele que substituiremos&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/b8d798436ed111d4676312f8aeba443d">&lt;img src="https://i.gyazo.com/b8d798436ed111d4676312f8aeba443d.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Então iremos apenas copiar o método do controlador principal e, no nosso caso, atualizar a mensagem que retornamos.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/dcdd9f8166e4ba8820cb8e285c43dec8">&lt;img src="https://i.gyazo.com/dcdd9f8166e4ba8820cb8e285c43dec8.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Se você se lembra, já temos uma mensagem no controlador principal: &lt;code>Overriding the getDataByIdAction in the core!!&lt;/code> e agora devemos ver &lt;code>Overriding the getDataByIdAction in the bundle!!&lt;/code>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/8e16b1de2a40292c843c51576acf43c4">&lt;img src="https://i.gyazo.com/8e16b1de2a40292c843c51576acf43c4.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Vamos apenas não retornar nada e ver que agora podemos ver a página como era antes&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/dda8df5f6d721f5ba25d6a056ac7f9bf">&lt;img src="https://i.gyazo.com/dda8df5f6d721f5ba25d6a056ac7f9bf.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Comente a declaração de retorno e recarregue&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/2fdbff7f45c38fb2bc56a3fc73661077">&lt;img src="https://i.gyazo.com/2fdbff7f45c38fb2bc56a3fc73661077.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="é-isso">É isso&lt;/h2>
&lt;p>E com esta pequena demonstração você pode ver como é fácil substituir um controlador central Pimcore existente. Existem alguns passos que você deve seguir, mas não é nada difícil. Apenas certifique-se de expor o controlador e limpar o cache de vez em quando enquanto estiver fazendo alterações, às vezes ele fica armazenado em cache e você fica pensando que não está funcionando e apenas está armazenado em cache.&lt;/p>
&lt;p>Se você está se perguntando como substituir os arquivos javascript, isso virá em outro tutorial 😁.&lt;/p></content:encoded></item><item><title>Configure o Apple Magic Keyboard 2 no Windows 10</title><link>https://emimontesdeoca.github.io/pt/posts/configure-apple-keyboard-windows/</link><pubDate>Wed, 11 Nov 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/configure-apple-keyboard-windows/</guid><description>Instale os drivers e configure o Apple Magic Keyboard 2 para funcionar corretamente no Windows 10.</description><content:encoded>&lt;p>&lt;a href="https://gyazo.com/9c8641cdd22bd528b2141bad1322c74a">&lt;img src="https://i.gyazo.com/9c8641cdd22bd528b2141bad1322c74a.jpg" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Ainda ontem comprei um Apple Magic Keyboard 2, mesmo tendo uns 5 teclados mecânicos, porque queria experimentar e era wireless.&lt;/p>
&lt;p>Meu sistema operacional principal é o Windows 10, adoro ele e não quero trocá-lo então pensando nisso sabia que seria necessário fazer algumas coisas para que o teclado funcionasse perfeitamente. Eu sei como a Apple funciona e como eles gostam de manter seus dispositivos em seu ecossistema.&lt;/p>
&lt;h2 id="problemas">Problemas&lt;/h2>
&lt;p>Se você emparelhar o teclado, reconhecerá algumas coisas:&lt;/p>
&lt;p>*As teclas de função não funcionam&lt;/p>
&lt;ul>
&lt;li>Algumas teclas estão mapeadas incorretamente (isso aconteceu comigo na versão em espanhol)&lt;/li>
&lt;/ul>
&lt;h2 id="documentação">Documentação&lt;/h2>
&lt;p>Para fazer funcionar tive que ler muito na web, mas esses dois links são os que me ajudaram a fazer funcionar:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.bluetoothgoodies.com/info/apple-devices/">Faça pleno uso do Apple Magic Keyboard/Mouse/Trackpad no Windows&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://superuser.com/questions/82826/how-do-i-use-my-f-1-f12-keys-without-pressing-fn-on-windows-7-using-bootcamp-o">Como faço para usar minhas teclas f-1 - f12 sem pressionar FN no Windows 7 usando bootcamp em um Macbook Pro?&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="instalando-o-driver-do-teclado-apple">Instalando o driver do teclado Apple&lt;/h2>
&lt;p>Algumas dessas etapas são da documentação mencionada anteriormente:&lt;/p>
&lt;ol>
&lt;li>Instale o &lt;a href="https://www.7-zip.org/">7zip&lt;/a> no seu computador, caso ainda não o tenha.&lt;/li>
&lt;li>Instale &lt;a href="https://www.python.org/downloads/">Python (versão 2.x)&lt;/a> em seu computador, caso não o tenha.
&lt;ul>
&lt;li>IMPORTANTE: A versão mais recente do Python é 3.x. Porém, você precisa da versão 2.x porque o script brigadeiro não é compatível com a versão 3.x.&lt;/li>
&lt;li>(opção) O instalador, por padrão, não adiciona python.exe ao seu PATH. Se desejar, você precisa habilitar esta opção. (veja a imagem à direita)&lt;/li>
&lt;li>Se você já possui outra versão do Python, provavelmente não deseja ativar esta opção.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Baixe o brigadier (um script Python que ajuda você a baixar a versão mais recente do Boot Camp).&lt;/li>
&lt;li>Clique com o botão direito no link a seguir e salve o arquivo usando &amp;ldquo;Salvar link como&amp;hellip;&amp;rdquo;. &lt;a href="https://raw.githubusercontent.com/timsutton/brigadier/master/brigadier">https://raw.githubusercontent.com/timsutton/brigadier/master/brigadier&lt;/a>&lt;/li>
&lt;li>Abra a janela do prompt de comando (também conhecida como caixa DOS) e mude o diretório para onde você baixou o script brigadier.&lt;/li>
&lt;li>Supondo que o script brigadier foi salvo como &amp;ldquo;brigadier.txt&amp;rdquo;, execute o seguinte comando:
&lt;ul>
&lt;li>Se o Python versão 2.x estiver em seu PATH: python brigadier.txt &amp;ndash;model=MacBook13,2&lt;/li>
&lt;li>Caso contrário: [Caminho para a versão 2.x do Python]\python.exe brigadier.txt &amp;ndash;model=MacBook13,2&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Ele irá baixar um grande pacote com todos os drivers do bootcamp&lt;/li>
&lt;li>Crie uma pasta chamada &lt;code>BootCamp&lt;/code> e copie &lt;code>BootCamp-xxx-yyyyyy\BootCamp\Drivers\Apple\BootCamp.msi&lt;/code> e &lt;code>BootCamp-xxx-yyyyyy\BootCamp\Drivers\Apple\AppleKeyboardMagic2&lt;/code> nela.&lt;/li>
&lt;li>Execute um powershell de administrador e execute o &lt;code>BootCamp.msi&lt;/code>, ele irá instalar algumas coisas, mas precisamos atualizar o driver usando o conteúdo da pasta &lt;code>AppleKeyboardMagic2&lt;/code>&lt;/li>
&lt;li>Inicie o Gerenciador de Dispositivos (&lt;code>devmgmt.msc&lt;/code>)&lt;/li>
&lt;li>Expanda o nó &lt;code>Human Interface Devices&lt;/code>&lt;/li>
&lt;li>Procure por &lt;code>Bluetooth HID Device&lt;/code>&lt;/li>
&lt;li>Atualize o driver usando o conteúdo da pasta &lt;code>AppleKeyboardMagic2&lt;/code>&lt;/li>
&lt;li>Reinicie o computador&lt;/li>
&lt;/ol>
&lt;p>Você deverá ver o teclado bluetooth agora detectado como um teclado Apple&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/278f6bd3e419d6688ccfadf6918ff309">&lt;img src="https://i.gyazo.com/278f6bd3e419d6688ccfadf6918ff309.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h3 id="atualizar-comportamento-das-teclas-fnse-você-instalou-tudo-corretamente-você-notará-que-as-teclas-fn-estão-habilitadas-por-padrão-isso-significa-que-você-precisa-pressionar-fn--f5-para-realmente-pressionar-o-botão-f5">Atualizar comportamento das teclas FNSe você instalou tudo corretamente você notará que as teclas FN estão habilitadas por padrão, isso significa que você precisa pressionar &lt;code>fn&lt;/code> + &lt;code>F5&lt;/code> para realmente pressionar o botão &lt;code>F5&lt;/code>.&lt;/h3>
&lt;p>Para corrigir isso, encontrei uma solução, indicada na seção de documentação, que funciona alterando alguma entrada no regedit.&lt;/p>
&lt;ol>
&lt;li>Abra o regedit&lt;/li>
&lt;li>Vá para &lt;code>HKEY_CURRENT_USER\SOFTWARE\Apple Inc.\Apple Keyboard Support&lt;/code>&lt;/li>
&lt;li>Crie ou atualize &lt;code>OSXFnBehavior&lt;/code> e defina-o como &lt;code>0&lt;/code>&lt;/li>
&lt;li>Reinicie o computador&lt;/li>
&lt;/ol>
&lt;h3 id="atualizar-mapeamento-de-chaves">Atualizar mapeamento de chaves&lt;/h3>
&lt;p>Se você tiver algum problema com os mapeamentos, você pode usar &lt;a href="https://www.randyrants.com/category/sharpkeys/">SharpKeys&lt;/a> e atualizá-los.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ee0301205ffeddaae4241db40002864d">&lt;img src="https://i.gyazo.com/ee0301205ffeddaae4241db40002864d.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>É muito simples de usar, mas lembre-se de sair ou reiniciar o computador para habilitar as atualizações, pois atualiza o registro.&lt;/p>
&lt;p>No meu caso, tive que atualizar as chaves &lt;code>Windows&lt;/code>, &lt;code>alt&lt;/code>, &lt;code>º&lt;/code> e &lt;code>&amp;lt;&amp;gt;&lt;/code>.&lt;/p></content:encoded></item><item><title>Adicionando bibliotecas compiladas a um pacote NuGet</title><link>https://emimontesdeoca.github.io/pt/posts/adding-compiled-dll-to-nuget/</link><pubDate>Thu, 01 Oct 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/adding-compiled-dll-to-nuget/</guid><description>Corrija DLLs ausentes em pacotes NuGet incluindo referências de biblioteca compiladas no arquivo nuspec.</description><content:encoded>&lt;p>Outro dia encontrei um bug sobre um &lt;code>NullReferenceException&lt;/code> em uma biblioteca ausente que deveria estar lá, pois vem de um pacote nuget que carregamos.&lt;/p>
&lt;p>Basicamente, compilei uma classe de biblioteca que fazia referência a alguns projetos. Na fase de construção, ele adicionaria os arquivos &lt;code>dll&lt;/code> à pasta &lt;code>bin&lt;/code>, mas quando &lt;code>packaging&lt;/code> fosse um pacote nuget e os instalasse em outra solução, os arquivos &lt;code>dll&lt;/code> que mencionei e deveriam ser copiados para a pasta bin, não estão lá.&lt;/p>
&lt;h3 id="como-resolver">Como resolver&lt;/h3>
&lt;p>Resolver este problema é bem simples, você tem que atualizar o arquivo &lt;code>nuspec&lt;/code> e para cada uma das bibliotecas que deseja copiar, adicione o &lt;code>file&lt;/code> na parte &lt;code>files&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;package&lt;/span> &lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;metadata&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;id&amp;gt;&lt;/span>$id$&lt;span style="color:#7ee787">&amp;lt;/id&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;version&amp;gt;&lt;/span>$version$&lt;span style="color:#7ee787">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;title&amp;gt;&lt;/span>$title$&lt;span style="color:#7ee787">&amp;lt;/title&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;authors&amp;gt;&lt;/span>$author$&lt;span style="color:#7ee787">&amp;lt;/authors&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;owners&amp;gt;&lt;/span>$author$&lt;span style="color:#7ee787">&amp;lt;/owners&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;requireLicenseAcceptance&amp;gt;&lt;/span>false&lt;span style="color:#7ee787">&amp;lt;/requireLicenseAcceptance&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;license&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;expression&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>MIT&lt;span style="color:#7ee787">&amp;lt;/license&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;projectUrl&amp;gt;&lt;/span>$projectUrl$&lt;span style="color:#7ee787">&amp;lt;/projectUrl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;iconUrl&amp;gt;&lt;/span>$projectIconUrl$&lt;span style="color:#7ee787">&amp;lt;/iconUrl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;description&amp;gt;&lt;/span>$description$&lt;span style="color:#7ee787">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;releaseNotes&amp;gt;&amp;lt;/releaseNotes&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;copyright&amp;gt;&lt;/span>$copyright$&lt;span style="color:#7ee787">&amp;lt;/copyright&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;tags&amp;gt;&amp;lt;/tags&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/metadata&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;files&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;file&lt;/span> src=&lt;span style="color:#a5d6ff">&amp;#34;bin\Release\MyFile1.dll&amp;#34;&lt;/span> target=&lt;span style="color:#a5d6ff">&amp;#34;lib\net47&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;file&lt;/span> src=&lt;span style="color:#a5d6ff">&amp;#34;bin\Release\MyFile2.dll&amp;#34;&lt;/span> target=&lt;span style="color:#a5d6ff">&amp;#34;lib\net47&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;file&lt;/span> src=&lt;span style="color:#a5d6ff">&amp;#34;bin\Release\MyFile3.dll&amp;#34;&lt;/span> target=&lt;span style="color:#a5d6ff">&amp;#34;lib\net47&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;file&lt;/span> src=&lt;span style="color:#a5d6ff">&amp;#34;bin\Release\MyFile4.dll&amp;#34;&lt;/span> target=&lt;span style="color:#a5d6ff">&amp;#34;lib\net47&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/files&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/package&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content:encoded><category>NuGet</category></item><item><title>Faça o carregamento do terminal no diretório inicial do WSL</title><link>https://emimontesdeoca.github.io/pt/posts/bash-straight-to-wsl-machine-with-terminal/</link><pubDate>Tue, 22 Sep 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/bash-straight-to-wsl-machine-with-terminal/</guid><description>Configure o Terminal do Windows para abrir sessões WSL diretamente no diretório inicial do Linux via .bashrc.</description><content:encoded>&lt;p>Tenho adorado o WSL para desenvolvimento e testes desde o dia em que foi lançado, então o aplicativo Terminal caiu e eu não poderia estar mais feliz, parece bom, funciona perfeitamente e ótimo desempenho.&lt;/p>
&lt;p>Seria estúpido não usá-lo com a distribuição WSL em vez de usar o console que ela oferece.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/245fb4ff4fbd5297b2a8d9917dbee236">&lt;img src="https://i.gyazo.com/245fb4ff4fbd5297b2a8d9917dbee236.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Este console é obviamente bom, mas depois de usar o Terminal, não há como voltar atrás. É muito melhor, também &lt;em>cor&lt;/em>.&lt;/p>
&lt;p>Agora, se você iniciar o terminal e abrir uma nova aba para carregar sua distribuição WSL, ele carregará sua pasta de usuário montada.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/dac1264f7b8dbae1f64e769b64c551da">&lt;img src="https://i.gyazo.com/dac1264f7b8dbae1f64e769b64c551da.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Isso não é realmente um problema, porque se você apenas fizer &lt;code>cd ~&lt;/code> ele apenas carregará a pasta do usuário da distribuição.&lt;/p>
&lt;p>O problema é que não quero fazer isso de novo e de novo, então vamos atualizar um arquivo para fazer isso sozinho.&lt;/p>
&lt;h2 id="bashrc">.bashrc&lt;/h2>
&lt;p>Abra o terminal ou console, vá para a pasta do seu perfil e edite o arquivo &lt;code>.bashrc&lt;/code> usando seu editor de texto favorito.&lt;/p>
&lt;p>Adicione &lt;code>cd ~&lt;/code> no final do arquivo antes da última instrução.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/b601aba59b9e877bc3926ab9ceb2b98c">&lt;img src="https://i.gyazo.com/b601aba59b9e877bc3926ab9ceb2b98c.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Salve, reabra o console WSL com o terminal e você deverá estar na pasta de perfis.&lt;/p>
&lt;p>Tenha em mente que você terá que fazer isso para todas as suas instalações WSL, já que estamos atualizando o arquivo &lt;code>bashrc&lt;/code> para uma única instalação.&lt;/p></content:encoded></item><item><title>Geração dinâmica de objetos usando ExpandoObject</title><link>https://emimontesdeoca.github.io/pt/posts/dynamic-object-generation-expando-object/</link><pubDate>Fri, 06 Mar 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/dynamic-object-generation-expando-object/</guid><description>Use ExpandoObject para criar objetos dinamicamente com propriedades definidas em tempo de execução para exportação flexível de dados.</description><content:encoded>&lt;p>Eu estava precisando converter arquivos do Excel com suas próprias colunas definidas para um novo com colunas dinâmicas e fiquei meio confuso sobre como fazer isso corretamente, sem sérios problemas de desempenho.&lt;/p>
&lt;p>Cheguei a um tutorial que você pode encontrar &lt;a href="https://www.oreilly.com/content/building-c-objects-dynamically/">aqui&lt;/a> que usa .NET &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.dynamic.expandoobject?view=netframework-4.8">ExpandoObject&lt;/a> que praticamente permite criar um objeto e adicionar membros dinâmicos.&lt;/p>
&lt;h2 id="expandoobject">ExpandoObject&lt;/h2>
&lt;p>A definição da Microsoft é:&lt;/p>
&lt;blockquote>
&lt;p>Representa um objeto cujos membros podem ser adicionados e removidos dinamicamente em tempo de execução.&lt;/p>&lt;/blockquote>
&lt;p>E tem algumas observações:&lt;/p>
&lt;blockquote>
&lt;p>A classe ExpandoObject permite adicionar e excluir membros de suas instâncias em tempo de execução e também definir e obter valores desses membros. Esta classe suporta ligação dinâmica, o que permite usar sintaxe padrão como sampleObject.sampleMember em vez de sintaxe mais complexa como sampleObject.GetAttribute(&amp;ldquo;sampleMember&amp;rdquo;).&lt;/p>&lt;/blockquote>
&lt;h2 id="comportamento-atual">Comportamento atual&lt;/h2>
&lt;p>Temos um &lt;code>ExcelExportService&lt;/code> que, ao passar um &lt;code>List&amp;lt;T&amp;gt;&lt;/code>, neste caso &lt;code>ExcelItem&lt;/code>, usará &lt;code>Reflection&lt;/code> para criar um arquivo &lt;code>xlsx&lt;/code>.&lt;/p>
&lt;p>Até agora, nosso código está assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ExcelItem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Surname { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Age { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> CreatedAt { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> UpdatedAt { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>ExcelItem&lt;/code> é o objeto com todas as propriedades que estão sendo utilizadas para gerar o arquivo excel.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">byte&lt;/span>[] GetExcelBytes() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;ExcelItem&amp;gt; excelItems = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;ExcelItem&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExcelExportService exportService = &lt;span style="color:#ff7b72">new&lt;/span> ExcelExportService();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> items &lt;span style="color:#ff7b72">in&lt;/span> itemsToExcel)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> excelItems.Add(&lt;span style="color:#ff7b72">new&lt;/span> ExcelItem()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Id = item.Id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = item.Name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Surname = item.Surname,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Age = item.Age,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CreatedAt = item.CreatedAt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UpdatedAt = item.UpdatedAt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> exportService.ExportToExcel(excelItems);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esta abordagem funciona perfeitamente, o arquivo &lt;code>xlsx&lt;/code> é gerado sem problemas com cada coluna sendo cada propriedade, você usaria este método &lt;code>GetExcelBytes&lt;/code> assim, por exemplo, para salvá-lo em um arquivo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>File.WriteAllBytes(&lt;span style="color:#a5d6ff">&amp;#34;path-to-somewhere/my-file.xlsx&amp;#34;&lt;/span>, GetExcelBytes());
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="o-problema">O problema&lt;/h2>
&lt;p>Já que o que estamos fazendo certo é usar todas as propriedades do objeto &lt;code>ExcelItem&lt;/code>, tudo que eu quero é não usar todas elas, apenas usando talvez 2 ou 3 delas.&lt;/p>
&lt;p>Também é um requisito que eu não queira alterar o código, tudo deve ser feito pelo &lt;code>administrator&lt;/code>, que decidirá quais colunas devem ser mostradas, podendo ou não conhecer as propriedades do código.&lt;/p>
&lt;p>TLDR: tudo deve ser dinâmico, temos um objeto com propriedades e devemos ter certeza de que o arquivo gerado possui &lt;code>N&lt;/code> propriedades desse objeto, mas obviamente não codificado.&lt;/p>
&lt;h2 id="colunas-dinâmicas">Colunas dinâmicas&lt;/h2>
&lt;p>A mudança seria utilizar um objeto &lt;code>dynamic&lt;/code>, pois gostaríamos de definir quais propriedades do objeto serão utilizadas para gerar a lista de colunas.&lt;/p>
&lt;p>Digamos que temos um objeto com muitas propriedades, como &lt;em>uma tonelada&lt;/em> delas, e não queremos realmente alterar o objeto &lt;code>ExcelItem&lt;/code> toda vez que fizermos uma alteração, criamos uma tabela &lt;code>ColumnExcelItem&lt;/code>, que será usada para gerar esse &lt;code>ExcelItem&lt;/code>.&lt;/p>
&lt;h3 id="estrutura-do-banco-de-dados">Estrutura do banco de dados&lt;/h3>
&lt;p>Salvamos esta definição em nosso banco de dados com algo assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Id PropertyName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---------------------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 Name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 Surname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3 Age
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Observe que temos menos valores que as propriedades existentes do objeto &lt;code>ExcelItem&lt;/code>, sendo que ele possui propriedades &lt;code>6&lt;/code> e só temos &lt;code>3&lt;/code> listado no banco de dados.&lt;/p>
&lt;p>Observe também que &lt;code>PropertyName&lt;/code> deve corresponder ao nome da propriedade do objeto que usarei para a atribuição dinâmica.&lt;/p>
&lt;h2 id="construindo-o-objeto">Construindo o objeto&lt;/h2>
&lt;p>Agora que temos a tabela do banco de dados, precisamos construir o repositório para obtê-la e poder usá-la no código.Não farei um tutorial desta parte, vou pular logo no início da nova função &lt;code>GetExcelBytes()&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">byte&lt;/span>[] GetExcelBytes(List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; columns) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;ExcelItem&amp;gt; excelItems = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;ExcelItem&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;&lt;span style="color:#ff7b72">dynamic&lt;/span>&amp;gt; objectsToExcel = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">dynamic&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExcelExportService exportService = &lt;span style="color:#ff7b72">new&lt;/span> ExcelExportService();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> item &lt;span style="color:#ff7b72">in&lt;/span> itemsToExcel)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">dynamic&lt;/span> newObject = &lt;span style="color:#ff7b72">new&lt;/span> ExpandoObject();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> col &lt;span style="color:#ff7b72">in&lt;/span> columns)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.AddProperty(newObject, col, product.GetType().GetProperty(col).GetValue(item, &lt;span style="color:#79c0ff">null&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> objectsToExcel.Add(newObject);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> exportService.ExportToExcel(objectsToExcel);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora teremos a criação de um objeto com algumas das propriedades de &lt;code>ExcelItem&lt;/code>, mas completamente dinâmico. Em vez de usar todas as 6 propriedades, estamos usando apenas 3 delas, aquela que queremos apenas enviar.&lt;/p>
&lt;p>Agora vamos fingir que este objeto &lt;code>ExcelItem&lt;/code> tem 200 propriedades, &lt;em>louca&lt;/em>, mas isso pode acontecer.&lt;/p>
&lt;p>A única coisa que preciso fazer é inserir as propriedades que desejo renderizar no arquivo Excel naquela tabela &lt;code>ColumnExcelItem&lt;/code> e pronto.&lt;/p>
&lt;h2 id="casos">Casos&lt;/h2>
&lt;p>Neste caso usamos apenas um caso, mas digamos que você deseja ter relatórios diferentes para alguns usuários. Digamos que o papel &lt;code>admin&lt;/code> deva obter todas as propriedades, então você criaria algo como esta estrutura no banco de dados&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>ReportingTemplates
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Id PropertyName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---------------------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 Admin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 Users
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>ReportingTemplateItems
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Id TemplateId PropertyName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---------------------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 1 Id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 1 Name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3 1 Surname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>4 1 Age
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>5 1 CreatedAt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>6 1 UpdatedAt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>7 2 Name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>8 2 Surname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>9 2 Age
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Então, ao obter o modelo, você poderá ter colunas diferentes, todas dinamicamente e sem código relacionado.&lt;/p>
&lt;p>Se tivermos um usuário com a função &lt;code>Admins&lt;/code>, o arquivo conterá as colunas &lt;code>Id&lt;/code>, &lt;code>Name&lt;/code>, &lt;code>Surname&lt;/code>, &lt;code>Age&lt;/code>, &lt;code>CreatedAt&lt;/code>, &lt;code>UpdatedAt&lt;/code>.&lt;/p>
&lt;p>Caso você gere-o usando a função &lt;code>Users&lt;/code>, ele terá &lt;code>Name&lt;/code>, &lt;code>Age&lt;/code>. &lt;code>Surname&lt;/code>.&lt;/p>
&lt;h2 id="conclusão">Conclusão&lt;/h2>
&lt;p>Esta é uma maneira legal de aprender como o &lt;code>dynamic&lt;/code> funciona no .NET, e é uma solução muito boa quando você precisa ter funções ou modelos diferentes para usuários diferentes e não está realmente interessado em investir tempo codificando coisas.&lt;/p></content:encoded><category>.NET</category></item><item><title>Entrega contínua de pacote NuGet usando TravisCI</title><link>https://emimontesdeoca.github.io/pt/posts/automated-nuget-deployment-travis-ci/</link><pubDate>Tue, 21 Jan 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/automated-nuget-deployment-travis-ci/</guid><description>Automatize a compilação, o teste e a publicação de pacotes NuGet usando pipelines de entrega contínua do Travis CI.</description><content:encoded>&lt;p>﻿Recentemente fiz um &lt;a href="https://emimontesdeoca.github.io/2020/ci-dotnet-core-and-travis-ci/">tutorial&lt;/a> sobre como usar o Travis CI como uma ferramenta para testar automaticamente seu código que você acabou de enviar ou está tentando enviar para o branch &lt;code>master&lt;/code>. Fazendo com que ele seja compilado e testado e finalmente informe o status dessa mudança.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/e4c3f9019fdb8a8d80b2649f4c4bbbde">&lt;img src="https://i.gyazo.com/e4c3f9019fdb8a8d80b2649f4c4bbbde.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Mas podemos continuar &lt;strong>melhorando&lt;/strong>, por exemplo, a biblioteca principal que foi feita no último tutorial, quero torná-la pública usando o feed &lt;a href="https://www.nuget.org/">NuGet&lt;/a> para que todos possam facilitar a operação em suas bibliotecas!&lt;/p>
&lt;p>&lt;strong>Mas como?&lt;/strong> Como vamos compilar, testar e depois publicar esse pacote no feed para que todos possam baixá-lo em seus projetos?&lt;/p>
&lt;p>Bom, esse é o tutorial para 😁!&lt;/p>
&lt;h1 id="o-que-há-de-novo">O que há de novo?&lt;/h1>
&lt;p>Quase não há mudanças em relação ao código, as coisas que precisamos mudar são a embalagem, a publicação e como podemos automatizá-lo.&lt;/p>
&lt;h1 id="cli-do-net-core">CLI do .NET Core&lt;/h1>
&lt;p>Vamos usar o &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/tools/?tabs=netcore2x">.NET Core CLI&lt;/a>, usamos no tutorial anterior com comandos como &lt;code>dotnet restore&lt;/code>, &lt;code>dotnet build&lt;/code> e &lt;code>dotnet test&lt;/code>.&lt;/p>
&lt;p>Agora vamos usar um novo conjunto de comandos!&lt;/p>
&lt;p>#&lt;code>dotnet nuget&lt;/code>&lt;/p>
&lt;p>Você pode verificar a documentação deste conjunto de ferramentas &lt;a href="https://docs.microsoft.com/en-gb/nuget/reference/dotnet-commands">aqui&lt;/a>, mas uma breve explicação:&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>dotnet nuget&lt;/code> é um conjunto de ferramentas essenciais que inclui instalação, restauração, remoção e publicação de pacotes.&lt;/p>&lt;/blockquote>
&lt;h1 id="feed-nuget">feed NuGet&lt;/h1>
&lt;p>&lt;a href="https://www.nuget.org/">NuGet é o gerenciador de pacotes para .NET&lt;/a>. As ferramentas cliente NuGet fornecem a capacidade de produzir e consumir pacotes. A Galeria NuGet é o repositório central de pacotes usado por todos os autores e consumidores de pacotes.&lt;/p>
&lt;p>O NuGet é gratuito, então você pode se inscrever, pegar sua chave de API e começar a fazer upload de pacotes. Eles serão carregados, verificados e listados para &lt;strong>todos&lt;/strong>.&lt;/p>
&lt;h1 id="obtenha-a-chave-api">Obtenha a chave API&lt;/h1>
&lt;p>Para fazer upload do seu pacote, você precisará obter sua chave de API após a inscrição, então vá para a &lt;a href="https://www.nuget.org/account/apikeys">página de chaves de API&lt;/a> e crie uma nova.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/7509632471a35fb6b5b909699dec2520">&lt;img src="https://i.gyazo.com/7509632471a35fb6b5b909699dec2520.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>&lt;strong>Lembre-se de que a chave de API tem validade de 365 dias.&lt;/strong>&lt;/p>
&lt;p>Em seguida, copie sua chave e guarde-a em algum lugar, como dito na página, você não poderá vê-la novamente e, se não a salvou, terá que regenerá-la.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/2feb401c84034706d12a59f03bd30136">&lt;img src="https://i.gyazo.com/2feb401c84034706d12a59f03bd30136.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>#Gerar pacote NuGet&lt;/p>
&lt;p>Agora que temos a chave &lt;code>API&lt;/code>, nossa próxima etapa é gerar esse pacote NuGet a partir da solução, então vá em frente e abra a solução onde você tem sua biblioteca de classes.&lt;/p>
&lt;p>Usarei a solução do meu &lt;a href="https://emimontesdeoca.github.io/2020/ci-dotnet-core-and-travis-ci/">último tutorial&lt;/a>, que possui uma classe de biblioteca .NET Core chamada &lt;code>CalculatorCLI.Core&lt;/code>.&lt;/p>
&lt;p>Agora vá para &lt;strong>propriedades do projeto&lt;/strong> e depois &lt;strong>Pacote&lt;/strong>, você verá muitas informações sobre o pacote como versão do pacote, autores, descrições e muito mais.&lt;/p>
&lt;p>O próximo passo é preencher isso, você realmente não precisa preencher tudo, mas os importantes e obrigatórios são &lt;code>Package id&lt;/code>, &lt;code>Package version&lt;/code>, &lt;code>Authors&lt;/code> e &lt;code>Description&lt;/code>. Além disso, se você tentar fazer upload do arquivo sozinho, será solicitado &lt;code>License&lt;/code>, precisaremos adicioná-lo também.&lt;a href="https://gyazo.com/6becdace2915c40fa2091ed9916fe517">&lt;img src="https://i.gyazo.com/6becdace2915c40fa2091ed9916fe517.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Depois você pode salvar, clicar com o botão direito no projeto e selecionar &lt;code>Pack&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">1&lt;/span>&amp;gt;&lt;span style="color:#ff7b72;font-weight:bold">------&lt;/span> Build started: Project: CalculatorCLI.Core, Configuration: Debug Any CPU &lt;span style="color:#ff7b72;font-weight:bold">------&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">1&lt;/span>&amp;gt;CalculatorCLI.Core &lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&amp;gt; D:&lt;span style="color:#f85149">\&lt;/span>Development&lt;span style="color:#f85149">\&lt;/span>Personal&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>demo&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI.Core&lt;span style="color:#f85149">\&lt;/span>bin&lt;span style="color:#f85149">\&lt;/span>Debug&lt;span style="color:#f85149">\&lt;/span>netstandard2&lt;span style="color:#a5d6ff">.1&lt;/span>&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI.Core.dll
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">1&lt;/span>&amp;gt;Successfully created &lt;span style="color:#ff7b72">package&lt;/span> &lt;span style="color:#f85149">&amp;#39;&lt;/span>D:&lt;span style="color:#f85149">\&lt;/span>Development&lt;span style="color:#f85149">\&lt;/span>Personal&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>demo&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI.Core&lt;span style="color:#f85149">\&lt;/span>bin&lt;span style="color:#f85149">\&lt;/span>Debug&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI.Core&lt;span style="color:#a5d6ff">.1.0.0.1&lt;/span>.nupkg&lt;span style="color:#f85149">&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">==========&lt;/span> Build: &lt;span style="color:#a5d6ff">1&lt;/span> succeeded, &lt;span style="color:#a5d6ff">0&lt;/span> failed, &lt;span style="color:#a5d6ff">0&lt;/span> up&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>to&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>date, &lt;span style="color:#a5d6ff">0&lt;/span> skipped &lt;span style="color:#ff7b72;font-weight:bold">==========&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A saída mostrará que se tudo estiver bem, será impresso &lt;code>Successfully created package&lt;/code>.&lt;/p>
&lt;h1 id="usando-dotnet">Usando &lt;code>dotnet&lt;/code>&lt;/h1>
&lt;p>Assim como no tutorial anterior, usamos diferentes comandos para construir e testar a solução. Agora vamos adicionar mais comandos para empacotar e publicar os pacotes.&lt;/p>
&lt;p>Esses comandos são:&lt;/p>
&lt;ol>
&lt;li>&lt;code>dotnet pack -c &amp;lt;configuration&amp;gt;&lt;/code>&lt;/li>
&lt;li>&lt;code>dotnet nuget push &amp;lt;package&amp;gt; &amp;lt;k &amp;lt;apikey&amp;gt; -s &amp;lt;source&amp;gt;&lt;/code>&lt;/li>
&lt;/ol>
&lt;h1 id="scripts-de-implantação">Scripts de implantação&lt;/h1>
&lt;p>Como vamos mudar a forma como será a compilação, acredito que ter arquivos diferentes, cada um deles contendo uma definição de compilação dependendo do ambiente, seria a melhor ideia.&lt;/p>
&lt;p>Então vamos criar uma pasta chamada &lt;code>scripts&lt;/code> com três scripts &lt;code>bash&lt;/code> na raiz do repositório.&lt;/p>
&lt;p>&lt;code>scripts\compile.sh&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Restoring...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet restore &lt;span style="color:#a5d6ff">&amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Building...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet build &lt;span style="color:#a5d6ff">&amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34;&lt;/span> -c Release
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>scripts\test.sh&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Testing...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet test &lt;span style="color:#a5d6ff">&amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34;&lt;/span> -c Release -v n
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>scripts\push.sh&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Packing...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet pack ./CalculatorCLI/CalculatorCLI.Core/CalculatorCLI.Core.csproj -c Release
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Pushing...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet nuget push ./CalculatorCLI/CalculatorCLI.Core/bin/Release/*.nupkg -s &lt;span style="color:#a5d6ff">&amp;#34;https://nuget.org&amp;#34;&lt;/span> -k &lt;span style="color:#79c0ff">$NUGET_API_KEY&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="melhorando-o-arquivo-travisyml">Melhorando o arquivo &lt;code>.travis.yml&lt;/code>&lt;/h1>
&lt;p>Agora que temos o código fonte atualizado, sabemos os comandos que precisamos usar, precisamos integrar nossa entrega contínua com integração contínua. Com isso, não precisamos fazer nada além de codificar, testar, revisar e enviar.&lt;/p>
&lt;p>O que vamos fazer agora, com o arquivo &lt;code>.travis.yml&lt;/code>, é o seguinte:&lt;/p>
&lt;ol>
&lt;li>Adicione &lt;code>stages&lt;/code> diferente&lt;/li>
&lt;li>Cada &lt;code>stage&lt;/code> depende da filial&lt;/li>
&lt;li>Se sua compilação estiver no master, significa que o pacote deve ser atualizado, portanto, enviaremos nosso pacote para os feeds do NuGet&lt;/li>
&lt;li>Se sua compilação for uma solicitação pull, ainda iremos verificar se a compilação é compilada e testada, mas não iremos publicá-la.&lt;/li>
&lt;/ol>
&lt;h2 id="estágios">Estágios&lt;/h2>
&lt;p>Da documentação deles:&lt;/p>
&lt;blockquote>
&lt;p>Você pode filtrar e rejeitar compilações, estágios e trabalhos especificando condições em sua configuração de compilação (seu arquivo .travis.yml).&lt;/p>&lt;/blockquote>
&lt;p>Você pode encontrar mais informações sobre o TravisCI &lt;code>stages&lt;/code> na &lt;a href="https://docs.travis-ci.com/user/conditional-builds-stages-jobs">página de documentação&lt;/a>.&lt;/p>
&lt;p>Então vamos alterar o arquivo e adicionar as etapas, que fica assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">language&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">csharp&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">sudo&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">required&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">mono&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">none &lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">dotnet&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">3.0&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">os&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">linux&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">jobs&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">include&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">stage&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">compile&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">script&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">bash scripts/compile.sh&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">stage&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">test&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">script&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">bash scripts/test.sh&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">stage&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">deploy-prod&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">if&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">branch = master AND type = push&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Deploy to NuGet&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">script&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">bash scripts/push.sh&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Como você pode ver, temos três estágios diferentes &lt;code>compile&lt;/code>, &lt;code>test&lt;/code> e &lt;code>push&lt;/code>, sendo que o último só será executado quando o &lt;code>branch&lt;/code> for &lt;code>master&lt;/code> e não for um &lt;code>pull request&lt;/code> para ele, apenas um &lt;code>push&lt;/code>.&lt;/p>
&lt;p>Se você não entende isso, basta acessar a documentação e é explicado com bastante facilidade.&lt;/p>
&lt;p>Com isso em mente, todos os &lt;code>pull request&lt;/code> não enviarão nada para o feed do NuGet.&lt;/p>
&lt;h1 id="configurando-a-chave-api-como-uma-variável-de-ambiente">Configurando a chave API como uma variável de ambiente&lt;/h1>
&lt;p>Vá para as configurações de compilação do seu repositório e adicione uma nova variável de ambiente, chamada &lt;code>NUGET_API_KEY&lt;/code> com o valor sendo a chave de API copiada da página NuGet.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/68a4bcc4ce57eccd6bb25b7d62901c84">&lt;img src="https://i.gyazo.com/68a4bcc4ce57eccd6bb25b7d62901c84.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="crie-um-pull-request">Crie um &lt;code>pull request&lt;/code>&lt;/h2>
&lt;p>Agora que temos tudo configurado, é hora de fazer um &lt;code>pull request&lt;/code> e verificar se a compilação daquele pull request está ignorando o último &lt;code>stage&lt;/code>.&lt;/p>
&lt;h2 id="verifique-as-compilações">Verifique as compilações&lt;/h2>
&lt;p>Assim que você fizer a solicitação pull, um build será enfileirado no painel do TravisCI, e como você pode ver, temos 2 builds diferentes lá e não três.&lt;a href="https://gyazo.com/11a209e72a121aef82a5b5a80d77a2f0">&lt;img src="https://i.gyazo.com/11a209e72a121aef82a5b5a80d77a2f0.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Agora vamos aceitar o &lt;code>pull request&lt;/code> e verificar a compilação novamente para ver que agora temos três jobs em vez de dois.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/46dad20d00defb8c14279332a946e73e">&lt;img src="https://i.gyazo.com/46dad20d00defb8c14279332a946e73e.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>E assim que você ver a compilação sendo enfileirada, você poderá ver os três trabalhos, incluindo o &lt;code>Deploy-prod&lt;/code> no final.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/d24344e42f2b45225acb618ac030fd8f">&lt;img src="https://i.gyazo.com/d24344e42f2b45225acb618ac030fd8f.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Assim que toda a compilação for concluída, você poderá ver nos logs que o pacote foi enviado para as fontes.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/77b1e44b4f9059cb7266fb18fbace8a7">&lt;img src="https://i.gyazo.com/77b1e44b4f9059cb7266fb18fbace8a7.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>E obviamente você pode ver isso na página do NuGet&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/09421094730ea2e087cc5da639069ec4">&lt;img src="https://i.gyazo.com/09421094730ea2e087cc5da639069ec4.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/a7905ddfd6665bb6e8e62e160f21630d">&lt;img src="https://i.gyazo.com/a7905ddfd6665bb6e8e62e160f21630d.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>#É isso&lt;/p>
&lt;p>Neste tutorial, descobrimos como automatizar a entrega de pacotes NuGet. Usamos algumas ferramentas como &lt;code>TravisCI&lt;/code>, &lt;code>stages&lt;/code> e &lt;code>dotnet nuget&lt;/code>.&lt;/p>
&lt;p>Na minha opinião, mesmo que seja algo que levaria tempo para ser configurado corretamente e poderia ser muito complexo em alguns casos. Vale a pena investir tempo nesse tipo de técnica para deixar tudo perfeito e automatizado.&lt;/p>
&lt;p>Você pode encontrar o código fonte, com o arquivo &lt;code>.travis.yml&lt;/code> &lt;a href="https://github.com/emimontesdeoca/CalculatorCLI-demo">aqui&lt;/a>, se você tiver alguma dúvida, sinta-se à vontade para entrar em contato comigo no meu &lt;a href="https://twitter.com/emimontesdeocaa">twitter&lt;/a>!&lt;/p></content:encoded><category>.NET</category><category>NuGet</category><category>CI/CD</category></item><item><title>Integração contínua para um projeto .NET Core 3.0 usando TravisCI</title><link>https://emimontesdeoca.github.io/pt/posts/ci-dotnet-core-and-travis-ci/</link><pubDate>Tue, 14 Jan 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/ci-dotnet-core-and-travis-ci/</guid><description>Configure a integração contínua para projetos .NET Core usando Travis CI com compilações e testes automatizados.</description><content:encoded>&lt;p>Neste último fim de semana decidi que queria iniciar corretamente meu projeto scraper-checker-downloader que venho fazendo em diferentes repositórios.&lt;/p>
&lt;p>Depois de iniciar mais um projeto, isso tinha que ser legal, &lt;strong>muito legal&lt;/strong>, usando CI/CD, pull request, documentação, badges no readme, tudo que eu vi que é legal e de fato são as melhores práticas.&lt;/p>
&lt;p>E depois de um bom final de semana acabei criando o &lt;a href="https://github.com/Dramarr">Dramarr&lt;/a>, um conjunto de ferramentas que fazem scrap e baixam programas de diversas fontes.&lt;/p>
&lt;p>Possui diferentes repositórios na organização e a maioria deles são bibliotecas que estão sendo compiladas, testadas e implantadas no pull request e quando são mescladas no branch master.&lt;/p>
&lt;p>Isso é chamado de CI/CD ou integração contínua/entrega contínua.&lt;/p>
&lt;p>Mas neste tutorial falaremos apenas sobre CI.&lt;/p>
&lt;h1 id="integração-contínua">Integração contínua&lt;/h1>
&lt;h2 id="o-que-é-isso">O que é isso?&lt;/h2>
&lt;p>Retirado do [blog](&lt;a href="https://martinfowler.com/articles/continuousIntegration.html">https://martinfowler.com/articles/continuousIntegration.html&lt;/a> de Martin Fowler), que é a melhor explicação que li:&lt;/p>
&lt;blockquote>
&lt;p>Integração Contínua é uma prática de desenvolvimento de software onde os membros de uma equipe integram seu trabalho com frequência, geralmente cada pessoa integra pelo menos diariamente - levando a múltiplas integrações por dia.
Cada integração é verificada por uma construção automatizada (incluindo teste) para detectar erros de integração o mais rápido possível.&lt;/p>&lt;/blockquote>
&lt;h2 id="ferramentas">Ferramentas&lt;/h2>
&lt;p>Existem muitas ferramentas para integrar seu fluxo de trabalho com CI/CD, mas para este tutorial usaremos &lt;a href="https://github.com/">Github&lt;/a> para armazenar nosso código e as ferramentas &lt;a href="https://travis-ci.org/">TravisCI&lt;/a> para configurar o CI. Em relação à linguagem e frameworks, utilizaremos C# e o novo .NET Core 3.0.&lt;/p>
&lt;h1 id="requisitos">Requisitos&lt;/h1>
&lt;p>Para fazer isso funcionar, você precisa de três coisas simples:&lt;/p>
&lt;ol>
&lt;li>Versão mais recente do Visual Studio 2019&lt;/li>
&lt;li>Conta Github&lt;/li>
&lt;li>Conta Travis-CI vinculada à sua conta Github&lt;/li>
&lt;/ol>
&lt;h1 id="projeto">Projeto&lt;/h1>
&lt;p>Para o propósito deste tutorial, faremos uma calculadora simples. Estaremos criando uma biblioteca, uma ferramenta de linha de comando e um projeto de teste para testar tudo.&lt;/p>
&lt;p>Este projeto de teste também estará em execução quando configurarmos o CI, o que significa que se no futuro fizermos uma alteração no código e os testes que criamos inicialmente não passarem, receberemos uma notificação ou poderemos simplesmente rejeitar o pull request.&lt;/p>
&lt;h2 id="criando-o-repositório-github">Criando o repositório Github&lt;/h2>
&lt;p>Primeiro, criaremos um repositório Github, então vá até o Github, crie o repositório e clone-o em seu ambiente local. Decidi chamar este novo repositório de &lt;code>CalculatorCLI-demo&lt;/code>.&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/0657eb2bdeb3c331b9e4585d7deed5ef.png" >
&lt;h2 id="criando-a-solução">Criando a solução&lt;/h2>
&lt;p>Agora vamos criar uma solução vazia chamada &lt;code>CalculatorCLI&lt;/code>, na pasta raiz do repositório clonado&lt;/p>
&lt;h2 id="biblioteca-principal">Biblioteca principal&lt;/h2>
&lt;p>Como seria em um projeto do mundo real, armazenaremos nossa lógica em um projeto separado que gera uma biblioteca, então vamos criá-la.&lt;/p>
&lt;p>Vá e crie um &lt;code>Class Library (.NET Standard)&lt;/code> e nomeie-o como &lt;code>CalculatorCLI.Core&lt;/code>&lt;/p>
&lt;h3 id="versão-do-net-coreassim-que-você-criar-o-projeto-acesse-as-propriedades-do-projeto-e-altere-o-target-framework-para-net-standard-21-para-torná-lo-compatível-com-projetos-construídos-em-net-core-30">Versão do NET CoreAssim que você criar o projeto, acesse as propriedades do projeto e altere o &lt;code>Target framework&lt;/code> para &lt;code>.NET Standard 2.1&lt;/code>, para torná-lo compatível com projetos construídos em &lt;code>.NET Core 3.0&lt;/code>.&lt;/h3>
&lt;h3 id="código">Código&lt;/h3>
&lt;p>Para fins de tutorial, vamos criar uma classe simples que lida com operações.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">ConsoleCalculator.Core&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">enum&lt;/span> OperatorsEnum
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ADD,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SUBSTRACT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MULTIPLY,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DIVIDE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Operation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> OperatorsEnum OperatorEnum { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> LeftValue { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> RightValue { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Operation(&lt;span style="color:#ff7b72">string&lt;/span> operatorString, &lt;span style="color:#ff7b72">int&lt;/span> leftValue, &lt;span style="color:#ff7b72">int&lt;/span> rightValue)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (operatorString)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.ADD;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.SUBSTRACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;*&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.MULTIPLY;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.DIVIDE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Operator invalid&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LeftValue = leftValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RightValue = rightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> DoOperation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (OperatorEnum)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.ADD:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue + RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.SUBSTRACT:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue - RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.MULTIPLY:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue * RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.DIVIDE:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue / RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Operator is not valid&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>##CLI&lt;/p>
&lt;p>Agora que temos o projeto principal, vamos criar o aplicativo. Neste caso, será um aplicativo de console simples que aceita argumentos e mostra um resultado.&lt;/p>
&lt;p>Então vamos em frente e criar um novo &lt;code>Console App (.NET Core)&lt;/code>, eu o nomeei &lt;code>CalculatorCLI.CLI&lt;/code>.&lt;/p>
&lt;h3 id="versão-do-net-core">Versão do NET Core&lt;/h3>
&lt;p>Assim como fizemos antes, assim que você criar o projeto, vá nas propriedades do projeto e altere o &lt;code>Target framework&lt;/code> para &lt;code>.NET Core 3.0&lt;/code>, caso ainda não esteja assim.&lt;/p>
&lt;p>Em seguida, adicione a referência ao &lt;code>ConsoleCLI.Core&lt;/code> ao nosso projeto recém-criado.&lt;/p>
&lt;h3 id="código-1">Código&lt;/h3>
&lt;p>Agora, para o código, isso é mais simples do que antes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">ConsoleCalculator.Core&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text.RegularExpressions&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">ConsoleCalculator.CLI&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Program&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> Main(&lt;span style="color:#ff7b72">string&lt;/span>[] args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (args.Length == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PrintUsage();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> joinedArgs = &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34; &amp;#34;&lt;/span>, args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> regex = &lt;span style="color:#a5d6ff">@&amp;#34;-op [\+\-\*\/] -l [-0-9]+ -r [-0-9]+&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (Regex.IsMatch(joinedArgs, regex))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span> _left = Int32.Parse(args[&lt;span style="color:#a5d6ff">3&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span> _right = Int32.Parse(args[&lt;span style="color:#a5d6ff">5&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> _operator = args[&lt;span style="color:#a5d6ff">1&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> _operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(_operator, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> _result = _operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Result is: {_result}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PrintUsage();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> PrintUsage()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Welcome to ConsoleCalculator!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;-op Operator, it must be +,-,*,/&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;-l Left number&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;-r Left number&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Example usage: -op + -l 5 -r 6&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Estaremos utilizando esta aplicação a partir de um comando, então para que funcione temos que chamá-la passando alguns parâmetros. Por exemplo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ConsoleCalculator.CLI.exe -op + -l &lt;span style="color:#a5d6ff">10&lt;/span> -r &lt;span style="color:#a5d6ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O que se traduz em:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ConsoleCalculator.CLI.exe -operator + -leftValue &lt;span style="color:#a5d6ff">10&lt;/span> -rightValue &lt;span style="color:#a5d6ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>O código para isso é bem simples, se não corresponder a um determinado padrão regex, é uma chamada errada e chama o &lt;code>PrintUsage()&lt;/code>. Isso significa que se inserirmos algo diferente de um número, por estar definido na regex, ele nem tentará fazer o cálculo.&lt;/p>
&lt;p>Isso significa que, se chamarmos assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ConsoleCalculator.CLI.exe -operator + -leftValue asdfg -rightValue ghjk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ele nunca entrará na lógica de operações e estamos salvando verificações futuras, como &lt;code>TryParse&lt;/code> dos valores.&lt;/p>
&lt;h2 id="teste">Teste&lt;/h2>
&lt;p>Temos a biblioteca principal e a linha de comando, mas precisamos testar agora, porque é isso que queremos fazer no CI.&lt;/p>
&lt;p>Então, vamos criar um novo &lt;code>MSTest Test Project (.NET Core)&lt;/code> e nomeá-lo &lt;code>CalculatorCLI.Tests&lt;/code>.&lt;/p>
&lt;h3 id="versão-do-net-core-1">Versão do NET Core&lt;/h3>
&lt;p>Assim como fizemos antes, assim que você criar o projeto, vá nas propriedades do projeto e altere o &lt;code>Target framework&lt;/code> para &lt;code>.NET Core 3.0&lt;/code>, caso ainda não esteja assim.&lt;/p>
&lt;p>Em seguida, adicione a referência a &lt;code>ConsoleCLI.Core&lt;/code> e &lt;code>ConsoleCLI.Core&lt;/code> ao nosso projeto de teste recém-criado.&lt;/p>
&lt;h3 id="código-2">Código&lt;/h3>
&lt;p>Vamos dividir o teste em dois arquivos diferentes: &lt;code>CoreTests.cs&lt;/code> e &lt;code>CLITests.cs&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">CalculatorCLI.Core&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.VisualStudio.TestTools.UnitTesting&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Collections.Generic&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CalculatorCLI.Tests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestClass]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CoreTests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> _left = &lt;span style="color:#a5d6ff">2&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> _right = &lt;span style="color:#a5d6ff">2&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldAdd()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> expectedResult = &lt;span style="color:#a5d6ff">4&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> functionResult = operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.AreEqual(functionResult, expectedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldSubstract()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> expectedResult = &lt;span style="color:#a5d6ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> functionResult = operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.AreEqual(functionResult, expectedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldMultiply()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> expectedResult = &lt;span style="color:#a5d6ff">4&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;*&amp;#34;&lt;/span>, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> functionResult = operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.AreEqual(functionResult, expectedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldDivide()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> expectedResult = &lt;span style="color:#a5d6ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> functionResult = operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.AreEqual(functionResult, expectedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ExpectedException(typeof(System.DivideByZeroException))]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldThrowExceptionForDivideByZero()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, _left, &lt;span style="color:#a5d6ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ExpectedException(typeof(System.Exception), &amp;#34;Operator invalid&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldThrowExceptionForWrongOperator()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;text&amp;#34;&lt;/span>, _left, &lt;span style="color:#a5d6ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.VisualStudio.TestTools.UnitTesting&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Collections.Generic&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CalculatorCLI.Tests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestClass]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CLITests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _left = &lt;span style="color:#a5d6ff">&amp;#34;2&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _right = &lt;span style="color:#a5d6ff">&amp;#34;2&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldAdd()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> args = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;-op&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-l&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;45&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-r&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;96&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CalculatorCLI.CLI.Program.Main(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldSubstract()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> args = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;-op&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-l&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;45&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-r&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;96&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CalculatorCLI.CLI.Program.Main(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldMultiply()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> args = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;-op&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;*&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-l&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;45&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-r&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;96&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CalculatorCLI.CLI.Program.Main(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldDivide()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> args = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;-op&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-l&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;45&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-r&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;96&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CalculatorCLI.CLI.Program.Main(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Com tudo criado terminaremos com uma solução como esta:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/ffecc23a14d796af9a46dbb390c0d072.png" />
&lt;p>E com isso agora podemos executar os Testes, então vá até o &lt;code>Test Explorer&lt;/code> no Visual Studio e execute-os!&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/17d7ac9b2f1e7fb2666a68fc87882eed.png" />
&lt;p>#Travis CI&lt;/p>
&lt;p>Se ainda não o fez, TravisCI é um sistema hospedado de integração e implantação contínua.&lt;/p>
&lt;p>Existem alguns passos que precisamos seguir aqui, mas primeiro vamos vincular nosso repositório Github para ser ouvido pelos agentes TravisCI para construir e testar nosso projeto.&lt;/p>
&lt;h2 id="habilitar-repositório">Habilitar repositório&lt;/h2>
&lt;p>Para fazer isso, faça login na página do Travis CI e vá até seus repositórios, depois filtre o projeto que você criou e habilite-o, clicando no controle deslizante ao lado do nome do repositório.&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/28b366dddd3f5caa9100ca6b6d200764.png" >
&lt;h2 id="crie-travisymlprecisamos-criar-um-arquivo-chamado-travisyml-na-raiz-do-seu-projeto-isso-porque-conforme-indicado-na-documentação">Crie .travis.ymlPrecisamos criar um arquivo chamado &lt;code>.travis.yml&lt;/code> na raiz do seu projeto, isso porque &lt;a href="https://docs.travis-ci.com/user/tutorial/">conforme indicado na documentação&lt;/a>:&lt;/h2>
&lt;blockquote>
&lt;p>O Travis só executa compilações nos commits que você envia depois de adicionar um arquivo .travis.yml.&lt;/p>&lt;/blockquote>
&lt;p>Então crie um arquivo &lt;code>.travis.yml&lt;/code> na raiz do repositório com as seguintes linhas:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">language&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">csharp&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">sudo&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">required&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">mono&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">none &lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">dotnet&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">3.0&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">os&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">linux&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">before_script&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">dotnet restore &amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">script&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">dotnet build &amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34; -c Release&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">dotnet test &amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34; -c Release -v n&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Não vou entrar na sintaxe de como o arquivo &lt;code>.travis.yml&lt;/code> funciona, mas vamos revisar o que ele está fazendo:&lt;/p>
&lt;ol>
&lt;li>Configuramos que o idioma será &lt;code>csharp&lt;/code>.&lt;/li>
&lt;li>Não usaremos &lt;code>mono&lt;/code> porque &lt;code>.NET Core 3.0&lt;/code> será executado nativamente no Linux.&lt;/li>
&lt;li>Definimos a versão &lt;code>dotnet&lt;/code> como &lt;code>3.0&lt;/code>.&lt;/li>
&lt;li>Definimos o &lt;code>os&lt;/code>, por padrão é &lt;code>linux&lt;/code>, mas eu adicionei mesmo assim.&lt;/li>
&lt;li>Agora temos &lt;code>before_script&lt;/code> que irá funcionar antes da lógica principal .aqui, então o que coloquei foi rodar &lt;code>dotnet restore&lt;/code> para a solução para que tudo carregue perfeitamente depois.&lt;/li>
&lt;li>Agora no &lt;code>script&lt;/code>, faremos um &lt;code>dotnet builld&lt;/code> e &lt;code>dotnet test&lt;/code> em nossa solução, isso irá verificar se ela compila e então executar os testes.&lt;/li>
&lt;/ol>
&lt;p>Eae terminamos!&lt;/p>
&lt;h1 id="carregar-para-mestre">Carregar para mestre&lt;/h1>
&lt;p>Agora só precisamos empurrar tudo para master.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cmd" data-lang="cmd">&lt;span style="display:flex;">&lt;span>git add --all
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git commit -m &lt;span style="color:#a5d6ff">&amp;#34;Initial files&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git push
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="verifique-a-integração-contínua">Verifique a integração contínua&lt;/h1>
&lt;p>Podemos verificar o status do CI do push para &lt;code>master&lt;/code> que fizemos na página do repositório ou no painel do TravisCI.&lt;/p>
&lt;h2 id="em-progresso">Em progresso&lt;/h2>
&lt;img align="center" src="https://i.gyazo.com/977ea42a90adccf0736464b6603867a5.png" >
&lt;br/>
&lt;br/>
&lt;img align="center" src="https://i.gyazo.com/52a5f9356df5436c862b7df6fe66a9f4.png" >
&lt;h2 id="concluído">Concluído&lt;/h2>
&lt;img align="center" src="https://i.gyazo.com/c3d3521925a3e20bcf55bf5f6a2a711d.png" >
&lt;br/>
&lt;br/>
&lt;img align="center" src="https://i.gyazo.com/8c749c2ce44837a39fc5cd3e8838a798.png" >
&lt;p>#Vamos quebrar isso&lt;/p>
&lt;p>Agora, para ver o quão poderoso isso é, vamos quebrar o código e alterar a biblioteca principal para fazer com que ela falhe.&lt;/p>
&lt;h2 id="mudanças-de-código">Mudanças de código&lt;/h2>
&lt;p>Então vá para o &lt;code>Operation.cs&lt;/code> e mude algo que irá quebrar alguns testes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CalculatorCLI.Core&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">enum&lt;/span> OperatorsEnum
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ADD,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SUBSTRACT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MULTIPLY,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DIVIDE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Operation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> OperatorsEnum OperatorEnum { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> LeftValue { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> RightValue { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Operation(&lt;span style="color:#ff7b72">string&lt;/span> operatorString, &lt;span style="color:#ff7b72">int&lt;/span> leftValue, &lt;span style="color:#ff7b72">int&lt;/span> rightValue)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (operatorString)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.SUBSTRACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.SUBSTRACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;*&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.MULTIPLY;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.DIVIDE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Operator invalid&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LeftValue = leftValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RightValue = rightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> DoOperation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (OperatorEnum)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.ADD:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue + RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.SUBSTRACT:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue - RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.MULTIPLY:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue * RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.DIVIDE:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue / RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Operator is not valid&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E se executarmos o teste novamente, porque alteramos o caso para adição, ele falhará:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.SUBSTRACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Como esperado, falhou no caso &lt;code>ShouldAdd&lt;/code>:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/a57d0a0f8c07cc1ab6e4f55a8466cbbd.png" >
&lt;p>Agora faça um commit dessa mudança e envie-a para o master, e aguarde os resultados do agente TravisCI.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git add --all
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git commit -m &lt;span style="color:#a5d6ff">&amp;#34;Breaking changes&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git push
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="construir">Construir&lt;/h2>
&lt;p>Agora vamos para os logs do TravisCI e veremos que quebramos o projeto com sucesso, pois os testes de integração estão falhando e o status da compilação é erro.&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/397befe9b7f5a32b6e97511733296b00.png" >
&lt;p>No final do log podemos ver o próprio erro:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/e5cbf32c5dd7a08bf6628d81edff3130.png" >
&lt;h2 id="vamos-consertar-de-novo">Vamos consertar de novo!&lt;/h2>
&lt;p>Agora reverta o que fizemos e envie o código para master e verifique o status da nova compilação.&lt;/p>
&lt;p>Os testes estão passando com sucesso:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/02deb8fcb4fca618ff2d79f1c27c6df5.png" >
&lt;p>E construir também é um sucesso:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/38a227f0634c6040c6608f8c51f36cd3.png" >
&lt;h1 id="conclusão">Conclusão&lt;/h1>
&lt;p>&lt;strong>É realmente muito poderoso&lt;/strong>, CI e CD já existem há muito tempo, mas agora é muito simples executá-lo em cada projeto, não importa quão pequeno ou simples seja.Do meu ponto de vista, todos deveriam pelo menos configurar o CI para cada um de seus projetos, porque é uma boa prática e eventualmente economizará tempo na depuração e na localização de erros que não deveriam ocorrer se você tivesse definido &lt;code>tests&lt;/code> e CI adequados.&lt;/p>
&lt;p>#É isso&lt;/p>
&lt;p>É isso sobre como criar uma solução .NET Core 3.0 que tenha integração contínua em cada build usando TravisCI e armazenando o código no Github.&lt;/p>
&lt;p>Você pode encontrar o código-fonte deste projeto &lt;a href="https://github.com/emimontesdeoca/CalculatorCLI-demo">aqui&lt;/a>.&lt;/p></content:encoded><category>.NET</category><category>CI/CD</category></item><item><title>Minha opinião sobre cache na memória</title><link>https://emimontesdeoca.github.io/pt/posts/my-take-on-in-memory-cache/</link><pubDate>Tue, 03 Sep 2019 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/my-take-on-in-memory-cache/</guid><description>Crie uma implementação de cache na memória personalizada com suporte a expiração usando C# e genéricos.</description><content:encoded>&lt;p>Tenho trabalhado em algumas coisas que lidam com uma grande quantidade de dados. Ao fazer isso, percebi que uma grande parte disso nunca muda, ou pelo menos por algum tempo fixo, isso não acontece.&lt;/p>
&lt;p>Então pensei que seria útil criar um repositório de cache pessoal, claro que isso não é novidade, há algumas semanas li sobre isso no &lt;a href="https://nickcraver.com/blog/2019/08/06/stack-overflow-how-we-do-app-caching/#in-memory--redis-cache">post&lt;/a> do StackOverflow escrito por &lt;a href="https://nickcraver.com/">Nick Craver&lt;/a> sobre como eles gerenciam o cache do aplicativo.&lt;/p>
&lt;p>Além disso, sempre quis trabalhar com cache, como funciona, a lógica e como poderia fazê-lo funcionar, &lt;em>então&amp;hellip; por que não!&lt;/em>&lt;/p>
&lt;h2 id="fluxo">Fluxo&lt;/h2>
&lt;p>Aqui está um fluxo rápido de como a classe que tem o controle do cache se comportará quando o usuário solicitar um valor.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/830d5a91089c3344c8b406c66ea547b8">&lt;img src="https://i.gyazo.com/830d5a91089c3344c8b406c66ea547b8.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="implementação">Implementação&lt;/h2>
&lt;h3 id="antes-de-começarmos">Antes de começarmos&lt;/h3>
&lt;p>Eu sei, eu sei. Existe &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.caching.memorycache?view=netframework-4.8">System.Runtime.Caching&lt;/a> que lida com o cache de memória. Mas eu decidi construí-lo sozinho, se você quiser usar essa classe, verifique &lt;a href="https://stackoverflow.com/search?q=System.Runtime.Caching">aqui&lt;/a> para obter instruções.&lt;/p>
&lt;h3 id="cacheitem">CacheItem&lt;/h3>
&lt;p>O primeiro passo é ter uma classe que armazene o valor de um objeto e a data de validade. Provavelmente há uma maneira melhor de fazer isso, mas foi isso que pensei, então pronto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CacheItem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Identifier { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">object&lt;/span> Value { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime ValidUntil { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CacheItem(&lt;span style="color:#ff7b72">string&lt;/span> identifier, &lt;span style="color:#ff7b72">object&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>, TimeSpan valid)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Identifier = identifier;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Value = &lt;span style="color:#ff7b72">value&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ValidUntil = DateTime.UtcNow.Add(valid);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="cacherepositório">CacheRepositório&lt;/h3>
&lt;p>Então precisamos de uma classe que lide com os objetos e os salve em algum lugar (como &lt;code>CacheItem&lt;/code>). Eu gosto de lidar com todos os dados/modelos em classes que possuem o sufixo &lt;code>Repository&lt;/code>, &lt;em>mas você não precisa&lt;/em>, então vamos construir um para armazenamento em cache.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CacheRepository&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; Cache = &lt;span style="color:#ff7b72">new&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T Set&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> key, Func&amp;lt;T&amp;gt; lookup, TimeSpan durationMinutes)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> item = &lt;span style="color:#ff7b72">new&lt;/span> Models.Item(key, lookup(), durationMinutes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Save&amp;lt;T&amp;gt;(key, item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T Save&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> key, Item item)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Cache[key] = JsonConvert.SerializeObject(item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> (T)item.Value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T Get&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> cached = Cache.FirstOrDefault(x =&amp;gt; x.Key == key).Value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> item = &lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(cached) ? &lt;span style="color:#79c0ff">null&lt;/span> : JsonConvert.DeserializeObject&amp;lt;Item&amp;gt;(cached);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> (item == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? &lt;span style="color:#ff7b72">default&lt;/span> : (item.ValidUntil &amp;gt; DateTime.UtcNow)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(JsonConvert.SerializeObject(item.Value)) : &lt;span style="color:#ff7b72">default&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T GetOrSet&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> key, Func&amp;lt;T&amp;gt; lookup, TimeSpan durationMinutes)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> cache = Get&amp;lt;T&amp;gt;(key);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> cache == &lt;span style="color:#79c0ff">null&lt;/span> ? Set(key, lookup, durationMinutes) : cache;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Até agora temos um dicionário estático chamado &lt;code>Cache&lt;/code> onde todos os itens serão armazenados. Lembre-se de que isso só durará enquanto o aplicativo estiver em execução, por isso este título &lt;em>tutorial&lt;/em> contém cache na memória.&lt;/p>
&lt;p>&lt;em>Tenha em mente que o item &lt;code>Cache&lt;/code> será inicializado assim que a classe &lt;code>CacheRepository&lt;/code> for carregada.&lt;/em>&lt;/p>
&lt;p>O único método disponível ao invocar a classe CacheRepository é &lt;code>GetOrSet(string key, Func&amp;lt;T&amp;gt; lookup, TimeSpan durationMinutes)&lt;/code> que precisa de três parâmetros:&lt;/p>
&lt;ol>
&lt;li>&lt;code>key&lt;/code>: o identificador do objeto a ser salvo.&lt;/li>
&lt;li>&lt;code>lookup&lt;/code>: a função de retorno de chamada caso o cache tenha expirado ou seja nulo.&lt;/li>
&lt;li>&lt;code>durationMinutes&lt;/code>: a duração em minutos que será adicionada ao horário atual (em UTC).&lt;/li>
&lt;/ol>
&lt;h2 id="hora-de-armazenar-em-cache">Hora de armazenar em cache&lt;/h2>
&lt;p>Agora, use nosso repositório de cache para obter alguns dados de algum lugar.&lt;/p>
&lt;p>Para que tudo isso faça sentido, vamos criar um objeto de exemplo com algumas propriedades, e depois um repositório para buscar e preencher uma lista desse objeto.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">User&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Guid Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Email { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">UserRepository&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;User&amp;gt; Get()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Code to get users&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Como temos o método para preencher uma lista de &lt;code>User&lt;/code>, vamos usar a classe &lt;code>CacheRepository&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> UserRepository _userRepository = &lt;span style="color:#ff7b72">new&lt;/span> UserRepository();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;User&amp;gt; _user;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;User&amp;gt; User
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">get&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _user = CacheRepository.GetOrSet(&lt;span style="color:#a5d6ff">$&amp;#34;users&amp;#34;&lt;/span>, usersRepo.Get, TimeSpan.FromMinutes(&lt;span style="color:#a5d6ff">10&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> _user;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E assim mesmo, toda vez que você acessar a variável &lt;code>User&lt;/code>, ele irá pedir ao &lt;code>CacheRepository&lt;/code> o valor de um objeto que possui a chave &lt;code>users&lt;/code>.&lt;/p>
&lt;p>Se essa chave existir, ela verificará a data de validade. Se qualquer uma dessas condições for falsa, ele usará o retorno de chamada para definir o valor (com &lt;code>usersRepo.Get&lt;/code>) do objeto, salvá-lo no cache com a data de expiração definida como &lt;code>DateTime.UtcNow + TimeSpan.FromMinutes(10)&lt;/code> e devolvê-lo.&lt;/p></content:encoded><category>.NET</category></item><item><title>Faça capturas de tela da página usando Devtools</title><link>https://emimontesdeoca.github.io/pt/posts/google-chrome-screenshot-tool/</link><pubDate>Thu, 09 May 2019 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/google-chrome-screenshot-tool/</guid><description>Capture capturas de tela de página inteira e de área usando comandos de captura de tela integrados do Chrome DevTools.</description><content:encoded>&lt;p>Houve momentos em que precisei mostrar a um cliente ou colega de equipe uma captura de tela completa do estado atual do site ou até mesmo de parte dele, então tenho usado &lt;a href="https://chrome.google.com/webstore/detail/full-page-screen-capture/fdpohaocaechififmbbbbbknoalclacl?hl=en">Captura de tela de página inteira&lt;/a> e, para qualquer outro tipo de captura de tela, &lt;a href="https://support.microsoft.com/en-us/help/13776/windows-use-snipping-tool-to-capture-screenshots">Ferramenta de snippet do Windows&lt;/a> ou &lt;a href="https://www.techsmith.com/store/snagit">Snaggit&lt;/a>. Se eu quiser hospedá-lo online como neste post &lt;a href="https://gyazo.com">Gyazo&lt;/a>, que também tem um criador de GIF.&lt;/p>
&lt;p>Recentemente descobri que &lt;strong>Devtools&lt;/strong> tem uma ferramenta que faz uma captura de tela de página inteira, sem necessidade de extensão!&lt;/p>
&lt;p>Este não é um pensamento novo, foi incluído na &lt;a href="https://developers.google.com/web/updates/2017/04/devtools-release-notes">atualização do Devtools em abril de 2017&lt;/a>, &lt;em>mas parece que moro debaixo de uma rocha e não descobri até agora&amp;hellip;&lt;/em>&lt;/p>
&lt;h2 id="ferramentas-de-desenvolvimento">Ferramentas de desenvolvimento&lt;/h2>
&lt;p>Conforme declarado na &lt;a href="https://developers.google.com/web/tools/chrome-devtools/?hl=en">página oficial do Devtools&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>Chrome DevTools é um conjunto de ferramentas para desenvolvedores web integradas diretamente no navegador Google Chrome. DevTools pode ajudá-lo a editar páginas dinamicamente e diagnosticar problemas rapidamente, o que, em última análise, ajuda a construir sites melhores e mais rápido.&lt;/p>&lt;/blockquote>
&lt;h2 id="executando-comandos-no-devtools">Executando comandos no Devtools&lt;/h2>
&lt;p>Você já sabe como abrir o Devtools, mas caso tenha esquecido e &lt;em>por causa deste post&lt;/em>, abra-o usando a tecla &lt;code>F12&lt;/code> ou o atalho &lt;code>Ctrl + Shift + I&lt;/code>. Então você tem que abrir o &lt;code>Run command&lt;/code> no menu dentro do Devtools ou usar &lt;code>Ctrl + Shift + P&lt;/code>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/8209d7d3132efba1843a3d51e4ad2183">&lt;img src="https://i.gyazo.com/8209d7d3132efba1843a3d51e4ad2183.gif" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="ferramenta-de-captura-de-tela">Ferramenta de captura de tela&lt;/h2>
&lt;p>&lt;a href="https://gyazo.com/25ea6c9d258a1117efca5a2d92f715e2">&lt;img src="https://i.gyazo.com/25ea6c9d258a1117efca5a2d92f715e2.gif" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Se você digitar &lt;code>screenshot&lt;/code> irá filtrar os comandos, haverá 4 tipos de métodos de captura de tela que você pode usar:&lt;/p>
&lt;ol>
&lt;li>Captura de tela da área&lt;/li>
&lt;li>Captura de tela em tamanho real&lt;/li>
&lt;li>Captura de tela do nó&lt;/li>
&lt;li>Capturar captura de tela&lt;/li>
&lt;/ol>
&lt;p>Antes de verificar todos os métodos de captura de tela, após o devtools terminar de processar a imagem, &lt;strong>ele irá baixá-la automaticamente com o nome da página&lt;/strong>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/89a4935eb0ddb1a06ae997551fd19677">&lt;img src="https://i.gyazo.com/89a4935eb0ddb1a06ae997551fd19677.gif" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/e2e29c7fd7e90bb8bfd36ef130793394">&lt;img src="https://i.gyazo.com/e2e29c7fd7e90bb8bfd36ef130793394.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h3 id="captura-de-tela-da-área">Captura de tela da área&lt;/h3>
&lt;p>O &lt;code>area screenshot&lt;/code> permitirá que você selecione uma parte do site e faça uma captura de tela.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/f508a479893b6e8af9234d6a77404b26">&lt;img src="https://i.gyazo.com/f508a479893b6e8af9234d6a77404b26.gif" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Resultado:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/2b096e1ca2974157e478587a4902c1d2">&lt;img src="https://i.gyazo.com/2b096e1ca2974157e478587a4902c1d2.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h3 id="captura-de-tela-em-tamanho-real">Captura de tela em tamanho real&lt;/h3>
&lt;p>O &lt;code>full size screenshot&lt;/code> fará uma captura de tela de toda a página da web, do início ao fim.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/e6c30b8eafbef04091bf66c16429017c">&lt;img src="https://i.gyazo.com/e6c30b8eafbef04091bf66c16429017c.gif" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Resultado:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/9519ca32c0029a36432b700268741280">&lt;img src="https://i.gyazo.com/9519ca32c0029a36432b700268741280.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h3 id="captura-de-tela-do-nó">Captura de tela do nó&lt;/h3>
&lt;p>O &lt;code>node screenshot&lt;/code> permitirá que você faça uma captura de tela de um nó selecionado no elemento inspecionar.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/5b936026735964bb789c4bcae02bb792">&lt;img src="https://i.gyazo.com/5b936026735964bb789c4bcae02bb792.gif" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Resultado:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/56089c3d89f9b059dfb018d18572276d">&lt;img src="https://i.gyazo.com/56089c3d89f9b059dfb018d18572276d.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h3 id="capturar-captura-de-tela">Capturar captura de tela&lt;/h3>
&lt;p>O &lt;code>capture screenshot&lt;/code> fará uma captura de tela da página atual sem rolar e do tamanho do navegador.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/6dc4f654e8f218e44fcc1fb51ce4c9f5">&lt;img src="https://i.gyazo.com/6dc4f654e8f218e44fcc1fb51ce4c9f5.gif" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Resultado:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/3f15d4a004cba6152d1f9606c20540cc">&lt;img src="https://i.gyazo.com/3f15d4a004cba6152d1f9606c20540cc.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p></content:encoded></item><item><title>Linguagens personalizadas usando recursos incorporados e externos no .NET Framework</title><link>https://emimontesdeoca.github.io/pt/posts/embedded-resources-and-external-resources/</link><pubDate>Mon, 06 May 2019 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/embedded-resources-and-external-resources/</guid><description>Implemente suporte multilíngue usando arquivos de recursos .resx integrados e externos no .NET Framework.</description><content:encoded>&lt;h1 id="introdução">Introdução&lt;/h1>
&lt;p>No trabalho, encontramos um problema normal com o qual tenho certeza que muitos desenvolvedores têm que lidar, que são linguagens e linguagens personalizadas dos clientes que estão prestando nossos serviços aos seus clientes.&lt;/p>
&lt;p>Por exemplo, digamos que a linguagem base seja uma linguagem informal e um cliente queira uma linguagem formal porque seus clientes são pessoas mais velhas.&lt;/p>
&lt;p>Observe também que esta solução é para vários idiomas, usaremos inglês e espanhol.&lt;/p>
&lt;p>&lt;em>Minha língua materna não é o inglês. Peço desculpas por quaisquer erros no tutorial. Se você encontrar erros e quiser corrigi-los, você pode abrir uma solicitação pull em &lt;a href="https://github.com/emimontesdeoca/emimontesdeoca.github.io">este repositório&lt;/a> e terei prazer em aprová-lo!&lt;/em>&lt;/p>
&lt;h1 id="recursos">Recursos&lt;/h1>
&lt;p>Recursos são arquivos XML com extensão &lt;code>.resx&lt;/code> que possuem uma estrutura chave/valor e se parecem com o seguinte:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;root&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:schema&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;root&amp;#34;&lt;/span> xmlns=&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span> xmlns:xsd=&lt;span style="color:#a5d6ff">&amp;#34;http://www.w3.org/2001/XMLSchema&amp;#34;&lt;/span> xmlns:msdata=&lt;span style="color:#a5d6ff">&amp;#34;urn:schemas-microsoft-com:xml-msdata&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:import&lt;/span> namespace=&lt;span style="color:#a5d6ff">&amp;#34;http://www.w3.org/XML/1998/namespace&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;root&amp;#34;&lt;/span> msdata:IsDataSet=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:choice&lt;/span> maxOccurs=&lt;span style="color:#a5d6ff">&amp;#34;unbounded&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;metadata&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;value&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> minOccurs=&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;name&amp;#34;&lt;/span> use=&lt;span style="color:#a5d6ff">&amp;#34;required&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;type&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;mimetype&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> ref=&lt;span style="color:#a5d6ff">&amp;#34;xml:space&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;assembly&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;alias&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;name&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;data&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;value&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> minOccurs=&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;comment&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> minOccurs=&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;2&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;name&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> use=&lt;span style="color:#a5d6ff">&amp;#34;required&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;type&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;3&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;mimetype&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;4&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> ref=&lt;span style="color:#a5d6ff">&amp;#34;xml:space&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;resheader&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;value&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> minOccurs=&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;name&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> use=&lt;span style="color:#a5d6ff">&amp;#34;required&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:choice&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:schema&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;resheader&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;resmimetype&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>text/microsoft-resx&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/resheader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;resheader&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;version&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>2.0&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/resheader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;resheader&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;reader&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/resheader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;resheader&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;writer&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/resheader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;data&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;Action_cancel&amp;#34;&lt;/span> xml:space=&lt;span style="color:#a5d6ff">&amp;#34;preserve&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>Finish&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/data&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;data&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;Action_greeting&amp;#34;&lt;/span> xml:space=&lt;span style="color:#a5d6ff">&amp;#34;preserve&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>Hello&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/data&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/root&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Estaremos criando recursos para dois idiomas diferentes: &lt;strong>inglês&lt;/strong> e &lt;strong>espanhol&lt;/strong>, então a nomenclatura dos arquivos será: &lt;code>resources.ISOLANGUAGECODE.resx&lt;/code>, por exemplo: &lt;code>resource.es.resx&lt;/code> para espanhol e &lt;code>resource.resx&lt;/code> para inglês, caso queiramos adicionar posteriormente o alemão, o arquivo será nomeado &lt;code>resource.de.resx&lt;/code>.&lt;/p>
&lt;h2 id="recurso-incorporado">Recurso incorporado&lt;/h2>
&lt;p>Recursos incorporados, quando o projeto for compilado, serão adicionados dentro do &lt;code>dll&lt;/code>.&lt;/p>
&lt;p>Aqui está uma imagem com um recurso incorporado, como você pode ver, o arquivo &lt;code>Resource.resx&lt;/code> não pode ser visto na compilação.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/b696d4ff1129634477c0fe3d570a05e8">&lt;img src="https://i.gyazo.com/b696d4ff1129634477c0fe3d570a05e8.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="recurso-externo">Recurso externo&lt;/h2>
&lt;p>Por outro lado os recursos externos ou não incorporados são recursos que serão adicionados à pasta após a compilação.&lt;/p>
&lt;p>Os arquivos de recursos (&lt;code>.resx&lt;/code>) estarão dentro da pasta &lt;code>Properties&lt;/code>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/db83dc7bb9e3d3fb521cb321eaa8e74a">&lt;img src="https://i.gyazo.com/db83dc7bb9e3d3fb521cb321eaa8e74a.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h1 id="construindo-o-projeto">Construindo o projeto&lt;/h1>
&lt;p>Vamos começar com um projeto de console no .NET Framework.&lt;/p>
&lt;h2 id="criando-o-arquivo-de-recurso-incorporado">Criando o arquivo de recurso incorporado&lt;/h2>
&lt;p>Adicione um arquivo de recurso com algumas chaves e certifique-se de que seja um recurso incorporado, que posteriormente usaremos para atualizar com recursos externos.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/79af1981a3ee3214089791a3511b94ba">&lt;img src="https://i.gyazo.com/79af1981a3ee3214089791a3511b94ba.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="criando-o-arquivo-de-recurso-externo">Criando o arquivo de recurso externo&lt;/h2>
&lt;p>Para separar os recursos externos dos incorporados, iremos adicionar os recursos externos dentro de uma pasta com um nome para que possamos acessá-los facilmente posteriormente.&lt;/p>
&lt;p>&lt;em>Dica: para adicionar uma pasta dentro da pasta Propriedades, criá-la fora e movê-la para dentro, o Visual Studio não permite criá-la&lt;/em>&lt;/p>
&lt;p>Faça o mesmo que fizemos para o recurso incorporado, vá para &lt;code>properties&lt;/code> do arquivo e dentro de &lt;code>Advanced&lt;/code>, &lt;code>Build action&lt;/code> e altere para: &lt;code>Content&lt;/code> e altere &lt;code>Copy to Output Dictionary&lt;/code> para &lt;code>Copy if newer&lt;/code>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/6f7d53c82760b6989d74dc1864bbc2d4">&lt;img src="https://i.gyazo.com/6f7d53c82760b6989d74dc1864bbc2d4.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>É assim que deve ser:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/75b2f06b11fa99b01bca7b5ac64b4e35">&lt;img src="https://i.gyazo.com/75b2f06b11fa99b01bca7b5ac64b4e35.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="adicionando-uma-chave-ao-appconfig">Adicionando uma chave ao app.config&lt;/h2>
&lt;p>Como somos desenvolvedores legais e gostamos de ter tudo bem feito e &lt;strong>NÃO&lt;/strong> alterar o código de cada cliente, vamos adicionar uma chave ao &lt;code>appconfig&lt;/code> que terá o nome da pasta onde estaremos procurando os arquivos de recursos&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34; ?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;startup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;supportedRuntime&lt;/span> version=&lt;span style="color:#a5d6ff">&amp;#34;v4.0&amp;#34;&lt;/span> sku=&lt;span style="color:#a5d6ff">&amp;#34;.NETFramework,Version=v4.7.2&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/startup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;appSettings&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;add&lt;/span> key=&lt;span style="color:#a5d6ff">&amp;#34;CustomResources.Folder&amp;#34;&lt;/span> value=&lt;span style="color:#a5d6ff">&amp;#34;John&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/appSettings&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>```Então, mais tarde, quando procurarmos os recursos, ele procurará em `Properties.John` em vez de `Properties.Doe`.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Além disso, isso facilita a alteração quando o aplicativo já está implantado, pois você pode alterar facilmente o app.config.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>## Acessando arquivos de recursos incorporados
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Usar o .NET Framework é bastante fácil, então para acessar as propriedades do Embedded basta usar o objeto `Properties`, que terá como propriedade os arquivos de recursos, e dentro desse objeto estarão toda a chave/valor que temos no arquivo `resx`.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>```csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>static void Main(string[] args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> var hello = Properties.Resource.Action_greeting;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> var bye = Properties.Resource.Action_cancel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine($&amp;#34;Action_greeting value: {hello}, Action_cancel value: {bye}&amp;#34;);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Read();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Executando isso deve nos mostrar algo assim:&lt;/p>
&lt;p>&lt;code>Action_greeting value: Hello, Action_cancel value: Cancel&lt;/code>&lt;/p>
&lt;h2 id="acessando-arquivos-de-recursos-externos">Acessando arquivos de recursos externos&lt;/h2>
&lt;p>Esta parte é praticamente a mesma, você pode acessar os arquivos de recursos pelo objeto &lt;code>Properties&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> hello = Properties.Resource.Action_greeting;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> bye = Properties.Resource.Action_cancel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> johnhello = Properties.John.Resource.Action_greeting;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> johnbye = Properties.John.Resource.Action_cancel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doehello = Properties.Doe.Resource.Action_greeting;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doebye = Properties.Doe.Resource.Action_cancel;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="problema">Problema&lt;/h1>
&lt;p>O principal problema ao usar este método é que cada chave é uma propriedade dentro do objeto, então temos que chamá-la como vimos antes. Se você quiser chamar a chave &lt;code>Action_greeting&lt;/code> do arquivo de recursos de &lt;code>John&lt;/code> temos que usar o seguinte &lt;code>Properties.John&lt;/code> e depois &lt;code>Resource.Action_greeting&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Aí está o problema.&lt;/strong>&lt;/p>
&lt;p>Isso porque se estamos desenvolvendo uma aplicação para muitos clientes, é uma má ideia mudar a forma como chamamos os arquivos de recursos de cada um deles.&lt;/p>
&lt;p>&lt;em>Você consegue imaginar isso?&lt;/em> Compilar a aplicação para cada cliente e mudar &lt;code>John&lt;/code> para &lt;code>Doe&lt;/code> e depois para outra coisa. &lt;strong>Isso é loucura!&lt;/strong>&lt;/p>
&lt;h1 id="solução">Solução&lt;/h1>
&lt;p>Nosso líder de equipe pensou em um método muito bom, algo como um sistema substituto, devemos ter um modelo base de recursos, e então para cada um dos clientes temos um arquivo de recursos que irá atualizar o arquivo com seus recursos, e terminamos com uma única lista de recursos.&lt;/p>
&lt;p>Se o cliente não quiser recursos customizados usamos os recursos base, e se ele quiser, usamos os deles.&lt;/p>
&lt;p>Para colocar isso em uma lista de verificação, temos que fazer:&lt;/p>
&lt;ul>
&lt;li>[] Encontre uma maneira de mapear todas as chaves/valores para um dicionário para os recursos incorporados.&lt;/li>
&lt;li>[] Encontre uma maneira de mapear todas as chaves/valores para um dicionário para os recursos externos&lt;/li>
&lt;li>[] Misture os dois arquivos e tenha um único dicionário para cada idioma&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Crie um método que acesse o dicionário e retorne o valor&lt;/li>
&lt;/ul>
&lt;h2 id="diagrama">Diagrama&lt;/h2>
&lt;p>&lt;a href="https://gyazo.com/c7e9ae6c7792c3bace10ff9b3b2b08ee">&lt;img src="https://i.gyazo.com/c7e9ae6c7792c3bace10ff9b3b2b08ee.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="vamos-codificar">Vamos codificar&lt;/h2>
&lt;p>Primeiramente, vamos criar uma classe separada onde teremos toda a nossa lógica, desde a obtenção dos arquivos de recursos, até a mistura deles e o retorno do valor. Essa classe será chamada &lt;code>CustomResources&lt;/code>.&lt;/p>
&lt;p>Isto é o que parece:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CustomResources&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; _ResourcesEnglish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ResourcesEnglish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; _ResourcesSpanish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ResourcesSpanish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; OverwriteDictionary(Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; currentDictionary, Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; newDictionary, &lt;span style="color:#ff7b72">bool&lt;/span> addIfDoesntExist = &lt;span style="color:#79c0ff">false&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetDictionaryFromEmbedded(&lt;span style="color:#ff7b72">string&lt;/span> embedded, &lt;span style="color:#ff7b72">string&lt;/span> cultureInfoCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetDictionaryFromFile(&lt;span style="color:#ff7b72">string&lt;/span> file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetText(&lt;span style="color:#ff7b72">string&lt;/span> key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetText(&lt;span style="color:#ff7b72">string&lt;/span> key, &lt;span style="color:#ff7b72">string&lt;/span> language)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Observe que estamos implementando o carregamento lento para propriedades, o que ajuda a aumentar o desempenho e faz com que o dicionário seja carregado uma vez.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>GetDictionaryFromEmbedded&lt;/code>: retorna um dicionário de recursos incorporados.&lt;/li>
&lt;li>&lt;code>GetDictionaryFromFile&lt;/code>: retorna um dicionáriou de recursos externos.&lt;/li>
&lt;li>&lt;code>OverwriteDictionary&lt;/code>: mistura dois dicionários e retorna um único.&lt;/li>
&lt;li>&lt;code>GetText&lt;/code>: retorna um valor dada uma chave&lt;/li>
&lt;/ul>
&lt;h2 id="do-recurso-incorporado-ao-dicionário">Do recurso incorporado ao dicionário&lt;/h2>
&lt;p>Temos que pegar todas as propriedades do arquivo xml e retornar um dicionário:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetDictionaryFromEmbedded(&lt;span style="color:#ff7b72">string&lt;/span> embedded, &lt;span style="color:#ff7b72">string&lt;/span> cultureInfoCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; res = &lt;span style="color:#ff7b72">new&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ResourceManager rm = &lt;span style="color:#ff7b72">new&lt;/span> ResourceManager(embedded, Assembly.GetExecutingAssembly());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> resourceSet = rm.GetResourceSet(&lt;span style="color:#ff7b72">new&lt;/span> CultureInfo(cultureInfoCode), &lt;span style="color:#79c0ff">true&lt;/span>, &lt;span style="color:#79c0ff">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> resourceDictionary = resourceSet.Cast&amp;lt;DictionaryEntry&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToDictionary(r =&amp;gt; r.Key.ToString(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> r =&amp;gt; r.Value.ToString());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> res = resourceDictionary;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> a = e.Message;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Error getting resource file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> res;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Duas coisas:- Observe que ele precisa de um parâmetro chamado &lt;code>embedded&lt;/code>, que parementers é o nome do arquivo que você pode ver no designer, no nosso caso é: &lt;code>resources-demo.Properties.Resource&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>Também temos um parâmetro chamado cultreInfoCode, que é o código do idioma a ser selecionado. Felizmente para nós, o .NET Framework faz o trabalho para nós e não precisamos fazer nada, apenas definir que queremos inglês ou espanhol, e ele selecionará entre &lt;code>resource.es.resx&lt;/code> ou &lt;code>resource.resx&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="do-recurso-externo-ao-dicionário">Do recurso externo ao dicionário&lt;/h2>
&lt;p>Sair do arquivo é um pouco hackeado, mas não difícil, temos que obter a localização atual do executável, concatenar a localização do arquivo de recurso e então analisá-lo em um dicionário.&lt;/p>
&lt;p>Mas primeiro você deve adicionar a referência a &lt;code>System.Windows.Forms&lt;/code>, para poder acessar &lt;code>ResXResourceReader&lt;/code>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/5bdf8353acc57b1d95b72acc14af41cd">&lt;img src="https://i.gyazo.com/5bdf8353acc57b1d95b72acc14af41cd.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Agora vamos ao nosso método &lt;code>GetDictionaryFromFile&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetDictionaryFromFile(&lt;span style="color:#ff7b72">string&lt;/span> file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; res = &lt;span style="color:#ff7b72">new&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> currentPath = (System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) + file).Replace(&lt;span style="color:#a5d6ff">&amp;#34;file:\\&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> (ResXResourceReader resxReader = &lt;span style="color:#ff7b72">new&lt;/span> ResXResourceReader(currentPath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (DictionaryEntry entry &lt;span style="color:#ff7b72">in&lt;/span> resxReader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> res.Add((&lt;span style="color:#ff7b72">string&lt;/span>)entry.Key, (&lt;span style="color:#ff7b72">string&lt;/span>)entry.Value);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> a = e.Message;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> res;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Os parâmetros do arquivo precisam ser preenchidos com a localização do executável até o arquivo de recurso, no nosso caso é: &lt;code>&amp;quot;\\Properties\\John\\Resource.resx&amp;quot;&lt;/code>.&lt;/p>
&lt;h2 id="misturando-dicionários">Misturando dicionários&lt;/h2>
&lt;p>Já terminamos, primeiro lembre-se de adicionar &lt;code>System.Configuration&lt;/code> às referências para que você possa acessar &lt;code>app.settings&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; OverwriteDictionary(Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; currentDictionary, Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; newDictionary, &lt;span style="color:#ff7b72">bool&lt;/span> addIfDoesntExist = &lt;span style="color:#79c0ff">false&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> identifier = ConfigurationManager.AppSettings[&lt;span style="color:#a5d6ff">&amp;#34;CustomResources.Folder&amp;#34;&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (String.IsNullOrEmpty(identifier))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> currentDictionary;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> item &lt;span style="color:#ff7b72">in&lt;/span> newDictionary)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentDictionary[item.Key] = item.Value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span>(addIfDoesntExist)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentDictionary.Add(item.Key, item.Value);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> currentDictionary;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="obtendo-texto-do-dicionário">Obtendo texto do dicionário&lt;/h2>
&lt;p>Crie um método público que chame um método privado que selecione um idioma:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetText(&lt;span style="color:#ff7b72">string&lt;/span> key, &lt;span style="color:#ff7b72">string&lt;/span> language)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (language)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ResourceSpanish[key];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;en&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ResourceEnglish[key];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;No value with key: {key} and language: {language}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetText(&lt;span style="color:#ff7b72">string&lt;/span> key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> GetText(key, Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;No value with key: {key}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Observe que você terá que modificar a opção se estiver adicionando mais idiomas.&lt;/strong>&lt;/p>
&lt;h2 id="adicionando-código-ao-getter-de-propriedades">Adicionando código ao getter de propriedades&lt;/h2>
&lt;p>Como temos todos os métodos agora, podemos modificar o getter da propriedade pública para obter os valores.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; _ResourcesEnglish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ResourcesEnglish
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">get&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (_ResourcesEnglish == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> folderIndentifier = ConfigurationManager.AppSettings[&lt;span style="color:#a5d6ff">&amp;#34;CustomResources.Folder&amp;#34;&lt;/span>]; ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> baseResources = GetDictionaryFromEmbedded(&lt;span style="color:#a5d6ff">&amp;#34;resources-demo.Properties.Resource&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> customResources = GetDictionaryFromFile(&lt;span style="color:#a5d6ff">$&amp;#34;\\Properties\\{folderIndentifier}\\Resource.resx&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;en&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ResourcesEnglish = OverwriteDictionary(baseResources, customResources);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> _ResourcesEnglish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; _ResourcesSpanish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ResourcesSpanish
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">get&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (_ResourcesSpanish == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> folderIndentifier = ConfigurationManager.AppSettings[&lt;span style="color:#a5d6ff">&amp;#34;CustomResources.Folder&amp;#34;&lt;/span>]; ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> baseResources = GetDictionaryFromEmbedded(&lt;span style="color:#a5d6ff">&amp;#34;resources-demo.Properties.Resources&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> customResources = GetDictionaryFromFile(&lt;span style="color:#a5d6ff">$&amp;#34;\\Properties\\{folderIndentifier}\\Resources.es.resx&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ResourcesSpanish = OverwriteDictionary(baseResources, customResources);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> _ResourcesSpanish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>Primeiro obtemos o identificador de app.settings.&lt;/li>
&lt;li>Depois obtemos os recursos básicos, os incorporados.&lt;/li>
&lt;li>Depois disso obtemos os recursos customizados e para isso precisamos do nome da pasta (que é o identificador).&lt;/li>
&lt;li>Em seguida, misturamos e retornamos o valor.&lt;/li>
&lt;/ol>
&lt;p>Tudo isso será feito uma vez, após o carregamento lento.&lt;/p>
&lt;h1 id="teste">Teste&lt;/h1>
&lt;p>Tudo relacionado ao código está finalizado, então agora vamos testar, para obter um valor do dicionário temos que chamar o método &lt;code>CustomResources.GetText(string key)&lt;/code> que retorna o valor.&lt;/p>
&lt;h2 id="atualizando-arquivos-de-recursos-inteiros">Atualizando arquivos de recursos inteiros&lt;/h2>
&lt;p>Este teste é um caso em que queremos atualizar toda a chave/valor dos arquivos de recursos, como você pode ver nas imagens temos as mesmas chaves, mas valores diferentes.&lt;/p>
&lt;p>Vamos testar &lt;code>John&lt;/code>, e para definir isso teremos o app.config definido como &lt;code>&amp;lt;add key=&amp;quot;CustomResources.Folder&amp;quot; value=&amp;quot;John&amp;quot; /&amp;gt;&lt;/code>.&lt;/p>
&lt;p>Agora vamos verificar nosso arquivo de recurso base (&lt;code>Properties/Resource.es.resx&lt;/code>):&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/26d6cef147ca3cdd93a1d6cc4c60c212">&lt;img src="https://i.gyazo.com/26d6cef147ca3cdd93a1d6cc4c60c212.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>E então nosso arquivo de recurso externo (&lt;code>Properties/John/Resource.es.resx&lt;/code>):&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/fd6582da0d6a427c2b21cc8b1999f9e0">&lt;img src="https://i.gyazo.com/fd6582da0d6a427c2b21cc8b1999f9e0.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Ok com tudo definido, rodamos a aplicação console, vamos parar na parte &lt;code>get&lt;/code> das propriedades e verificar tudo&lt;/p>
&lt;p>&lt;code>folderIdentifier&lt;/code> tem o valor do appseting:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ac30d3deb7abe29a877b368d9300b990">&lt;img src="https://i.gyazo.com/ac30d3deb7abe29a877b368d9300b990.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>&lt;code>baseResources&lt;/code> tem o valor dos recursos base, os incorporados:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/a61410b365b55d4735f2b2622a18b966">&lt;img src="https://i.gyazo.com/a61410b365b55d4735f2b2622a18b966.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>&lt;code>customResources&lt;/code> possui os valores dos recursos externos dentro da pasta &lt;code>John&lt;/code>:&lt;a href="https://gyazo.com/0e93733fbd616b274a69cdffbc16afb1">&lt;img src="https://i.gyazo.com/0e93733fbd616b274a69cdffbc16afb1.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>E por fim, &lt;code>_ResourcesSpanish&lt;/code> tem o valor misturado dos recursos base aos recursos externos.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/0e25a79023364ea1d5be2109dbf6492c">&lt;img src="https://i.gyazo.com/0e25a79023364ea1d5be2109dbf6492c.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="atualizando-apenas-um">Atualizando apenas um&lt;/h2>
&lt;p>Agora vamos testar o mesmo mas com cenário diferente, basta atualizar uma chave e deixar a outra igual.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/9c2a05fb976f1f671b13c9cf77220336">&lt;img src="https://i.gyazo.com/9c2a05fb976f1f671b13c9cf77220336.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Como você pode ver, os arquivos têm o mesmo significado de valor para &lt;code>Action_greeting&lt;/code>, mas valores diferentes para &lt;code>Action_cancel&lt;/code>, portanto, devem ser atualizados apenas &lt;code>Action_cancel&lt;/code>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/16938859b05bb1f0fe08751a08b1fcad">&lt;img src="https://i.gyazo.com/16938859b05bb1f0fe08751a08b1fcad.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;h2 id="faltando-o-arquivo-de-recurso">Faltando o arquivo de recurso&lt;/h2>
&lt;p>Se você não fornecer o arquivo de recurso externo, não importa nada porque como esperamos ter um arquivo, se falhar retornará um dicionário vazio e ao misturar os dois dicionários terminará com o base.&lt;/p>
&lt;h2 id="par-ausente-no-recurso-incorporado">Par ausente no recurso incorporado&lt;/h2>
&lt;p>Se você tiver um par no arquivo de recurso externo, por padrão ele não o adicionará ao dicionário final, você pode alterar isso chamando o método &lt;code>OverwriteDictionary()&lt;/code> ao misturar os dois dicionários e definir o parâmetro &lt;code>addIfDoesntExist&lt;/code> como &lt;code>true&lt;/code>.&lt;/p>
&lt;h2 id="diferentes-idiomas">Diferentes idiomas&lt;/h2>
&lt;p>Como você pode ver, não especificamos nenhum idioma, porque tudo isso está sendo feito pela função &lt;code>GetText(string key)&lt;/code> que está chamando &lt;code>GetText(string key, string language)&lt;/code>, e o parâmetro &lt;code>language&lt;/code> é preenchido por &lt;code>TwoLetterISOLanguageName&lt;/code> que retorna o idioma atual do nosso thread.&lt;/p>
&lt;p>No meu caso tenho o espanhol como idioma padrão e por isso sempre mostra em espanhol, mas podemos tentar usar o inglês também.&lt;/p>
&lt;p>Vamos codificar um pouco e construir algo para verificar espanhol e inglês.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> Main(&lt;span style="color:#ff7b72">string&lt;/span>[] args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(&amp;#34;en-US&amp;#34;);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.Threading.Thread.CurrentThread.CurrentCulture = &lt;span style="color:#ff7b72">new&lt;/span> CultureInfo(&lt;span style="color:#a5d6ff">&amp;#34;en-US&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WriteText(&lt;span style="color:#a5d6ff">&amp;#34;Action_greeting&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WriteText(&lt;span style="color:#a5d6ff">&amp;#34;Action_cancel&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(&amp;#34;es-ES&amp;#34;);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.Threading.Thread.CurrentThread.CurrentCulture = &lt;span style="color:#ff7b72">new&lt;/span> CultureInfo(&lt;span style="color:#a5d6ff">&amp;#34;es-ES&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WriteText(&lt;span style="color:#a5d6ff">&amp;#34;Action_greeting&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WriteText(&lt;span style="color:#a5d6ff">&amp;#34;Action_cancel&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Read();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> WriteText(&lt;span style="color:#ff7b72">string&lt;/span> key) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;[Culture: {System.Threading.Thread.CurrentThread.CurrentCulture}] Key: {key} -&amp;gt; value: {CustomResources.GetText(key)}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Executando isso, a saída deve ser algo assim:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>[Culture: en-US] Key: Action_greeting -&amp;gt; value: Hello
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Culture: en-US] Key: Action_cancel -&amp;gt; value: Finish
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Culture: es-ES] Key: Action_greeting -&amp;gt; value: Hola
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Culture: es-ES] Key: Action_cancel -&amp;gt; value: Terminar
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://gyazo.com/de7e5efcc2ff5e82529749b1188706ff">&lt;img src="https://i.gyazo.com/de7e5efcc2ff5e82529749b1188706ff.png" alt="Imagem de Gyazo">&lt;/a>&lt;/p>
&lt;p>Os dois primeiros valores são de &lt;code>resources.resx&lt;/code> que estão em inglês e os últimos ambos estão em espanhol e os valores são recuperados de &lt;code>resources.es.resx&lt;/code>.&lt;/p>
&lt;p>#É isso&lt;/p>
&lt;p>Neste tutorial encontramos uma forma de mesclar recursos embarcados e externos para idiomas, essa foi uma solução para um problema que tivemos na equipe e está rodando desde então sem problemas.&lt;/p>
&lt;p>Você pode verificar o código fonte &lt;a href="https://github.com/emimontesdeoca/resources-demo">aqui&lt;/a>.&lt;/p>
&lt;p>Se você tiver alguma dúvida, sinta-se à vontade para me enviar um tweet para &lt;a href="https://twitter.com/emimontesdeocaa">@emimontesdeocaa&lt;/a> e entrarei em contato com você quando tiver tempo.&lt;/p></content:encoded><category>.NET</category></item><item><title>Teste de integração usando Bot Framework e DirectLine para casos de fluxo</title><link>https://emimontesdeoca.github.io/pt/posts/integration-test-bot-framework-with-flow-cases/</link><pubDate>Wed, 25 Apr 2018 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/integration-test-bot-framework-with-flow-cases/</guid><description>Estenda os testes de integração do Bot Framework para oferecer suporte a cenários de fluxo de conversação multiturno.</description><content:encoded>&lt;h2 id="introdução">Introdução&lt;/h2>
&lt;p>Nas postagens anteriores do blog, fizemos alguns testes de integração para casos únicos. “Casos únicos” são aqueles casos que após perguntar algo ao bot, ele responderá apenas uma vez e depois compararemos os resultados.&lt;/p>
&lt;p>&lt;strong>Este guia não explica como são feitas a autenticação e as chamadas de API. Se você quiser conferir, consulte o guia de casos únicos.&lt;/strong>&lt;/p>
&lt;h3 id="e-as-conversas-de-fluxo">E as conversas de fluxo?&lt;/h3>
&lt;p>Usando a forma atual não temos nenhum tipo de fluxo na conversa, por exemplo, se você quiser pedir ajuda e aí aparece um menu com diversas opções, e depois de selecionar uma delas, outro menu. Isso daria cerca de 2 ou mais &lt;code>responses&lt;/code>. &lt;strong>Então, isso significa que usar a solução de teste de integração que usamos antes não funcionará.&lt;/strong>&lt;/p>
&lt;p>Aqui está um diagrama de como funciona o teste de integração para casos únicos.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/8915f2653033c1143947ef59196403f4">&lt;img src="https://i.gyazo.com/8915f2653033c1143947ef59196403f4.png" alt="https://gyazo.com/8915f2653033c1143947ef59196403f4">&lt;/a>&lt;/p>
&lt;p>E aqui está um diagrama de como vamos adaptar a atual solução de teste de integração aos casos de fluxo.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83">&lt;img src="https://i.gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83.png" alt="https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83">&lt;/a>&lt;/p>
&lt;p>&lt;strong>A explicação a seguir não cobrirá todas as informações básicas de como funciona o Bot Framework. Se você não entender, consulte a documentação oficial.&lt;/strong>&lt;/p>
&lt;h2 id="exemplo-de-caso">Exemplo de caso&lt;/h2>
&lt;p>Para o guia a seguir, usarei um bot com um fluxo de conversa criado por mim, que pede ajuda e depois seleciona diferentes opções.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/0a1104cce67c07331a6e0fbd8e19b3e2">&lt;img src="https://i.gyazo.com/0a1104cce67c07331a6e0fbd8e19b3e2.gif" alt="https://gyazo.com/0a1104cce67c07331a6e0fbd8e19b3e2">&lt;/a>&lt;/p>
&lt;h2 id="nova-estrutura-json">Nova estrutura JSON&lt;/h2>
&lt;p>Agora que temos mais de um &lt;code>request&lt;/code> ao falar com o bot, precisamos modificar nossa estrutura json para adicionar todos os &lt;code>request&lt;/code> que iremos fazer.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;secret&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;direct-line-secret&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;directlineGenerateTokenEndpoint&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;https://directline.botframework.com/v3/directline/tokens/generate&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;directlineConversationEndpoint&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;https://directline.botframework.com/v3/directline/conversations/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entries&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;PedirAyuda&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requests&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Ayuda&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Telefono&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Oficina&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Tenerife&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;response&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.901Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;localTimestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T09:04:37+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;serviceUrl&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;http://localhost:50629&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;emulator&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;j98bbdf097a&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Bot&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;conversation&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;eabcie4be8ak&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;recipient&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;922920252&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;attachments&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;replyToId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;47me557ikbf7&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;assert&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Request.Text == Response.Text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Como você pode ver, há uma mudança importante, &lt;code>request&lt;/code> agora é &lt;code>requests&lt;/code>. Isso significa que agora temos um &lt;code>List&amp;lt;Activity&amp;gt;&lt;/code> em vez de um único &lt;code>activity&lt;/code>.&lt;/p>
&lt;h2 id="novos-objetos">Novos objetos&lt;/h2>
&lt;p>Anteriormente tínhamos nossos objetos definidos para casos únicos: &lt;code>TestEntry&lt;/code> e &lt;code>TestEntryCollection&lt;/code>. Para casos de fluxo estaremos criando novos objetos: &lt;code>TestEntryFlow&lt;/code> e &lt;code>TestEntryFlowCollection&lt;/code>.&lt;/p>
&lt;h3 id="testentryflow">&lt;code>TestEntryFlow&lt;/code>&lt;/h3>
&lt;p>Este objeto é para cada entrada que temos na coleção, veja que o objeto &lt;code>Requests&lt;/code> agora é um &lt;code>List&amp;lt;Activity&amp;gt;&lt;/code> em vez de um único &lt;code>Activity&lt;/code> como mencionei antes.&lt;/p>
&lt;p>Como perguntaremos ao bot várias vezes, precisamos ter vários &lt;code>activities&lt;/code> que serão enviados para a conversa.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TestEntryFlow&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Entry name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;name&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Activity requested by the entry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;requests&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;Activity&amp;gt; Requests { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Activity response expected by the entry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;response&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Response { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert value in string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;assert&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Assert { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="testentirescollection">&lt;code>TestEntiresCollection&lt;/code>&lt;/h3>
&lt;p>Este objeto conterá as informações relevantes para &lt;code>DirectLine&lt;/code> como o &lt;code>secret&lt;/code> e os endpoints, além da lista de &lt;code>Entries&lt;/code> que iremos testar.&lt;/p>
&lt;p>Observe que, o &lt;code>Entries&lt;/code> agora é uma lista de &lt;code>TestEntryFlow&lt;/code> e não de &lt;code>TestEntry&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TestEntryFlowCollection&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// DirectLine Secret&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;secret&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Secret { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Endpoint to get the token using the secret for DirectLine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;directlineGenerateTokenEndpoint&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> DirectLineGenerateTokenEndpoint { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Endpoint for a conversation in DirectLine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;directlineConversationEndpoint&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> DirectLineConversationEndpoint { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Entries list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;entries&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;TestEntryFlow&amp;gt; Entries { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="criando-o-testmethod-para-casos-de-fluxo">Criando o &lt;code>TestMethod&lt;/code> para casos de fluxo&lt;/h2>
&lt;h3 id="novo-fluxo">Novo fluxo&lt;/h3>
&lt;p>Em primeiro lugar, dê uma olhada novamente no diagrama(é o mesmo que postei acima).&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83">&lt;img src="https://i.gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83.png" alt="https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83">&lt;/a>&lt;/p>
&lt;p>Como você pode ver, a estrutura do fluxo para fazer o teste é praticamente a mesma:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Obtenha informações&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Autenticar&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Crie uma conversa&lt;strong>E aqui o que muda, agora temos que enviar várias vezes toda a solicitação para a conversa. Para fazer isso, precisamos fazer um loop para cada solicitação, enviá-la ao bot e comparar a resposta mais recente com a resposta esperada.&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Envie todas as solicitações&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Receba todas as mensagens&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Obtenha a resposta mais recente&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Compare com a resposta esperada&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Afirmar resultado&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="código">Código&lt;/h3>
&lt;p>Em primeiro lugar, precisamos obter as informações do arquivo, é o mesmo que fizemos antes com os casos únicos.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Load entries from file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> path = System.IO.File.ReadAllText(&lt;span style="color:#a5d6ff">@&amp;#34;C:\dataFlow.json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Deserialize to object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> data = JsonConvert.DeserializeObject&amp;lt;TestEntryFlowCollection&amp;gt;(path);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora temos que fazer um loop para cada &lt;code>TestEntryFlow&lt;/code>, do &lt;code>data.entries&lt;/code>, com isso podemos seguir o mesmo fluxo que fizemos nos casos únicos até a nova parte, onde fazemos o loop no &lt;code>requests&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with current requested values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> token, newToken, conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Activity latestResponse = &lt;span style="color:#ff7b72">new&lt;/span> Activity();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Act for step&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 1 - Get token using secret from DirectLine in BotFramework panel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>token = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(data.Secret, data.DirectLineGenerateTokenEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>).token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 2 - Create a new conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> createdConversation = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(token, data.DirectLineConversationEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// This returns a new token and a conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>newToken = createdConversation.token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>conversationId = createdConversation.conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 3 - Send an activity to the conversation with new token and conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + &lt;span style="color:#a5d6ff">&amp;#34;/activities&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A etapa a seguir é bem simples, temos que fazer um loop no &lt;code>entry.requests&lt;/code> e enviar cada &lt;code>activity&lt;/code> para a conversa.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">foreach&lt;/span> (Activity step &lt;span style="color:#ff7b72">in&lt;/span> entry.Requests)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (step.Type == ActivityTypes.Message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Step&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(step));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 4 - Get all activities, we get a List&amp;lt;activity&amp;gt; and a watermark&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> getLastActivity = Utils.downloadString&amp;lt;ActivityResponse&amp;gt;(newToken, directlineConversationActivitiesEndpoint);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 5 - Get the latest activity which is the response we should be expecting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Usamos o &lt;code>watermark&lt;/code> para obter a última mensagem, o &lt;code>watermark&lt;/code> é um valor que a API DirectLine retorna ao solicitar as informações da conversa.&lt;/p>
&lt;p>Depois disso, só temos que preencher o &lt;code>globals&lt;/code> com nosso &lt;code>latestReponse&lt;/code> e &lt;code>expectedResponse&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with new values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> globals = &lt;span style="color:#ff7b72">new&lt;/span> Objects.Globals { Request = entry.Response, Response = latestResponse };
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E para finalizar o caso, avaliamos a string &lt;code>assert&lt;/code> no &lt;code>entry&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Assert.IsTrue(&lt;span style="color:#ff7b72">await&lt;/span> CSharpScript.EvaluateAsync&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt;(entry.Assert, globals: globals));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="código-final">Código final&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task ShouldTestFlowCases()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Load entries from file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> path = System.IO.File.ReadAllText(&lt;span style="color:#a5d6ff">@&amp;#34;C:\dataFlow.json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Deserialize to object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> data = JsonConvert.DeserializeObject&amp;lt;TestEntryFlowCollection&amp;gt;(path);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Flow: Arrange -&amp;gt; Act -&amp;gt; arrange -&amp;gt; assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (TestEntryFlow entry &lt;span style="color:#ff7b72">in&lt;/span> data.Entries)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with current requested values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> token, newToken, conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Activity latestResponse = &lt;span style="color:#ff7b72">new&lt;/span> Activity();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Act for step&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 1 - Get token using secret from DirectLine in BotFramework panel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> token = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(data.Secret, data.DirectLineGenerateTokenEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>).token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 2 -Create a new conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> createdConversation = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(token, data.DirectLineConversationEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// This returns a new token and a conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newToken = createdConversation.token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conversationId = createdConversation.conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 3 - Send an activity to the conversation with new token and conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + &lt;span style="color:#a5d6ff">&amp;#34;/activities&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (Activity step &lt;span style="color:#ff7b72">in&lt;/span> entry.Requests)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (step.Type == ActivityTypes.Message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Step&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(step));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 4 - Get all activities, we get a List&amp;lt;activity&amp;gt; and a watermark&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> getLastActivity = Utils.downloadString&amp;lt;ActivityResponse&amp;gt;(newToken, directlineConversationActivitiesEndpoint);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 5 - Get the latest activity which is the response we should be expecting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with new values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> globals = &lt;span style="color:#ff7b72">new&lt;/span> Objects.Globals { Request = entry.Response, Response = latestResponse };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.IsTrue(&lt;span style="color:#ff7b72">await&lt;/span> CSharpScript.EvaluateAsync&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt;(entry.Assert, globals: globals));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="melhorias">Melhorias&lt;/h2>
&lt;p>Acredito que um fluxo melhor poderia ser possível, mas essa melhoria significará que a estrutura JSON também deve ser alterada. Além disso, para que isso aconteça, o json precisa estar mais preenchido.&lt;/p>
&lt;p>Para melhorar esse teste, deveríamos ter um &lt;code>response&lt;/code> para cada &lt;code>request&lt;/code>, e deveríamos estar afirmando toda vez que enviarmos uma mensagem. A maneira como estamos fazendo isso agora é armazenando todos os &lt;code>requests&lt;/code> e o &lt;strong>[[TOK_56]]]&lt;/strong> final.&lt;/p>
&lt;p>Fiz um diagrama para mostrar como ficaria.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/dfd4e9f87ff69159f02a0bcc70ae1edc">&lt;img src="https://i.gyazo.com/dfd4e9f87ff69159f02a0bcc70ae1edc.png" alt="https://gyazo.com/dfd4e9f87ff69159f02a0bcc70ae1edc">&lt;/a>&lt;/p>
&lt;p>Acredito fortemente que essa forma é muito melhor no geral para a integridade do teste, já que você está testando praticamente todos os comportamentos do fluxo, em vez de apenas testar a resposta final.&lt;/p>
&lt;hr>
&lt;p>&lt;strong>Bem, isso é tudo neste guia, lembre-se que este guia pretende ser uma continuação do guia de casos únicos, se você se sentir perdido, verifique aquele guia que é mais longo e tem mais explicações para tudo.&lt;/strong>&lt;/p>
&lt;p>Lembre-se de que todo o código está armazenado em meu github em &lt;a href="https://github.com/emimontesdeoca/integration-test-directline-bot-framework">this&lt;/a> repositório.&lt;/p>
&lt;p>Tenha um bom dia!&lt;/p></content:encoded><category>.NET</category><category>Bot Framework</category></item><item><title>Teste de integração usando Bot Framework e DirectLine (1)</title><link>https://emimontesdeoca.github.io/pt/posts/integration-test-bot-framework-1/</link><pubDate>Tue, 24 Apr 2018 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/integration-test-bot-framework-1/</guid><description>Configure testes de integração para chatbots do Bot Framework usando API DirectLine e casos de teste JSON.</description><content:encoded>&lt;p>Como comecei a trabalhar em uma empresa por um curto período de tempo, fui designado para trabalhar em testes de integração do Bot Framework.&lt;/p>
&lt;p>Meu trabalho no grupo é fazer com que o bot fique o mais estável possível, mas primeiro, o que é Bot Framework?&lt;/p>
&lt;blockquote>
&lt;p>Crie, conecte, implante e gerencie bots inteligentes para interagir naturalmente com seus usuários em um site, aplicativo, Cortana, Microsoft Teams, Skype, Slack, Facebook Messenger e muito mais. Comece rapidamente com um ambiente completo de criação de bots, pagando apenas pelo que usar.&lt;/p>&lt;/blockquote>
&lt;p>Você pode encontrar mais informações sobre o Bot Framework diretamente &lt;a href="https://dev.botframework.com/">aqui&lt;/a>.&lt;/p>
&lt;p>&lt;strong>A explicação a seguir não cobrirá todas as informações básicas de como funciona o Bot Framework. Se você não entender, consulte a documentação oficial.&lt;/strong>&lt;/p>
&lt;h2 id="por-que-preciso-de-teste-de-integração">Por que preciso de teste de integração?&lt;/h2>
&lt;p>Os testes de integração são necessários porque toda vez que um dos meus colegas de trabalho envia uma correção, um novo recurso ou até mesmo um novo bug, esses testes serão executados antes de enviar o código para produção e se algum dos testes falhar, o código não irá para produção, o que significa que o usuário final não terá o bug.&lt;/p>
&lt;h2 id="teste-de-integração-em-bots">Teste de integração em bots?&lt;/h2>
&lt;p>Acredito que em bots os testes de integração são muito importantes. Você não pode ter um bot onde alguns de seus menus não estejam funcionando ou alguns dos recursos não retornem nada.&lt;/p>
&lt;p>As empresas estão usando bots para seus clientes porque não querem que as pessoas fiquem ocupadas com seus problemas. Se um bot puder ajudar um usuário, os outros trabalhadores poderão usar seu tempo para fazer algo mais importante.&lt;/p>
&lt;h2 id="visão-geral-da-solução">Visão geral da solução.&lt;/h2>
&lt;p>Para fazer isso funcionar, utilizei um projeto de Teste no Visual Studio, que utilizará WebClient para API Rest e um arquivo Json, onde armazenaremos nossos cases.&lt;/p>
&lt;h2 id="arquivo-json">arquivo JSON&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;secret&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;direct-line-secret&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;directlineGenerateTokenEndpoint&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;https://directline.botframework.com/v3/directline/tokens/generate&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;directlineConversationEndpoint&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;https://directline.botframework.com/v3/directline/conversations/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entries&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;DecirHola&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;request&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Hola&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;response&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.901Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;localTimestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T09:04:37+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;serviceUrl&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;http://localhost:50629&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;emulator&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;j98bbdf097a&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Bot&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;conversation&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;eabcie4be8ak&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;recipient&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;No tengo respuesta para eso.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;attachments&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;replyToId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;47me557ikbf7&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;assert&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Request.Text == Response.Text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Como você pode ver, temos:&lt;/p>
&lt;ul>
&lt;li>Segredo da linha direta -&amp;gt; segredo do bot publicado&lt;/li>
&lt;li>Endpoint de geração de token Directline -&amp;gt; endpoint para obter o token usando o segredo&lt;/li>
&lt;li>Endpoint de conversação direta -&amp;gt; endpoint para brincar com a conversa&lt;/li>
&lt;li>Entrada -&amp;gt; o caso de teste
&lt;ul>
&lt;li>Solicitar -&amp;gt; o que enviamos para a conversa&lt;/li>
&lt;li>Resposta -&amp;gt; o que esperamos obter&lt;/li>
&lt;li>Afirmar -&amp;gt; o que estamos comparando&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="desserialização">Desserialização&lt;/h2>
&lt;p>Temos o arquivo json perfeitamente formatado, agora temos que carregá-lo na solução, então usaremos JSON.NET e algumas classes. Primeiro temos a coleção de entradas, que tem tudo, e depois temos, para cada coleção uma lista de entradas.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Object to parse from the file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TestEntriesCollection&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// DirectLine Secret&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;secret&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Secret { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Endpoint to get the token using the secret for DirectLine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;directlineGenerateTokenEndpoint&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> DirectLineGenerateTokenEndpoint { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Endpoint for a conversation in DirectLine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;directlineConversationEndpoint&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> DirectLineConversationEndpoint { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Entries list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;entries&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;TestEntry&amp;gt; Entries { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E este é o objeto que possui o nome, solicitação, resposta e afirmação do caso de teste.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TestEntry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Entry name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;name&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Activity requested by the entry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;request&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Request { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Activity response expected by the entry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;response&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Response { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert value in string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;assert&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Assert { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="análise-de-json-para-objeto-no-caso-de-teste">Análise de JSON para objeto no caso de teste&lt;/h2>
&lt;p>Tendo as classes para o objeto de análise, é muito fácil, pois precisamos ler como o objeto.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Load entries from file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> path = System.IO.File.ReadAllText(&lt;span style="color:#a5d6ff">@&amp;#34;C:\data.json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Deserialize to object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> data = JsonConvert.DeserializeObject&amp;lt;TestEntriesCollection&amp;gt;(path);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora com esta coleção, poderemos percorrê-la e obter as informações usando, por exemplo, um foreach.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (TestEntry entry &lt;span style="color:#ff7b72">in&lt;/span> data.Entries)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ....
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E isso é tudo por esta parte, a próxima parte incluirá a autorização para DirectLine, lembre-se que todo o código está armazenado no meu github no repositório &lt;a href="https://github.com/emimontesdeoca/integration-test-directline-bot-framework">this&lt;/a>.&lt;/p></content:encoded><category>.NET</category><category>Bot Framework</category></item><item><title>Teste de integração usando Bot Framework e DirectLine (2)</title><link>https://emimontesdeoca.github.io/pt/posts/integration-test-bot-framework-2/</link><pubDate>Tue, 24 Apr 2018 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/integration-test-bot-framework-2/</guid><description>Implemente autorização DirectLine e chamadas de API para testes de integração do Bot Framework (Parte 2).</description><content:encoded>&lt;h4 id="nesta-parte-faremos-a-autorização-directline-e-obteremos-os-valores-da-resposta-do-bot">Nesta parte faremos a autorização DirectLine e obteremos os valores da resposta do bot.&lt;/h4>
&lt;p>Agora que fizemos a desserialização, é hora de pegar as informações da coleção e os inteiros que usaremos para obter a autorização, fazer as chamadas da API e afirmar o resultado.&lt;/p>
&lt;p>&lt;strong>A explicação a seguir não cobrirá todas as informações básicas de como funciona o Bot Framework. Se você não entender, consulte a documentação oficial.&lt;/strong>&lt;/p>
&lt;h2 id="fazendo-chamadas-de-api-usando-webclient">Fazendo chamadas de API usando WebClient&lt;/h2>
&lt;p>Para facilitar a chamada da api, criei uma classe &lt;code>utils&lt;/code>, onde salvamos as funções que usaremos algumas vezes, essa classe inclui &lt;code>uploadString&lt;/code> para POST e &lt;code>downloadString&lt;/code> para GET.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Utils&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Uploads to an URL and gets result&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;typeparam name=&amp;#34;T&amp;#34;&amp;gt;Type of object you are receiving&amp;lt;/typeparam&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;bearer&amp;#34;&amp;gt;Token&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;url&amp;#34;&amp;gt;Url&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;serializedJson&amp;#34;&amp;gt;Serialized JSON to send&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T uploadString&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> bearer, &lt;span style="color:#ff7b72">string&lt;/span> url, &lt;span style="color:#ff7b72">string&lt;/span> serializedJson)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> serializedResult = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Webclient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> client = &lt;span style="color:#ff7b72">new&lt;/span> WebClient())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Add headers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.Headers.Add(&lt;span style="color:#a5d6ff">&amp;#34;Content-Type&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;application/json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.Headers.Add(&lt;span style="color:#a5d6ff">&amp;#34;Authorization&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">$&amp;#34;Bearer {bearer}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Upload string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> serializedResult = client.UploadString(url, serializedJson);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Get result and return it as an object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(serializedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Downloads from URL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;typeparam name=&amp;#34;T&amp;#34;&amp;gt;Type of object you are receiving&amp;lt;/typeparam&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;bearer&amp;#34;&amp;gt;Token&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;url&amp;#34;&amp;gt;Url&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T downloadString&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> bearer, &lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> serializedResult = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Webclient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> client = &lt;span style="color:#ff7b72">new&lt;/span> WebClient())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Add headers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.Headers.Add(&lt;span style="color:#a5d6ff">&amp;#34;Content-Type&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;application/json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.Headers.Add(&lt;span style="color:#a5d6ff">&amp;#34;Authorization&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">$&amp;#34;Bearer {bearer}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Download string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> serializedResult = client.DownloadString(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Get result and return it as an object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(serializedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="autorização-directline">Autorização DirectLine&lt;/h2>
&lt;p>Se você ler a documentação oficial, poderá descobrir como fazer, e é bem fácil, usando nossas funções é ainda mais fácil. Primeiramente lembre-se que estamos dentro da instrução foreach, pois estamos fazendo a autenticação para cada caso, caso o tempo acabe, o que significará que o teste falhará.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with current requested values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> token, newToken, conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Act&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 1 - Get token using secret from DirectLine in BotFramework panel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>token = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(data.Secret, data.DirectLineGenerateTokenEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>).token;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agora temos o token, que será usado para fazer todas as chamadas seguintes para o endpoint da conversa.&lt;/p>
&lt;h2 id="criando-uma-conversa">Criando uma conversa.&lt;/h2>
&lt;p>Para falar com o bot, primeiro precisamos criar uma conversa, esta conversa retornará um novo token que inclui o id da conversa.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 2 -Create a new conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> createdConversation = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(token, data.DirectLineConversationEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// This returns a new token and a conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>newToken = createdConversation.token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>conversationId = createdConversation.conversationId;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Além disso, armazenamos &lt;code>newToken&lt;/code> e &lt;code>conversationId&lt;/code>, ambos serão necessários para o usuário enviar mensagens ao bot.&lt;/p>
&lt;h2 id="enviar-atividade-para-conversa">Enviar atividade para conversa&lt;/h2>
&lt;p>Agora, com o &lt;code>conversationId&lt;/code> e o &lt;code>conversationEndpoint&lt;/code>, podemos criar o endpoint final para enviar um &lt;code>Activity&lt;/code> que é o &lt;code>request&lt;/code> do arquivo json.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 3 - Send an activity to the conversation with new token and conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + &lt;span style="color:#a5d6ff">&amp;#34;/activities&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(entry.Request));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="receba-a-última-mensagem">Receba a última mensagem&lt;/h2>
&lt;p>No histórico de mensagens, depois de enviarmos a atividade, o bot já deveria ter respondido, então temos que pegar todas as mensagens com a marca d&amp;rsquo;água e então usando essa marca d&amp;rsquo;água, filtrar a última mensagem/atividade.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 4 - Get all activities, we get a List&amp;lt;activity&amp;gt; and a watermark&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> getLastActivity = Utils.downloadString&amp;lt;ActivityResponse&amp;gt;(newToken, directlineConversationActivitiesEndpoint);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 5 - Get the latest activity which is the response we should be expecting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>E isso é tudo por esta parte, a próxima parte incluirá a parte onde obtemos o texto do &lt;code>assert&lt;/code> no json, convertemos para código como usar &lt;code>eval()&lt;/code> em Javascript, mas em C#, e então usando o &lt;code>Assert.isTrue()&lt;/code> para obter o resultado final do teste.&lt;/p>
&lt;p>Lembre-se de que todo o código está armazenado em meu github em &lt;a href="https://github.com/emimontesdeoca/integration-test-directline-bot-framework">este&lt;/a> repositório.&lt;/p></content:encoded><category>.NET</category><category>Bot Framework</category></item><item><title>Teste de integração usando Bot Framework e DirectLine (3)</title><link>https://emimontesdeoca.github.io/pt/posts/integration-test-bot-framework-3/</link><pubDate>Tue, 24 Apr 2018 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/posts/integration-test-bot-framework-3/</guid><description>Avalie as respostas do bot usando Roslyn CodeAnalysis em testes de integração do Bot Framework (Parte 3).</description><content:encoded>&lt;h4 id="esta-é-a-última-parte-do-guia-nesta-parte-estaremos-avaliando-o-texto-do-assert-no-json">Esta é a última parte do guia, nesta parte estaremos avaliando o texto do assert no json.&lt;/h4>
&lt;p>Agora que usamos o &lt;code>request&lt;/code> e enviamos como um &lt;code>activity&lt;/code> para o bot, obtivemos o &lt;code>response&lt;/code> e temos que comparar o que o &lt;code>assert&lt;/code> no json (a resposta que armazenamos no json, esta é a resposta esperada) com a resposta que obtivemos do bot.&lt;/p>
&lt;p>&lt;strong>A explicação a seguir não cobrirá todas as informações básicas de como funciona o Bot Framework. Se você não entender, consulte a documentação oficial.&lt;/strong>&lt;/p>
&lt;h2 id="adicionando-microsoftcodeanalysis-à-solução">Adicionando Microsoft.CodeAnalysis à solução&lt;/h2>
&lt;p>Primeiro de tudo, temos que incluir CodeAnalysis como um pacote NuGet.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ce97a60ecab998f162f6ac7a2ab2c9a7">&lt;img src="https://i.gyazo.com/ce97a60ecab998f162f6ac7a2ab2c9a7.png" alt="https://gyazo.com/ce97a60ecab998f162f6ac7a2ab2c9a7">&lt;/a>&lt;/p>
&lt;p>Após a instalação, lembre-se de adicionar o pacote ao arquivo &lt;code>.cs&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.CodeAnalysis.CSharp.Scripting&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="criando-um-objeto-globals">Criando um objeto &lt;code>Globals&lt;/code>&lt;/h2>
&lt;p>Para avaliar, temos que passar os parâmetros ao avaliador. Este avaliador precisa de um arquivo de configuração.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Object to pass parameters to Roslyn compiler&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Globals&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// ExpectedResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Request;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// ReceivedResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Response;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Essa parte é muito importante, nesse arquivo de configuração estaremos incluindo quais objetos iremos comparar, então para nós precisamos passar a &lt;strong>resposta esperada&lt;/strong> e a &lt;strong>resposta recebida&lt;/strong>.&lt;/p>
&lt;p>Com essas informações, ele estará usando o &lt;code>assert&lt;/code> no arquivo json e estará avaliando o que está escrito.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with new values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> globals = &lt;span style="color:#ff7b72">new&lt;/span> Objects.Globals { Request = entry.Response, Response = latestResponse };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Assert.IsTrue(&lt;span style="color:#ff7b72">await&lt;/span> CSharpScript.EvaluateAsync&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt;(entry.Assert, globals: globals));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>O &lt;code>EvaluateAsync&amp;lt;T&amp;gt;&lt;/code> avalia e retorna T, no nosso caso passamos o &lt;code>string&lt;/code> para avaliar e o &lt;code>globals&lt;/code>, que possui os dados onde irá avaliar.&lt;/strong>&lt;/p>
&lt;p>Vou tentar explicar isso com um exemplo, usando uma entrada (que possui nome, solicitação, resposta e afirmação).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;DecirHola&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;request&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Hola&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;response&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.901Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;localTimestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T09:04:37+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;serviceUrl&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;http://localhost:50629&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;emulator&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;j98bbdf097a&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Bot&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;conversation&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;eabcie4be8ak&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;recipient&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;No tengo respuesta para eso.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;attachments&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;replyToId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;47me557ikbf7&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;assert&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Request.Text == Response.Text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Vamos pegar as coisas importantes desta entrada, primeiro a afirmação &lt;strong>&lt;code>&amp;quot;assert&amp;quot;: &amp;quot;Request.Text == Response.Text&amp;quot;&lt;/code>&lt;/strong>, isso significa que ele irá comparar o &lt;code>Request.Text&lt;/code> com o &lt;code>Response.Text&lt;/code> e retornar o valor como um booleano.&lt;/p>
&lt;p>Mas quando chamamos a função &lt;code>await CSharpScript.EvaluateAsync&amp;lt;bool&amp;gt;(entry.Assert, globals: globals)&lt;/code> estamos passando 2 parâmetros:&lt;/p>
&lt;ul>
&lt;li>&lt;code>string&lt;/code> string para avaliar -&amp;gt; &lt;code>&amp;quot;Request.Text == Response.Text&amp;quot;&lt;/code>&lt;/li>
&lt;li>Dados &lt;code>globals&lt;/code> para o avaliador -&amp;gt; neste caso temos que fornecer um &lt;code>Request&lt;/code> e um &lt;code>Response&lt;/code>, a solicitação é a nossa &lt;strong>resposta esperada&lt;/strong> e a resposta é a &lt;strong>resposta recebida&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>Como estamos preenchendo os dados no avaliador, agora podemos usar a string e avaliar, assim ela retornará &lt;code>true&lt;/code> ou &lt;code>false&lt;/code>.&lt;/p>
&lt;h2 id="concluído">Concluído&lt;/h2>
&lt;p>Terminamos, aqui você pode ver o &lt;code>TestMethod&lt;/code> finalizado&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task ShouldTestSingleCases()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Load entries from file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> path = System.IO.File.ReadAllText(&lt;span style="color:#a5d6ff">@&amp;#34;C:\data.json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Deserialize to object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> data = JsonConvert.DeserializeObject&amp;lt;TestEntriesCollection&amp;gt;(path);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Flow: Arrange -&amp;gt; Act -&amp;gt; arrange -&amp;gt; assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (TestEntry entry &lt;span style="color:#ff7b72">in&lt;/span> data.Entries)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with current requested values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> token, newToken, conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (entry.Request.Type == ActivityTypes.Message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Act&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 1 - Get token using secret from DirectLine in BotFramework panel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> token = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(data.Secret, data.DirectLineGenerateTokenEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>).token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 2 -Create a new conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> createdConversation = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(token, data.DirectLineConversationEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// This returns a new token and a conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newToken = createdConversation.token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conversationId = createdConversation.conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 3 - Send an activity to the conversation with new token and conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + &lt;span style="color:#a5d6ff">&amp;#34;/activities&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(entry.Request));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 4 - Get all activities, we get a List&amp;lt;activity&amp;gt; and a watermark&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> getLastActivity = Utils.downloadString&amp;lt;ActivityResponse&amp;gt;(newToken, directlineConversationActivitiesEndpoint);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 5 - Get the latest activity which is the response we should be expecting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with new values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> globals = &lt;span style="color:#ff7b72">new&lt;/span> Objects.Globals { Request = entry.Response, Response = latestResponse };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.IsTrue(&lt;span style="color:#ff7b72">await&lt;/span> CSharpScript.EvaluateAsync&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt;(entry.Assert, globals: globals));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="diagrama">Diagrama&lt;/h2>
&lt;p>Aqui está um diagrama de todo o fluxo que seguimos para chegar a este ponto, espero que se você não entendeu algo, isso esclareça tudo.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/1e3b7c9c2286844062878b4b8ca02d2d">&lt;img src="https://i.gyazo.com/1e3b7c9c2286844062878b4b8ca02d2d.png" alt="https://gyazo.com/1e3b7c9c2286844062878b4b8ca02d2d">&lt;/a>&lt;/p>
&lt;p>E isso é tudo, isso é feito para casos únicos onde o caso é 1 para 1, o usuário envia um &lt;code>activity&lt;/code> e o bot retorna outro único &lt;code>activity&lt;/code>.&lt;/p>
&lt;p>Espero que tenham gostado, &lt;strong>o próximo serão os casos de fluxo de testes com mais de uma resposta.&lt;/strong>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/d964cfac395ed438a5282e60614863e7">&lt;img src="https://i.gyazo.com/d964cfac395ed438a5282e60614863e7.png" alt="https://gyazo.com/d964cfac395ed438a5282e60614863e7">&lt;/a>&lt;/p>
&lt;p>Lembre-se de que todo o código está armazenado em meu github em &lt;a href="https://github.com/emimontesdeoca/integration-test-directline-bot-framework">este&lt;/a> repositório.&lt;/p></content:encoded><category>.NET</category><category>Bot Framework</category><category>NuGet</category></item><item><title>Palestras</title><link>https://emimontesdeoca.github.io/pt/speaking/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/speaking/</guid><description>Palestras e sessões de conferências de Emiliano Montesdeoca — Microsoft MVP e palestrante internacional.</description><content:encoded>&lt;p>Sou um palestrante frequente em conferências internacionais de tecnologia, com foco em &lt;strong>.NET&lt;/strong>, &lt;strong>Azure&lt;/strong>, &lt;strong>IA&lt;/strong> e &lt;strong>desenvolvimento cloud-native&lt;/strong>. Fui homenageado como &lt;strong>Sessionize Most Active Speaker&lt;/strong> em 2023, 2024 e 2025, e detenho a certificação &lt;strong>Microsoft MVP&lt;/strong> em Tecnologias para Desenvolvedores.&lt;/p>
&lt;p>Se deseja que eu fale no seu evento, &lt;a href="mailto:emimontesdeoca@outlook.es">vamos conversar&lt;/a>!&lt;/p>
&lt;hr>
&lt;h2 id="2026">2026&lt;/h2>
&lt;h3 id="blazor-em-2026">Blazor em 2026&lt;/h3>
&lt;p>Um olhar sobre o estado atual do Blazor em 2026 e como ele evoluiu junto ao .NET 10. Revisamos as últimas mudanças e melhorias, seu impacto no desenvolvimento de aplicações do mundo real e qual modelo de hospedagem Blazor se encaixa melhor em cada cenário.&lt;/p>
&lt;h3 id="turbinando-o-suporte-de-nível-3-com-ia-azure-functions--openai-em-ação">Turbinando o Suporte de Nível 3 com IA: Azure Functions + OpenAI em Ação&lt;/h3>
&lt;p>Como Azure Functions, alertas do Azure Monitor e Azure OpenAI trabalham juntos para acelerar o suporte de Nível 3. Desde triage automático de alertas até análise inteligente de logs — um olhar prático sobre adicionar IA a fluxos de trabalho de suporte reais.&lt;/p>
&lt;h3 id="do-dashboard-ao-agente-o-próximo-passo-da-observabilidade">Do dashboard ao agente: o próximo passo da observabilidade&lt;/h3>
&lt;p>Uma sessão prática conectando o mundo dos agentes IA e a observabilidade em produção. De Semantic Kernel a agentes autônomos que consultam dados OpenTelemetry, interagem com Grafana via MCP e ajudam a operar e otimizar sistemas reais.&lt;/p>
&lt;p>&lt;strong>Em breve&lt;/strong>: &lt;a href="https://globalazure.es">Global Azure 2026&lt;/a> — Abril 2026, Madrid, Espanha&lt;/p>
&lt;hr>
&lt;h2 id="2025">2025&lt;/h2>
&lt;h3 id="o-framework-de-agentes-da-microsoft-para-salvar-o-natal">O Framework de Agentes da Microsoft para salvar o Natal&lt;/h3>
&lt;p>Construindo e coordenando múltiplos agentes IA usando o Microsoft Agent Framework para encontrar os presentes de Natal perfeitos. Cada agente se especializa — desde geração de ideias de presentes até comparação de preços — trabalhando juntos para compras natalinas mais inteligentes.&lt;/p>
&lt;h3 id="potencializando-o-suporte-de-nível-3-com-ia-azure-functions--semantic-kernel-em-ação">Potencializando o Suporte de Nível 3 com IA: Azure Functions + Semantic Kernel em Ação&lt;/h3>
&lt;p>Azure Functions, Azure Monitor e Semantic Kernel trabalhando juntos para tornar o Suporte de Nível 3 mais rápido e eficiente — desde detecção automática de alertas até análise inteligente de logs.&lt;/p>
&lt;h3 id="o-que-há-de-novo-no-blazor-com-net-10">O que há de novo no Blazor com .NET 10?&lt;/h3>
&lt;p>Explorando os principais novos recursos do Blazor para .NET 10, incluindo melhorias de desempenho, formulários, estado persistente e interoperabilidade JavaScript mais poderosa.&lt;/p>
&lt;h3 id="ia-híbrida-com-c-semantic-kernel-e-gemini-construa-apps-empresariais-mais-inteligentes">IA Híbrida com C#, Semantic Kernel e Gemini: Construa Apps Empresariais mais Inteligentes&lt;/h3>
&lt;p>Orquestrando fluxos de trabalho de IA híbrida em C# — misturando modelos locais (Phi-3) com Gemini Pro/Ultra via Vertex AI, construindo agentes cognitivos com plugins do Semantic Kernel e projetando arquiteturas adaptativas.&lt;/p>
&lt;h3 id="net-aspire-construindo-apps-cloud-native-sem-dor-de-cabeça">.NET Aspire: Construindo Apps Cloud-Native Sem Dor de Cabeça&lt;/h3>
&lt;p>Uma sessão prática construindo microserviços cloud-first que nascem escaláveis, resilientes e observáveis. Explorando como o .NET Aspire elimina 70% do boilerplate de cloud.&lt;/p>
&lt;h3 id="net-aspire-cloud-native-sem-o-caos">.NET Aspire: Cloud-Native Sem o Caos&lt;/h3>
&lt;p>Exemplos do mundo real de design de aplicações que nascem escaláveis — com observabilidade e resiliência automática integradas desde o primeiro commit.&lt;/p>
&lt;hr>
&lt;h2 id="2024">2024&lt;/h2>
&lt;h3 id="ia-encontra-sql-construindo-uma-pizzaria-inteligente-com-net-e-semantic-kernel">IA Encontra SQL: Construindo uma Pizzaria Inteligente com .NET e Semantic Kernel&lt;/h3>
&lt;p>Interações de IA em linguagem natural que entendem seu código e seus dados — construindo uma pizzaria inteligente com .NET e Semantic Kernel.&lt;/p>
&lt;h3 id="net-aspire-aplicações-cloud-native-sem-complicações">.NET Aspire: Aplicações Cloud-Native sem complicações&lt;/h3>
&lt;p>Construindo microserviços cloud-first sem dor de cabeça — uma sessão prática e orientada a demos mostrando como o .NET Aspire simplifica o desenvolvimento cloud-native.&lt;/p>
&lt;h3 id="power-platform-a-jornada-do-low-code-para-as-tecnologias-pro-code">Power Platform: A Jornada do Low-code para as Tecnologias Pro-code&lt;/h3>
&lt;p>Transição de Power Apps de baixo código para componentes pro-code avançados usando TypeScript, tudo na mesma plataforma.&lt;/p>
&lt;h3 id="automação-inteligente-transforme-suas-aplicações-empresariais-com-ia-integrada">Automação Inteligente: Transforme suas Aplicações Empresariais com IA Integrada&lt;/h3>
&lt;p>Levando as aplicações .NET ao próximo nível — combinando lógica tradicional com raciocínio baseado em IA usando Semantic Kernel e .NET Aspire.&lt;/p>
&lt;h3 id="do-low-code-ao-pro-code-abordagens-para-o-desenvolvimento-de-ia-em-soluções-empresariais">Do Low-Code ao Pro-Code: Abordagens para o Desenvolvimento de IA em Soluções Empresariais&lt;/h3>
&lt;p>Explorando diferentes abordagens para soluções empresariais — de Power Platform com IA ao desenvolvimento tradicional com Semantic Kernel.&lt;/p>
&lt;h3 id="controlando-gastos-de-viagem-com-azure">Controlando Gastos de Viagem com Azure&lt;/h3>
&lt;p>Usando Azure AI Vision, Document Intelligence e OpenAI com Semantic Kernel para automatizar o gerenciamento de despesas de viagem.&lt;/p>
&lt;hr>
&lt;h2 id="badges-e-reconhecimentos">Badges e Reconhecimentos&lt;/h2>
&lt;ul>
&lt;li>🏆 &lt;strong>Sessionize Most Active Speaker 2025&lt;/strong>&lt;/li>
&lt;li>🏆 &lt;strong>Sessionize Most Active Speaker 2024&lt;/strong>&lt;/li>
&lt;li>🏆 &lt;strong>Sessionize Most Active Speaker 2023&lt;/strong>&lt;/li>
&lt;li>🏅 &lt;strong>Microsoft MVP em Tecnologias para Desenvolvedores&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://sessionize.com/emimontesdeoca/">Ver todas as sessões no Sessionize →&lt;/a>&lt;/p></content:encoded></item><item><title>Sobre mim</title><link>https://emimontesdeoca.github.io/pt/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/pt/about/</guid><description>Sobre Emiliano Montesdeoca — Microsoft MVP, Team Lead de Soluções em Nuvem e defensor da comunidade.</description><content:encoded>&lt;h2 id="sobre-mim">Sobre mim&lt;/h2>
&lt;p>Sou &lt;strong>Emiliano Montesdeoca&lt;/strong>, um desenvolvedor de software uruguaio-espanhol, &lt;strong>Microsoft MVP em Tecnologias para Desenvolvedores&lt;/strong>, e pai orgulhoso baseado em &lt;strong>Tenerife, Ilhas Canárias&lt;/strong>.&lt;/p>
&lt;p>Adoro enfrentar desafios técnicos complexos e construir soluções cloud escaláveis com tecnologias Microsoft. Meu kit de ferramentas diário gira em torno de &lt;strong>.NET&lt;/strong>, &lt;strong>Azure&lt;/strong>, &lt;strong>IA com Semantic Kernel&lt;/strong> e arquiteturas modernas como &lt;strong>.NET Aspire&lt;/strong>.&lt;/p>
&lt;h2 id="o-que-eu-faço">O que eu faço&lt;/h2>
&lt;p>Como &lt;strong>Cloud Solutions Team Lead&lt;/strong> na &lt;a href="https://intelequia.com">Intelequia Technologies&lt;/a>, lidero o design e a entrega de aplicações cloud-native — combinando visão estratégica com execução prática. Prospero na interseção entre arquitetura e código, garantindo que nossa equipe entregue soluções elegantes e prontas para produção.&lt;/p>
&lt;h2 id="comunidade">Comunidade&lt;/h2>
&lt;p>Sou um palestrante internacional frequente, tendo sido reconhecido como &lt;strong>Sessionize Most Active Speaker&lt;/strong> em &lt;a href="https://sessionize.com/emimontesdeoca/">2023&lt;/a>, &lt;a href="https://sessionize.com/emimontesdeoca/">2024&lt;/a> e &lt;a href="https://sessionize.com/emimontesdeoca/">2025&lt;/a>. Compartilho insights práticos do mundo real através de palestras em conferências ao redor do mundo e através dos meus blogs.&lt;/p>
&lt;p>Também sou o criador de &lt;a href="https://thedotnetblog.com">&lt;strong>The .NET Blog&lt;/strong>&lt;/a> — um recurso para a comunidade .NET — e escrevo regularmente neste blog sobre o que aprendo e construo.&lt;/p>
&lt;p>Mentoria e construção de comunidade são uma parte essencial de quem sou. Retribuir ao ecossistema que moldou minha carreira é algo que levo muito a sério.&lt;/p>
&lt;h2 id="vamos-nos-conectar">Vamos nos conectar&lt;/h2>
&lt;p>Estou sempre aberto a falar em eventos, colaborar em projetos ou conversar sobre arquitetura cloud e IA. Sinta-se à vontade para entrar em contato!&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Email&lt;/strong>: &lt;a href="mailto:emimontesdeoca@outlook.es">emimontesdeoca@outlook.es&lt;/a>&lt;/li>
&lt;li>&lt;strong>GitHub&lt;/strong>: &lt;a href="https://github.com/emimontesdeoca">@emimontesdeoca&lt;/a>&lt;/li>
&lt;li>&lt;strong>X / Twitter&lt;/strong>: &lt;a href="https://twitter.com/emimontesdeocaa">@emimontesdeocaa&lt;/a>&lt;/li>
&lt;li>&lt;strong>LinkedIn&lt;/strong>: &lt;a href="https://www.linkedin.com/in/emimontesdeoca/">emimontesdeoca&lt;/a>&lt;/li>
&lt;li>&lt;strong>Instagram&lt;/strong>: &lt;a href="https://www.instagram.com/emimontesdeoca/">@emimontesdeoca&lt;/a>&lt;/li>
&lt;li>&lt;strong>Sessionize&lt;/strong>: &lt;a href="https://sessionize.com/emimontesdeoca/">emimontesdeoca&lt;/a>&lt;/li>
&lt;/ul></content:encoded></item></channel></rss>