<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://ittranslator.cn/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ittranslator.cn/" rel="alternate" type="text/html" hreflang="zh" /><updated>2026-05-11T01:47:24+08:00</updated><id>https://ittranslator.cn/feed.xml</id><title type="html">技术译站</title><subtitle>一个技术的“译”站，专注于 IT 技术文章翻译和分享，微信公众号：技术译站</subtitle><author><name>技术译民</name></author><entry><title type="html">C# 实例解释面向对象编程中的依赖反转原则</title><link href="https://ittranslator.cn/dotnet/csharp/2022/07/25/SOLID-dependency-inversion-principle.html" rel="alternate" type="text/html" title="C# 实例解释面向对象编程中的依赖反转原则" /><published>2022-07-25T00:10:00+08:00</published><updated>2022-07-25T00:10:00+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2022/07/25/SOLID-dependency-inversion-principle</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2022/07/25/SOLID-dependency-inversion-principle.html"><![CDATA[<p>在面向对象编程中，<strong>SOLID</strong> 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。</p>

<p>SOLID 原则包含：</p>

<ul>
  <li><strong>S</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle.html">单一功能原则（single-responsibility principle）</a></li>
  <li><strong>O</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/17/SOLID-open-closed-principle.html">开闭原则（open-closed principle）</a></li>
  <li><strong>L</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/23/SOLID-liskov-substitution-principle.html">里氏替换原则（Liskov substitution principle）</a></li>
  <li><strong>I</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/13/SOLID-interface-segregation-principle.html">接口隔离原则（Interface segregation principle）</a></li>
  <li><strong>D</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/25/SOLID-dependency-inversion-principle.html">依赖反转原则（Dependency inversion principle）</a></li>
</ul>

<p>本文我们来介绍<em>依赖反转原则</em>。</p>

<h2 id="依赖反转原则">依赖反转原则</h2>

<p>在面向对象编程领域中，<strong>依赖反转原则</strong>（Dependency inversion principle，DIP）<strong>是指一种特定的解耦形式，使得高层次的模块不依赖于低层次模块的实现细节，依赖关系被颠倒（反转），从而使低层次模块依赖于高层次模块的需求抽象。</strong>（<em>传统的依赖关系创建在高层次上，而具体的策略设置则应用在低层次的模块上</em>）</p>

<!-- ![dependency inversion](/assets/images/2022/Dependency_inversion_2.png#center) -->

<p><img src="/assets/images/2022/Dependency_inversion.png#center" alt="dependency inversion" /></p>

<p>（<em>图1 中，高层 对象A 依赖于低层 对象B 的实现；图2 中，把高层 对象A 对低层对象的需求抽象为一个 接口A，低层 对象B 实现了 接口A，这就是依赖反转。</em>）</p>

<p>依赖反转原则约定：</p>

<ul>
  <li>高层次的模块不应该依赖于低层次的模块，两者都应该依赖于抽象接口。</li>
  <li>抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。</li>
</ul>

<p>该原则颠倒了一部分人对于面向对象设计的认识方式（如高层次和低层次对象都应该依赖于相同的抽象接口）。</p>

<p><em>依赖注入</em>是该原则的一种实现方式。</p>

<h2 id="c-示例">C# 示例</h2>

<p>先定义一个商品信息类：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ProductInfo</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">ID</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">ProductName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">ProductSpec</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Stock</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="糟糕的示范">糟糕的示范</h3>

<p>新建一个数据访问类 <em>ProductDataAccess</em> 和业务逻辑类 <em>ProductBusinessLogic</em>：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ProductDataAccess</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">ProductInfo</span> <span class="nf">GetDetail</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ProductInfo</span> <span class="n">product</span> <span class="p">=</span> <span class="k">new</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="n">ID</span> <span class="p">=</span> <span class="n">id</span><span class="p">,</span>
            <span class="n">ProductName</span> <span class="p">=</span> <span class="s">"白糖"</span><span class="p">,</span>
            <span class="n">ProductSpec</span> <span class="p">=</span> <span class="s">"500g"</span><span class="p">,</span>
            <span class="n">Stock</span> <span class="p">=</span> <span class="m">100</span>
        <span class="p">};</span>
        <span class="k">return</span> <span class="n">product</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">ProductBusinessLogic</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">ProductDataAccess</span> <span class="n">_productDataAccess</span><span class="p">;</span>
    <span class="k">public</span> <span class="nf">ProductBusinessLogic</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">_productDataAccess</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ProductDataAccess</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">ProductInfo</span> <span class="nf">GetProductDetails</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">_productDataAccess</span><span class="p">.</span><span class="nf">GetDetail</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在上面的代码中，高层次的类 <em>ProductBusinessLogic</em> 直接依赖于低层次的类 <em>ProductDataAccess</em>，这明显违反了 <em>依赖反转原则</em>。</p>

<h3 id="正确的示范">正确的示范</h3>

<p>根据 <em>依赖反转原则</em> 的要求，我们<strong>把高层对象</strong> <em>ProductBusinessLogic</em> 对低层对象<strong>的需求抽象为一个接口</strong> <em>IProductDataAccess</em>：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IProductDataAccess</span>
<span class="p">{</span>
    <span class="n">ProductInfo</span> <span class="nf">GetDetail</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在低层对象 <em>ProductDataAccess</em> 中实现接口 <em>IProductDataAccess</em>，然后在高层对象 <em>ProductBusinessLogic</em> 中引用（注入）接口 <em>IProductDataAccess</em>：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ProductDataAccess</span> <span class="p">:</span> <span class="n">IProductDataAccess</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">ProductInfo</span> <span class="nf">GetDetail</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ProductInfo</span> <span class="n">product</span> <span class="p">=</span> <span class="k">new</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="n">ID</span> <span class="p">=</span> <span class="n">id</span><span class="p">,</span>
            <span class="n">ProductName</span> <span class="p">=</span> <span class="s">"白糖"</span><span class="p">,</span>
            <span class="n">ProductSpec</span> <span class="p">=</span> <span class="s">"500g"</span><span class="p">,</span>
            <span class="n">Stock</span> <span class="p">=</span> <span class="m">100</span>
        <span class="p">};</span>
        <span class="k">return</span> <span class="n">product</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">ProductBusinessLogic</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IProductDataAccess</span> <span class="n">_productDataAccess</span><span class="p">;</span>
    <span class="k">public</span> <span class="nf">ProductBusinessLogic</span><span class="p">(</span><span class="n">IProductDataAccess</span> <span class="n">productDataAccess</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_productDataAccess</span> <span class="p">=</span> <span class="n">productDataAccess</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">ProductInfo</span> <span class="nf">GetProductDetails</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">_productDataAccess</span><span class="p">.</span><span class="nf">GetDetail</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这样，这些类的设计便遵守了<em>依赖反转原则</em>。</p>

<p>其实，ASP.NET Core 中服务的依赖注入正是遵循了<em>依赖反转原则</em>。</p>

<h2 id="总结">总结</h2>

<p>本文我介绍了 SOLID 原则中的<strong>依赖反转原则</strong>（Dependency inversion principle），并通过 C# 代码示例简明地诠释了它的含意和实现，希望对您有所帮助。</p>

<blockquote>
  <p>作者 ： 技术译民<br />
出品 ： <a href="https://ittranslator.cn/">技术译站</a></p>
</blockquote>

<p><br /></p>

<p>参考文档：</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/SOLID">https://en.wikipedia.org/wiki/SOLID</a></li>
  <li><a href="https://www.c-sharpcorner.com/blogs/dependency-inversion-principle-in-c-sharp">https://www.c-sharpcorner.com/blogs/dependency-inversion-principle-in-c-sharp</a></li>
  <li><a href="https://flylib.com/books/en/4.444.1.71/1/">https://flylib.com/books/en/4.444.1.71/1/</a></li>
</ul>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><summary type="html"><![CDATA[在面向对象编程中，SOLID 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。]]></summary></entry><entry><title type="html">C# 实例解释面向对象编程中的接口隔离原则</title><link href="https://ittranslator.cn/dotnet/csharp/2022/07/13/SOLID-interface-segregation-principle.html" rel="alternate" type="text/html" title="C# 实例解释面向对象编程中的接口隔离原则" /><published>2022-07-13T00:10:00+08:00</published><updated>2022-07-13T00:10:00+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2022/07/13/SOLID-interface-segregation-principle</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2022/07/13/SOLID-interface-segregation-principle.html"><![CDATA[<p>在面向对象编程中，<strong>SOLID</strong> 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。</p>

<p>SOLID 原则包含：</p>

<ul>
  <li><strong>S</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle.html">单一功能原则（single-responsibility principle）</a></li>
  <li><strong>O</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/17/SOLID-open-closed-principle.html">开闭原则（open-closed principle）</a></li>
  <li><strong>L</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/23/SOLID-liskov-substitution-principle.html">里氏替换原则（Liskov substitution principle）</a></li>
  <li><strong>I</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/13/SOLID-interface-segregation-principle.html">接口隔离原则（Interface segregation principle）</a></li>
  <li><strong>D</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/25/SOLID-dependency-inversion-principle.html">依赖反转原则（Dependency inversion principle）</a></li>
</ul>

<p>本文我们来介绍<em>接口隔离原则</em>。</p>

<h2 id="接口隔离原则">接口隔离原则</h2>

<p>接口隔离原则（Interface segregation principle，ISP）认为“<strong>多个特定用户接口要好于一个宽泛用途的接口</strong>”。</p>

<p>它指明用户（client）不应被迫使用对其而言无用的方法或功能。<em>接口隔离原则</em>将庞大臃肿的接口拆分成为更小、更具体的接口，让用户仅需知道他们感兴趣的方法。这种缩小了的接口也被称为<strong>角色接口</strong>（role interfaces）。<em>接口隔离原则</em>的目的是将系统解耦，从而使其易于重构、更改和重新部署。</p>

<h2 id="c-示例">C# 示例</h2>

<h3 id="糟糕的示范">糟糕的示范</h3>

<p>创建一个包含公司部门的 <em>ICompanyDepartment</em> 接口。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">interface</span> <span class="nc">ICompanyDepartment</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">AdminDepartment</span><span class="p">();</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">FinanaceDepartment</span><span class="p">();</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">HRDepartment</span><span class="p">();</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">ITDepartment</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>让我们首先为 A 公司实现 <em>ICompanyDepartment</em> 接口，文件名为 <em>ACompany.cs</em>：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ACompany</span> <span class="p">:</span> <span class="n">ICompanyDepartment</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">AdminDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">FinanaceDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">HRDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">ITDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>假如现在有一个从事制造业的 B 公司，他们没有 IT 部门，而多了一个生产部门，那么上面的实现就有问题了。</p>

<p>如果在接口 <em>ICompanyDepartment</em> 上添加一个 <code class="language-plaintext highlighter-rouge">ManufacturingDepartment</code> 生产部门，那我们就不得不在 <em>ACompany</em> 和 <em>BCompany</em> 两个类上都实现该签名。</p>

<p>也就是说，为了使用 <em>ICompanyDepartment</em> 接口，我们不得不在 <em>ACompany</em> 类上实现不必要的 <code class="language-plaintext highlighter-rouge">ManufacturingDepartment</code> 方法，在 <em>BCompany</em> 类上实现不必要的 <code class="language-plaintext highlighter-rouge">ITDepartment</code> 方法。这明显违反了<em>接口隔离原则</em>。</p>

<p>为了使其符合<em>接口隔离原则</em>，我们需要进行一些修改。</p>

<h3 id="正确的示范">正确的示范</h3>

<p>根据<em>接口隔离原则</em>，我们要将 <code class="language-plaintext highlighter-rouge">ITDepartment</code> 和 <code class="language-plaintext highlighter-rouge">ManufacturingDepartment</code> 方法从接口 <em>ICompanyDepartment</em> 拆分出来，形成两个独立的<strong>角色接口</strong>：</p>

<ol>
  <li>IACompanyUniqueFeature</li>
  <li>IBCompanyUniqueFeature</li>
</ol>

<p>这样我们就拥有了三个接口：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">interface</span> <span class="nc">ICompanyDepartment</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">AdminDepartment</span><span class="p">();</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">FinanaceDepartment</span><span class="p">();</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">HRDepartment</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">interface</span> <span class="nc">IACompanyUniqueFeature</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">ITDepartment</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">interface</span> <span class="nc">IBCompanyUniqueFeature</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">ManufacturingDepartment</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>类 <em>ACompany</em> 和 <em>BCompany</em> 的实现如下：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ACompany</span> <span class="p">:</span> <span class="n">ICompanyDepartment</span><span class="p">,</span> <span class="n">IACompanyUniqueFeature</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">AdminDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">FinanaceDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">HRDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">ITDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">class</span> <span class="nc">BCompany</span> <span class="p">:</span> <span class="n">ICompanyDepartment</span><span class="p">,</span> <span class="n">IBCompanyUniqueFeature</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">AdminDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">FinanaceDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">HRDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">ManufacturingDepartment</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">//DO SOMETHING</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这样，以上的接口设计便遵循了<em>接口隔离原则</em>。</p>

<h2 id="总结">总结</h2>

<p>本文我介绍了 SOLID 原则中的<strong>接口隔离原则</strong>（Interface segregation principle），并通过 C# 代码示例简明地诠释了它的含意和实现，希望对您有所帮助。</p>

<blockquote>
  <p>作者 ： 技术译民<br />
出品 ： <a href="https://ittranslator.cn/">技术译站</a></p>
</blockquote>

<p><br /></p>

<p>参考文档：</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/SOLID">https://en.wikipedia.org/wiki/SOLID</a></li>
  <li><a href="https://www.c-sharpcorner.com/blogs/interface-segregation-principle-in-c-sharp">https://www.c-sharpcorner.com/blogs/interface-segregation-principle-in-c-sharp</a></li>
</ul>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><summary type="html"><![CDATA[在面向对象编程中，SOLID 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。]]></summary></entry><entry><title type="html">C# 实例解释面向对象编程中的里氏替换原则</title><link href="https://ittranslator.cn/dotnet/csharp/2022/02/23/SOLID-liskov-substitution-principle.html" rel="alternate" type="text/html" title="C# 实例解释面向对象编程中的里氏替换原则" /><published>2022-02-23T00:10:00+08:00</published><updated>2022-02-23T00:10:00+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2022/02/23/SOLID-liskov-substitution-principle</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2022/02/23/SOLID-liskov-substitution-principle.html"><![CDATA[<p>在面向对象编程中，<strong>SOLID</strong> 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。</p>

<p>SOLID 原则包含：</p>

<ul>
  <li><strong>S</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle.html">单一功能原则（single-responsibility principle）</a></li>
  <li><strong>O</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/17/SOLID-open-closed-principle.html">开闭原则（open-closed principle）</a></li>
  <li><strong>L</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/23/SOLID-liskov-substitution-principle.html">里氏替换原则（Liskov substitution principle）</a></li>
  <li><strong>I</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/13/SOLID-interface-segregation-principle.html">接口隔离原则（Interface segregation principle）</a></li>
  <li><strong>D</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/25/SOLID-dependency-inversion-principle.html">依赖反转原则（Dependency inversion principle）</a></li>
</ul>

<p>本文我们来介绍<em>里氏替换原则</em>。</p>

<h2 id="里氏替换原则">里氏替换原则</h2>

<p>在面向对象的程序设计中，里氏替换原则（Liskov Substitution principle）是对子类型的特别定义。它由芭芭拉·利斯科夫（Barbara Liskov）在1987年的一次会议上，在名为“数据的抽象与层次”的演说中首次提出。</p>

<p><strong>里氏替换原则</strong>的内容<strong>可以描述为：</strong>“<strong>派生类（子类）对象可以在程序中代替其基类（超类）对象。</strong>”</p>

<p>也就是说，<strong>程序中的对象不管出现在什么地方，都应该可以使用其派生类（子类）的对象进行替换，而不影响程序运行的正确性。</strong></p>

<h2 id="c-示例">C# 示例</h2>

<p>我们看这样一个示例，假设一个企业有三种员工，一种是拿铁饭碗的永久雇员，一种是合同工，一种是临时工。我们设计几个类来表示这三种员工。</p>

<h3 id="糟糕的示范">糟糕的示范</h3>

<p>先定义一个 <em>Employee</em> 基类。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Employee</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 计算奖金</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;returns&gt;&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="k">abstract</span> <span class="kt">decimal</span> <span class="nf">CalculateBonus</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>再定义该基类的三个子类：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 永久雇员</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">PermanentEmployee</span> <span class="p">:</span> <span class="n">Employee</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">override</span> <span class="kt">decimal</span> <span class="nf">CalculateBonus</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="m">80000</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 合同工</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ContractEmployee</span> <span class="p">:</span> <span class="n">Employee</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">override</span> <span class="kt">decimal</span> <span class="nf">CalculateBonus</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="m">2000</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 临时工（临时工没有奖金）</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">TemporaryEmployee</span> <span class="p">:</span> <span class="n">Employee</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">override</span> <span class="kt">decimal</span> <span class="nf">CalculateBonus</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span> <span class="c1">//违反里氏替换原则</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>接下来在 <code class="language-plaintext highlighter-rouge">Main</code> 方法中调用它们。</p>

<p>先定义一个类型为基类 <em>Employee</em> 的变量 <code class="language-plaintext highlighter-rouge">e</code>，再分别使用其子类 <em>PermanentEmployee</em>、<em>ContractEmployee</em> 和 <em>TemporaryEmployee</em> 创建对象赋值给基类变量 <code class="language-plaintext highlighter-rouge">e</code>，然后调用 <code class="language-plaintext highlighter-rouge">e</code> 的 <code class="language-plaintext highlighter-rouge">CalculateBonus()</code> 方法。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">Employee</span> <span class="n">e</span><span class="p">;</span>

    <span class="n">e</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">PermanentEmployee</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"张三"</span> <span class="p">};</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> 的年终奖是 </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="nf">CalculateBonus</span><span class="p">()}</span><span class="s"> 元"</span><span class="p">);</span>

    <span class="n">e</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ContractEmployee</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"李四"</span> <span class="p">};</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> 的年终奖是 </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="nf">CalculateBonus</span><span class="p">()}</span><span class="s"> 元"</span><span class="p">);</span>

    <span class="n">e</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">TemporaryEmployee</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"王五"</span> <span class="p">};</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> 的年终奖是 </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="nf">CalculateBonus</span><span class="p">()}</span><span class="s"> 元"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>运行一下可以观察到（显而易见的），当使用 <em>PermanentEmployee</em> 和 <em>ContractEmployee</em> 类创建的对象替换基类型 <em>Employee</em> 的变量 <code class="language-plaintext highlighter-rouge">e</code> 时，调用 <code class="language-plaintext highlighter-rouge">CalculateBonus()</code> 方法可以正常运行，但是使用 <em>TemporaryEmployee</em> 类创建的对象替换变量 <code class="language-plaintext highlighter-rouge">e</code> 时，调用 <code class="language-plaintext highlighter-rouge">CalculateBonus()</code> 方法抛出了异常，导致程序无法正常运行。这就明显违反了<em>里氏替换原则</em>。</p>

<p>那么，应该如何改进一下呢？</p>

<h3 id="正确的示范">正确的示范</h3>

<p>我们看到，每种员工都有基本信息 <code class="language-plaintext highlighter-rouge">Name</code> 属性，但是由于临时工 <em>TemporaryEmployee</em> 没有奖金，所以不需要计算奖金。因此我们应该把计算奖金的方法 <code class="language-plaintext highlighter-rouge">CalculateBonus</code> 单独抽象出去，而不是让它们都继承于同一个基类，并将 <em>TemporaryEmployee</em> 子类中的 <code class="language-plaintext highlighter-rouge">CalculateBonus</code> 方法抛出一个异常。</p>

<p>改进后的代码：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">interface</span> <span class="nc">IEmployee</span>
<span class="p">{</span>
    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 计算年终奖</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;returns&gt;&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="kt">decimal</span> <span class="nf">CalculateBonus</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Employee</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 永久雇员</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">PermanentEmployee</span> <span class="p">:</span> <span class="n">Employee</span><span class="p">,</span> <span class="n">IEmployee</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">decimal</span> <span class="nf">CalculateBonus</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="m">80000</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 合同工</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ContractEmployee</span> <span class="p">:</span> <span class="n">Employee</span><span class="p">,</span> <span class="n">IEmployee</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">decimal</span> <span class="nf">CalculateBonus</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="m">2000</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 临时工</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">TemporaryEmployee</span> <span class="p">:</span> <span class="n">Employee</span>
<span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在 <code class="language-plaintext highlighter-rouge">Main</code> 方法中，将调用它们的测试代码改为：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">Employee</span> <span class="n">e</span><span class="p">;</span>
    <span class="n">IEmployee</span> <span class="n">ie</span><span class="p">;</span>

    <span class="kt">var</span> <span class="n">p</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">PermanentEmployee</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"张三"</span> <span class="p">};</span>
    <span class="n">e</span> <span class="p">=</span> <span class="n">p</span><span class="p">;</span>
    <span class="n">ie</span> <span class="p">=</span> <span class="n">p</span><span class="p">;</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> 的年终奖是 </span><span class="p">{</span><span class="n">ie</span><span class="p">.</span><span class="nf">CalculateBonus</span><span class="p">()}</span><span class="s"> 元"</span><span class="p">);</span>

    <span class="kt">var</span> <span class="n">c</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ContractEmployee</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"李四"</span> <span class="p">};</span>
    <span class="n">e</span> <span class="p">=</span> <span class="n">c</span><span class="p">;</span>
    <span class="n">ie</span> <span class="p">=</span> <span class="n">c</span><span class="p">;</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> 的年终奖是 </span><span class="p">{</span><span class="n">ie</span><span class="p">.</span><span class="nf">CalculateBonus</span><span class="p">()}</span><span class="s"> 元"</span><span class="p">);</span>

    <span class="n">e</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">TemporaryEmployee</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"王五"</span> <span class="p">};</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> 是临时工，无年终奖。"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>程序运行正常。</p>

<p>这样，这些子类的设计便遵循了<em>里氏替换原则</em>。</p>

<h2 id="总结">总结</h2>

<p>本文我介绍了 SOLID 原则中的<strong>里氏替换原则</strong>（Liskov substitution principle），并通过 C# 代码示例简明地诠释了它的含意和实现，希望对您有所帮助。</p>

<blockquote>
  <p>作者 ： 技术译民<br />
出品 ： <a href="https://ittranslator.cn/">技术译站</a></p>
</blockquote>

<p><br /></p>

<p>参考文档：</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/SOLID">https://en.wikipedia.org/wiki/SOLID</a></li>
  <li><a href="https://www.c-sharpcorner.com/blogs/liskov-substitution-principle-in-c-sharp">https://www.c-sharpcorner.com/blogs/liskov-substitution-principle-in-c-sharp</a></li>
</ul>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><summary type="html"><![CDATA[在面向对象编程中，SOLID 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。]]></summary></entry><entry><title type="html">C# 实例解释面向对象编程中的开闭原则</title><link href="https://ittranslator.cn/dotnet/csharp/2022/02/17/SOLID-open-closed-principle.html" rel="alternate" type="text/html" title="C# 实例解释面向对象编程中的开闭原则" /><published>2022-02-17T00:10:00+08:00</published><updated>2022-02-17T00:10:00+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2022/02/17/SOLID-open-closed-principle</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2022/02/17/SOLID-open-closed-principle.html"><![CDATA[<p>在面向对象编程中，<strong>SOLID</strong> 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。</p>

<p>SOLID 原则包含：</p>

<ul>
  <li><strong>S</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle.html">单一功能原则（single-responsibility principle）</a></li>
  <li><strong>O</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/17/SOLID-open-closed-principle.html">开闭原则（open-closed principle）</a></li>
  <li><strong>L</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/23/SOLID-liskov-substitution-principle.html">里氏替换原则（Liskov substitution principle）</a></li>
  <li><strong>I</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/13/SOLID-interface-segregation-principle.html">接口隔离原则（Interface segregation principle）</a></li>
  <li><strong>D</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/25/SOLID-dependency-inversion-principle.html">依赖反转原则（Dependency inversion principle）</a></li>
</ul>

<p>本文我们来介绍<em>开闭原则</em>。</p>

<h2 id="开闭原则">开闭原则</h2>

<p>在面向对象编程领域中，<strong>开闭原则</strong> (open-closed principle, OCP) <strong>规定</strong>“<strong>软件中的对象（类，模块，函数等等）应该对于扩展是开放的，而对于修改是封闭的</strong>”，这意味着<em>一个实体是允许在不改变它的源代码的前提下变更它的行为</em>。该特性在产品化的环境中是特别有价值的，在这种环境中，改变源代码需要代码审查，单元测试以及诸如此类的用以确保产品使用品质的过程。遵循开闭原则的代码在扩展时并不发生改变，因此无需这些过程。</p>

<p>具体到类，也就是说，在不修改类本身代码的情况下，应该是可以扩展它的行为的。</p>

<h2 id="c-示例">C# 示例</h2>

<p>让我们回顾一下上一篇文章<a href="https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle.html"><em>单一功能原则</em></a>中提到的 <em>AreaCalculator</em> 类，</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AreaCalculator</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="n">_shapes</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">AreaCalculator</span><span class="p">(</span><span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="n">shapes</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_shapes</span> <span class="p">=</span> <span class="n">shapes</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 计算所有形状的面积总和</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;returns&gt;&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="kt">double</span> <span class="nf">Sum</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">List</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;</span> <span class="n">areas</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;();</span>

        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">_shapes</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">item</span> <span class="k">is</span> <span class="n">Square</span> <span class="n">s</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">areas</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">SideLength</span><span class="p">,</span> <span class="m">2</span><span class="p">));</span>
            <span class="p">}</span>
            <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">item</span> <span class="k">is</span> <span class="n">Circle</span> <span class="n">c</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">areas</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">Math</span><span class="p">.</span><span class="n">PI</span> <span class="p">*</span> <span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">Radius</span><span class="p">,</span> <span class="m">2</span><span class="p">));</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">areas</span><span class="p">.</span><span class="nf">Sum</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>对于上面的计算方法，考虑这样一种场景，用户想要计算一些其它形状的面积总和，比如三角形、矩形、五边形等等…… 您将不得不反复编辑此类以添加更多的 <code class="language-plaintext highlighter-rouge">if/else</code> 块，这就违反了<em>开闭原则</em>。</p>

<h3 id="改进">改进</h3>

<p>一个更好的做法是，将计算每个形状的面积的逻辑从 <em>AreaCalculator</em> 类中移除，并将其添加到对应每个形状的类中。我们可以定义一个带有 <code class="language-plaintext highlighter-rouge">CalcArea</code> 方法的接口 <em>IShape</em>，然后让每个形状都实现这个接口。</p>

<p>接口 <em>IShape</em>：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">interface</span> <span class="nc">IShape</span>
<span class="p">{</span>
    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 计算面积</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;returns&gt;&lt;/returns&gt;</span>
    <span class="kt">double</span> <span class="nf">CalcArea</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>修改后的 <em>Square</em> 和 <em>Circle</em> 类：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 正方形</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">class</span> <span class="nc">Square</span> <span class="p">:</span> <span class="n">IShape</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="nf">Square</span><span class="p">(</span><span class="kt">double</span> <span class="n">length</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">SideLength</span> <span class="p">=</span> <span class="n">length</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="kt">double</span> <span class="n">SideLength</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>

    <span class="k">public</span> <span class="kt">double</span> <span class="nf">CalcArea</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="n">SideLength</span><span class="p">,</span> <span class="m">2</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 圆形</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">class</span> <span class="nc">Circle</span> <span class="p">:</span> <span class="n">IShape</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="nf">Circle</span><span class="p">(</span><span class="kt">double</span> <span class="n">radius</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Radius</span> <span class="p">=</span> <span class="n">radius</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">double</span> <span class="n">Radius</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>

    <span class="k">public</span> <span class="kt">double</span> <span class="nf">CalcArea</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">Math</span><span class="p">.</span><span class="n">PI</span> <span class="p">*</span> <span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="n">Radius</span><span class="p">,</span> <span class="m">2</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><em>AreaCalculator</em> 类也要对应做一些修改：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AreaCalculator</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">IShape</span><span class="p">&gt;</span> <span class="n">_shapes</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">AreaCalculator</span><span class="p">(</span><span class="n">List</span><span class="p">&lt;</span><span class="n">IShape</span><span class="p">&gt;</span> <span class="n">shapes</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_shapes</span> <span class="p">=</span> <span class="n">shapes</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 计算面积总和</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;returns&gt;&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="kt">double</span> <span class="nf">Sum</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">List</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;</span> <span class="n">areas</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;();</span>

        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">_shapes</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">areas</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">item</span><span class="p">.</span><span class="nf">CalcArea</span><span class="p">());</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">areas</span><span class="p">.</span><span class="nf">Sum</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>此时，如果我们有一个新的形状需要进行计算，我们可以直接添加一个实现了接口 <em>IShape</em> 的新类，而无需修改 <em>AreaCalculator</em> 类的代码，比如添加一个长方形类：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 长方形</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">class</span> <span class="nc">Rectangle</span> <span class="p">:</span> <span class="n">IShape</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="nf">Rectangle</span><span class="p">(</span><span class="kt">double</span> <span class="n">width</span><span class="p">,</span> <span class="kt">double</span> <span class="n">height</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Width</span> <span class="p">=</span> <span class="n">width</span><span class="p">;</span>
        <span class="n">Height</span> <span class="p">=</span> <span class="n">height</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">double</span> <span class="n">Width</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">double</span> <span class="n">Height</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>

    <span class="k">public</span> <span class="kt">double</span> <span class="nf">CalcArea</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">Width</span> <span class="p">*</span> <span class="n">Height</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>处理输出格式的 <em>SumCalculatorOutputter</em> 类同样无需修改：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SumCalculatorOutputter</span>
<span class="p">{</span>
    <span class="k">protected</span> <span class="n">AreaCalculator</span> <span class="n">_calculator</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">SumCalculatorOutputter</span><span class="p">(</span><span class="n">AreaCalculator</span> <span class="n">calculator</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_calculator</span> <span class="p">=</span> <span class="n">calculator</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="nf">String</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="s">$"Sum of the areas of provided shapes: </span><span class="p">{</span><span class="n">_calculator</span><span class="p">.</span><span class="nf">Sum</span><span class="p">()}</span><span class="s">"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="nf">JSON</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">data</span> <span class="p">=</span> <span class="k">new</span> <span class="p">{</span> <span class="n">Sum</span> <span class="p">=</span> <span class="n">_calculator</span><span class="p">.</span><span class="nf">Sum</span><span class="p">()</span> <span class="p">};</span>
        <span class="k">return</span> <span class="n">System</span><span class="p">.</span><span class="n">Text</span><span class="p">.</span><span class="n">Json</span><span class="p">.</span><span class="n">JsonSerializer</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>然后，我们修改 <code class="language-plaintext highlighter-rouge">Main</code> 方法中的代码来测试一下：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">shapes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">IShape</span><span class="p">&gt;</span> <span class="p">{</span>
            <span class="k">new</span> <span class="nf">Circle</span><span class="p">(</span><span class="m">2</span><span class="p">),</span>
            <span class="k">new</span> <span class="nf">Square</span><span class="p">(</span><span class="m">5</span><span class="p">),</span>
            <span class="k">new</span> <span class="nf">Rectangle</span><span class="p">(</span><span class="m">2</span><span class="p">,</span><span class="m">3</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="kt">var</span> <span class="n">areaCalculator</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AreaCalculator</span><span class="p">(</span><span class="n">shapes</span><span class="p">);</span>
    <span class="kt">var</span> <span class="n">outputer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SumCalculatorOutputter</span><span class="p">(</span><span class="n">areaCalculator</span><span class="p">);</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">outputer</span><span class="p">.</span><span class="nf">JSON</span><span class="p">());</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">outputer</span><span class="p">.</span><span class="nf">String</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>运行一下，输出结果为：</p>

<pre><code class="language-plain">{"Sum":43.56637061435917}
Sum of the areas of provided shapes: 43.56637061435917
</code></pre>

<p>现在，这些类的设计，既遵循了<em>单一功能原则</em>，又遵循了<em>开闭原则</em>。</p>

<h2 id="总结">总结</h2>

<p>本文我介绍了 SOLID 原则中的<strong>开闭原则</strong> (open-closed principle)，并通过 C# 代码示例简明地诠释了它的含意和实现，希望对您有所帮助。</p>

<blockquote>
  <p>作者 ： 技术译民<br />
出品 ： <a href="https://ittranslator.cn/">技术译站</a></p>
</blockquote>

<p><br /></p>

<p>参考文档：</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/SOLID">https://en.wikipedia.org/wiki/SOLID</a></li>
  <li><a href="https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design">https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design</a></li>
</ul>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><summary type="html"><![CDATA[在面向对象编程中，SOLID 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。]]></summary></entry><entry><title type="html">C# 实例解释面向对象编程中的单一功能原则</title><link href="https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle.html" rel="alternate" type="text/html" title="C# 实例解释面向对象编程中的单一功能原则" /><published>2022-02-07T00:10:00+08:00</published><updated>2022-02-07T00:10:00+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle.html"><![CDATA[<p>在面向对象编程中，<strong>SOLID</strong> 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。</p>

<p>SOLID 原则包含：</p>

<ul>
  <li><strong>S</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/07/SOLID-single-responsibility-principle.html">单一功能原则（single-responsibility principle）</a></li>
  <li><strong>O</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/17/SOLID-open-closed-principle.html">开闭原则（open-closed principle）</a></li>
  <li><strong>L</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/02/23/SOLID-liskov-substitution-principle.html">里氏替换原则（Liskov substitution principle）</a></li>
  <li><strong>I</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/13/SOLID-interface-segregation-principle.html">接口隔离原则（Interface segregation principle）</a></li>
  <li><strong>D</strong>：<a href="https://ittranslator.cn/dotnet/csharp/2022/07/25/SOLID-dependency-inversion-principle.html">依赖反转原则（Dependency inversion principle）</a></li>
</ul>

<p>本文我们来介绍<em>单一功能原则</em>。</p>

<h2 id="单一功能原则">单一功能原则</h2>

<p>在面向对象编程领域中，<strong>单一功能原则（Single responsibility principle）规定每个类都应该有且仅有一个单一的功能，并且该功能应该由这个类完全封装起来</strong>。所有它的（这个类的）服务都应该严密的和该功能平行（功能平行，意味着没有依赖）。</p>

<p>这个术语由罗伯特·C·马丁（Robert Cecil Martin）在他的《敏捷软件开发，原则，模式和实践》一书中的一篇名为『面向对象设计原则』的文章中提出。马丁表述该原则是基于《结构化分析和系统规格》一书中的内聚原则（Cohesion）之上的。</p>

<p>马丁把功能（职责）定义为：“改变的原因”，并总结出一个类或者模块应该有且只有一个改变的原因。一个具体的例子就是，想象有一个用于编辑和打印报表的模块。这样的一个模块存在两个改变的原因。第一，报表的内容可以改变（编辑）。第二，报表的格式可以改变（打印）。这两方面的改变会因为完全不同的起因而发生：一个是本质的修改，一个是表面的修改。单一功能原则认为这两方面的问题事实上是两个分离的功能，因此他们应该分离在不同的类或者模块里。把具有不同的改变原因的事物耦合在一起的设计是糟糕的。</p>

<p>保持一个类专注于单一功能点的一个重要的原因是，它可以使类更加的健壮。回顾上面的例子，如果有一个对于报表“编辑”流程的修改，那么将存在极大的危险性，因为假设这两个功能存在于同一个类中，修改报表的“编辑”流程会导致公共状态或者依赖关系的改变，从而可能使“打印”功能的代码无法正常运行。</p>

<h2 id="c-示例">C# 示例</h2>

<p>例如，考虑这样一个应用程序，它接受一组形状（圆形和正方形），并计算该列表中所有形状的面积之和。</p>

<p>首先，创建形状类，并通过构造函数设置所需的参数。</p>

<p>对于正方形，需要知道它的边长：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 正方形</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">class</span> <span class="nc">Square</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="nf">Square</span><span class="p">(</span><span class="kt">double</span> <span class="n">length</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">SideLength</span> <span class="p">=</span> <span class="n">length</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="kt">double</span> <span class="n">SideLength</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>对于圆形，需要它的半径：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// 圆形</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">class</span> <span class="nc">Circle</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="nf">Circle</span><span class="p">(</span><span class="kt">double</span> <span class="n">radius</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Radius</span> <span class="p">=</span> <span class="n">radius</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">double</span> <span class="n">Radius</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>接下来，创建 <em>AreaCalculator</em> 类，然后编写逻辑以计算所有提供的形状的面积。正方形的面积是用边长的平方计算的，圆的面积由 <code class="language-plaintext highlighter-rouge">π</code> 乘以半径的平方来计算的。</p>

<h3 id="糟糕的示范">糟糕的示范</h3>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AreaCalculator</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="n">_shapes</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">AreaCalculator</span><span class="p">(</span><span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="n">shapes</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_shapes</span> <span class="p">=</span> <span class="n">shapes</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 计算所有形状的面积总和</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;returns&gt;&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="kt">double</span> <span class="nf">Sum</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">List</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;</span> <span class="n">areas</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;();</span>

        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">_shapes</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">item</span> <span class="k">is</span> <span class="n">Square</span> <span class="n">s</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">areas</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">SideLength</span><span class="p">,</span> <span class="m">2</span><span class="p">));</span>
            <span class="p">}</span>
            <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">item</span> <span class="k">is</span> <span class="n">Circle</span> <span class="n">c</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">areas</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">Math</span><span class="p">.</span><span class="n">PI</span> <span class="p">*</span> <span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">Radius</span><span class="p">,</span> <span class="m">2</span><span class="p">));</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">areas</span><span class="p">.</span><span class="nf">Sum</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="nf">Output</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="s">$"Sum of the areas of provided shapes: </span><span class="p">{</span><span class="nf">Sum</span><span class="p">()}</span><span class="s">"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>要使用 <em>AreaCalculator</em> 类，您需要实例化这个类，并传入一个形状列表，并显示其输出。</p>

<p>在此，我们传入一个三个形状的列表：一个半径为 2 的圆，一个边长为 5 的正方形，一个边长为 6 的正方形。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">shapes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="p">{</span>
            <span class="k">new</span> <span class="nf">Circle</span><span class="p">(</span><span class="m">2</span><span class="p">),</span>
            <span class="k">new</span> <span class="nf">Square</span><span class="p">(</span><span class="m">5</span><span class="p">),</span>
            <span class="k">new</span> <span class="nf">Square</span><span class="p">(</span><span class="m">6</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="kt">var</span> <span class="n">areas</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AreaCalculator</span><span class="p">(</span><span class="n">shapes</span><span class="p">);</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">areas</span><span class="p">.</span><span class="nf">Output</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>运行程序，您会看到如下的输出：</p>

<blockquote>
  <p>Sum of the areas of provided shapes: 73.56637061435917</p>
</blockquote>

<p>输出正常，但这并不符合<em>单一功能原则</em>。因为 <em>AreaCalculator</em> 类既计算了所有形状的面积之和，又处理了输出数据的格式。</p>

<p>考虑这样一个场景，假如想要输出转换为另一种格式呢，如 JSON。我们就需要去修改 <em>AreaCalculator</em> 类，这样本来是为了修改输出数据的格式，却可能会影响到计算的逻辑，这明显违反了<em>单一功能原则</em>。</p>

<h3 id="正确的示范">正确的示范</h3>

<p><em>AreaCalculator</em> 类应该只关心计算提供的形状的面积之和，不应该关心输出什么格式。</p>

<p>下面我们来做一些修改，删除 <em>AreaCalculator</em> 类中的 <code class="language-plaintext highlighter-rouge">Output</code> 方法：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AreaCalculator</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="n">_shapes</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">AreaCalculator</span><span class="p">(</span><span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="n">shapes</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_shapes</span> <span class="p">=</span> <span class="n">shapes</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 计算所有形状的面积总和</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;returns&gt;&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="kt">double</span> <span class="nf">Sum</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">List</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;</span> <span class="n">areas</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;();</span>

        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">_shapes</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">item</span> <span class="k">is</span> <span class="n">Square</span> <span class="n">s</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">areas</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">SideLength</span><span class="p">,</span> <span class="m">2</span><span class="p">));</span>
            <span class="p">}</span>
            <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">item</span> <span class="k">is</span> <span class="n">Circle</span> <span class="n">c</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">areas</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">Math</span><span class="p">.</span><span class="n">PI</span> <span class="p">*</span> <span class="n">Math</span><span class="p">.</span><span class="nf">Pow</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">Radius</span><span class="p">,</span> <span class="m">2</span><span class="p">));</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">areas</span><span class="p">.</span><span class="nf">Sum</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>并新增一个 <em>SumCalculatorOutputter</em> 类来专门处理输出格式的逻辑：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SumCalculatorOutputter</span>
<span class="p">{</span>
    <span class="k">protected</span> <span class="n">AreaCalculator</span> <span class="n">_calculator</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">SumCalculatorOutputter</span><span class="p">(</span><span class="n">AreaCalculator</span> <span class="n">calculator</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_calculator</span> <span class="p">=</span> <span class="n">calculator</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="nf">String</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="s">$"Sum of the areas of provided shapes: </span><span class="p">{</span><span class="n">_calculator</span><span class="p">.</span><span class="nf">Sum</span><span class="p">()}</span><span class="s">"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="nf">JSON</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">data</span> <span class="p">=</span> <span class="k">new</span> <span class="p">{</span> <span class="n">Sum</span> <span class="p">=</span> <span class="n">_calculator</span><span class="p">.</span><span class="nf">Sum</span><span class="p">()</span> <span class="p">};</span>
        <span class="k">return</span> <span class="n">System</span><span class="p">.</span><span class="n">Text</span><span class="p">.</span><span class="n">Json</span><span class="p">.</span><span class="n">JsonSerializer</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>此时我们再来修改一下 <code class="language-plaintext highlighter-rouge">Main</code> 中的调用：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">shapes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="p">{</span>
            <span class="k">new</span> <span class="nf">Circle</span><span class="p">(</span><span class="m">2</span><span class="p">),</span>
            <span class="k">new</span> <span class="nf">Square</span><span class="p">(</span><span class="m">5</span><span class="p">),</span>
            <span class="k">new</span> <span class="nf">Square</span><span class="p">(</span><span class="m">6</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="kt">var</span> <span class="n">areaCalculator</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AreaCalculator</span><span class="p">(</span><span class="n">shapes</span><span class="p">);</span>
    <span class="kt">var</span> <span class="n">outputer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SumCalculatorOutputter</span><span class="p">(</span><span class="n">areaCalculator</span><span class="p">);</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">outputer</span><span class="p">.</span><span class="nf">JSON</span><span class="p">());</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">outputer</span><span class="p">.</span><span class="nf">String</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>运行程序，输出结果如下：</p>

<pre><code class="language-plain">{"Sum":73.56637061435917}
Sum of the areas of provided shapes: 73.56637061435917
</code></pre>

<p>现在，<em>AreaCalculator</em> 类处理计算逻辑，<em>SumCalculatorOutputter</em> 类处理输出格式，它们各司其职，遵循了<em>单一功能原则</em>。</p>

<h2 id="总结">总结</h2>

<p>本文我介绍了 SOLID 原则中的<strong>单一功能原则</strong>（single-responsibility principle），并通过 C# 代码示例简明地诠释了它的含意和实现，希望对您有所帮助。</p>

<blockquote>
  <p>作者 ： 技术译民<br />
出品 ： <a href="https://ittranslator.cn/">技术译站</a></p>
</blockquote>

<p><br /></p>

<p>参考文档：</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/SOLID">https://en.wikipedia.org/wiki/SOLID</a></li>
  <li><a href="https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design">https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design</a></li>
</ul>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><summary type="html"><![CDATA[在面向对象编程中，SOLID 是五个设计原则的首字母缩写，旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集，在他2000年的论文《设计原则与设计模式》中首次提出。]]></summary></entry><entry><title type="html">Blazor 组件库开发指南</title><link href="https://ittranslator.cn/dotnet/csharp/2021/09/06/a-developers-guide-to-blazor-component-libraries.html" rel="alternate" type="text/html" title="Blazor 组件库开发指南" /><published>2021-09-06T00:10:10+08:00</published><updated>2021-09-06T00:10:10+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2021/09/06/a-developers-guide-to-blazor-component-libraries</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2021/09/06/a-developers-guide-to-blazor-component-libraries.html"><![CDATA[<blockquote>
  <p>翻译自 Waqas Anwar 2021年5月21日的文章 <a href="https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-component-libraries">《A Developer’s Guide To Blazor Component Libraries》</a> <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>
</blockquote>

<p><img src="/assets/images/202109/A-Developers-Guide-To-Blazor-Component-Libraries.jpg" alt="A-Developers-Guide-To-Blazor-Component-Libraries" /></p>

<!--Blazor is all about components. We create different types of components and reuse them throughout the project. Nobody wants to reinvent the wheel so it is always a good idea to create a library of reusable Blazor components that can not only be shared across multiple projects but can also be shared with others as a NuGet package. Blazor allows us to create such component libraries with the help of a new project template called Razor Class Library and in this post, I will show you how to create such a library and share not only components but also static contents such as images, stylesheets, etc.-->

<p>Blazor 的核心是组件，我们创建不同类型的组件并在整个项目中重用它们。没有人想重复造轮子，因此创建一个可重用的 Blazor 组件库始终是一个好主意，这些组件不仅可以在多个项目之间共享，还可以作为 NuGet 包与其他人共享。Blazor 允许我们基于一个名为 Razor 类库（Razor Class Library）的新模板创建这样的组件库，在本文中，我将向您演示如何创建这样一个库，不仅仅共享组件，还共享静态内容（比如图片、样式表等等）。</p>

<p><a href="https://github.com/ezzylearning/BlazorClassLibraryDemo">下载源码</a><sup id="fnref:download" role="doc-noteref"><a href="#fn:download" class="footnote" rel="footnote">2</a></sup></p>

<h2 id="创建一个-razor-组件库">创建一个 Razor 组件库</h2>

<!--Create a new Blazor Server App with the name BlazorClassLibraryDemo in Visual Studio 2019. Make sure you can build and run your project without any problem. If you are not sure how to create Blazor Server apps then I will recommend you to read my post A Beginner’s Guide To Blazor Server and WebAssembly Applications-->

<p>在 Visual Studio 2019 中创建一个新的名为 <strong>BlazorClassLibraryDemo</strong> 的 Blazor Server 应用程序，确保您可以正常构建和运行该项目。如果您不确定如何创建 Blazor Server 应用程序，那么我建议您阅读我的文章<a href="https://ittranslator.cn/dotnet/csharp/2021/07/05/a-beginners-guide-to-blazor-server-and-webassembly-applications.html">《Blazor Server 和 WebAssembly 应用程序入门指南》</a></p>

<!--To add a new components library in your Blazor project, right-click on the solution and choose **Add > New Project…** option. Choose **Razor Class Library** project template from the list of available project templates-->

<p>要在 Blazor 项目中添加一个新组件库，请右键点击解决方案并选择 <strong>添加 &gt; 新建项目…</strong> 选项。从项目模板列表中选择 <strong>Razor 类库（Razor Class Library）</strong> 项目模板。</p>

<p><img src="/assets/images/202109/Add-New-Razor-Class-Library-in-Blazor-Project.jpg" alt="Add-New-Razor-Class-Library-in-Blazor-Project" /></p>

<!--Give the project any suitable name, I have chosen the name **MyComponentsLibrary**.-->

<p>为项目提供任意合适的名称，我这里将其命名为 <strong>MyComponentsLibrary</strong>。</p>

<p><img src="/assets/images/202109/Provide-Name-of-Razor-Class-Library.jpg" alt="Provide-Name-of-Razor-Class-Library" /></p>

<!--You will be asked to select the Project template once again with some additional options shown in the following dialog. There is only one item to select in this dialog so make sure Razor Class Library is selected and click Create button.-->

<p>然后，会询问您选择一些其他设置项，直接点击 <strong>创建</strong> 按钮就好。</p>

<p><img src="/assets/images/202109/Choose-Razor-Class-Library-Template.png" alt="Choose-Razor-Class-Library-Template" /></p>

<!--By default, the template will create an example component called **Component1.razor** with some additional files. Before we start creating our first shared component, we need to delete **Component1.razor** and **ExampleJsInterop.cs** as well as everything in the **wwwroot** folder so that we have a clean base to start.-->

<p>默认情况下，模板会创建一个名为 <strong>Component1.razor</strong> 的示例组件和一些附加文件。在开始创建我们的第一个共享组件之前，我们需要删除 <strong>Component1.razor</strong>、<strong>ExampleJsInterop.cs</strong> 以及 <strong>wwwroot</strong> 文件夹中的所有内容，以便我们有一个纯净的开始。</p>

<h2 id="在-razor-类库中创建一个组件">在 Razor 类库中创建一个组件</h2>

<!--Let’s create our first reusable/shared components in the Razor class library project **MyComponentsLibrary**. Right-click on the class library project and choose **Add > New Item…** option. Select **Razor Component** template and give the component name **TableWidget.razor**.-->

<p>让我们在 Razor 类库项目 <strong>MyComponentsLibrary</strong> 中创建我们的第一个可重用的共享组件。右键点击类库项目并选择 <strong>添加 &gt; 新建项…</strong> 选项。 选择 <strong>Razor 组件</strong> 模板并指定组件名称 <strong>TableWidget.razor</strong>。</p>

<p><img src="/assets/images/202109/Create-New-Blazor-Component-in-Razor-Class-Library.png" alt="Create-New-Blazor-Component-in-Razor-Class-Library" /></p>

<!--You can also add a **TableWidget.razor.cs** code-behind file if you want to keep your C# code separate from the component view. The TableWidget component is a simple Templated component that can be used to generate HTML tables from any list of objects. If you want to learn more about Blazor Templated components then you can read my post 《A Developer’s Guide To Blazor Templated Components》.-->

<p>如果您希望将 C# 代码与组件视图分开，您还可以添加代码隐藏文件 <strong>TableWidget.razor.cs</strong>。该 TableWidget 组件是一个简单的模板化组件，可用于从任何对象列表生成 HTML 表格。如果您想了解有关 Blazor 模板化组件的更多知识，那么您可以阅读我的文章<a href="https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components.html">《Blazor 模板化组件开发指南》</a>。</p>

<!--Add the following markup in TableWidget.razor component view file.-->

<p>在 TableWidget.razor 组件视图文件中添加以下标记。</p>

<p><b>TableWidget.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@typeparam TItem
<span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"table table-striped table-bordered"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;thead</span> <span class="na">class=</span><span class="s">"thead-green"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;tr&gt;</span>
            @HeaderTemplate
        <span class="nt">&lt;/tr&gt;</span>
    <span class="nt">&lt;/thead&gt;</span>
    <span class="nt">&lt;tbody&gt;</span>
    @foreach (var item in Items)
    {
        <span class="nt">&lt;tr&gt;</span>
            @RowTemplate(item)
        <span class="nt">&lt;/tr&gt;</span>
    }
    <span class="nt">&lt;/tbody&gt;</span>
    <span class="nt">&lt;tfoot&gt;</span>
        <span class="nt">&lt;tr&gt;</span>
            @FooterTemplate
        <span class="nt">&lt;/tr&gt;</span>
    <span class="nt">&lt;/tfoot&gt;</span>
<span class="nt">&lt;/table&gt;</span>
</code></pre></div></div>

<!--Following is the code-behind file of our TableWidget component. The component has Header, Row, and Footer templates of RenderFragment type to generate the header, body, and the footer of the HTML table in the view.-->

<p>下面是 TableWidget 组件的代码隐藏文件。该组件具有 RenderFragment 类型的 Header、Row 和 Footer 模板，用于在视图中生成 HTML 表格的表头、数据行和页脚。</p>

<p><b>TableWidget.razor.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Components</span><span class="p">;</span>
 
<span class="k">namespace</span> <span class="nn">MyComponentsLibrary</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">TableWidget</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span>
    <span class="p">{</span>
        <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
        <span class="k">public</span> <span class="n">RenderFragment</span> <span class="n">HeaderTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
        <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
        <span class="k">public</span> <span class="n">RenderFragment</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span> <span class="n">RowTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
        <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
        <span class="k">public</span> <span class="n">RenderFragment</span> <span class="n">FooterTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
        <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
        <span class="k">public</span> <span class="n">IReadOnlyList</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span> <span class="n">Items</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!--Every reusable component we create in the Razor class library can also have its stylesheet to define the look and feel of the component. For example, if we want our TableWidget component to generate tables with the dark green header, we can define the styles of our component in the **TableWidget.razor.css** file.-->

<p>我们在 Razor 类库中创建的每个可重用组件还可以有对应的样式表来定义组件的外观。例如，如果我们希望我们的 TableWidget 组件生成带有深绿色表头的表格，我们可以在 <strong>TableWidget.razor.css</strong> 文件中定义组件的样式。</p>

<p><b>TableWidget.razor.css</b></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.thead-green</span> <span class="p">{</span>
    <span class="nl">background-color</span><span class="p">:</span> <span class="no">darkgreen</span><span class="p">;</span>
    <span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="使用-razor-类库中的-razor-组件">使用 Razor 类库中的 Razor 组件</h2>

<!--Now we have defined our TableWidget component in the class library project, it is now time to use this component in our Blazor project. Right-click on the **Dependencies** node in the solution explorer and choose **Add Project Reference…** option from the context menu. Select the **MyComponentsLibrary** project and click OK.-->

<p>现在我们已经在类库项目中定义了我们的 TableWidget 组件，是时候在我们的 Blazor 项目中使用这个组件了。右键单击解决方案资源管理器中的 <strong>依赖项（Dependencies）</strong> 节点，然后从上下文菜单中选择 <strong>添加项目引用…（Add Project Reference…）</strong> 选项。选中 <strong>MyComponentsLibrary</strong> 项目并点击确定。</p>

<p><img src="/assets/images/202109/Add-Razor-Class-Library-in-Blazor-Project.jpg" alt="Add-Razor-Class-Library-in-Blazor-Project" /></p>

<!--If you want to use the TableWidget component on multiple pages then it is recommended to add the reference of the library in **_Imports.razor** file.-->

<p>如果您想在多个页面上使用 TableWidget 组件，那么推荐您在 <strong>_Imports.razor</strong> 文件中添加该类库的引用。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">@using</span> <span class="n">MyComponentsLibrary</span>
</code></pre></div></div>

<!--Blazor project template generates a **FetchData.razor** page by default that displays weather forecast objects from a backend service. We can test our **TableWidget** component on this page. Open the **FetchData.razor** file and replace the HTML table with the **TableWidget** component as shown in the code snippet below.-->

<p>Blazor 项目模板默认生成一个 <strong>FetchData.razor</strong> 页面，显示来自后端服务的天气预报对象。我们可以在此页面上测试我们的 <strong>TableWidget</strong> 组件。打开 <strong>FetchData.razor</strong> 文件，并使用 <strong>TableWidget</strong> 组件替换其中的 HTML 表格，如下面的代码片段所示。</p>

<p><b>FetchData.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/fetchdata"
 
@using BlazorClassLibraryDemo.Data
@inject WeatherForecastService ForecastService
 
<span class="nt">&lt;h1&gt;</span>Weather forecast<span class="nt">&lt;/h1&gt;</span>
 
@if (forecasts == null)
{
    <span class="nt">&lt;p&gt;&lt;em&gt;</span>Loading...<span class="nt">&lt;/em&gt;&lt;/p&gt;</span>
}
else
{
    <span class="nt">&lt;TableWidget</span> <span class="na">Items=</span><span class="s">"forecasts"</span> <span class="na">Context=</span><span class="s">"forecast"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;HeaderTemplate&gt;</span>
            <span class="nt">&lt;th&gt;</span>Date<span class="nt">&lt;/th&gt;</span>
            <span class="nt">&lt;th&gt;</span>Temp. (C)<span class="nt">&lt;/th&gt;</span>
            <span class="nt">&lt;th&gt;</span>Temp. (F)<span class="nt">&lt;/th&gt;</span>
            <span class="nt">&lt;th&gt;</span>Summary<span class="nt">&lt;/th&gt;</span>
        <span class="nt">&lt;/HeaderTemplate&gt;</span>
        <span class="nt">&lt;RowTemplate&gt;</span>
            <span class="nt">&lt;td&gt;</span>@forecast.Date.ToShortDateString()<span class="nt">&lt;/td&gt;</span>
            <span class="nt">&lt;td&gt;</span>@forecast.TemperatureC<span class="nt">&lt;/td&gt;</span>
            <span class="nt">&lt;td&gt;</span>@forecast.TemperatureF<span class="nt">&lt;/td&gt;</span>
            <span class="nt">&lt;td&gt;</span>@forecast.Summary<span class="nt">&lt;/td&gt;</span>
        <span class="nt">&lt;/RowTemplate&gt;</span>
    <span class="nt">&lt;/TableWidget&gt;</span>
}
 
@code {
    private WeatherForecast[] forecasts;
 
    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}
</code></pre></div></div>

<!-- Run the project and you should see the weather forecast grid generated using our TableWidget component. You can now reuse the TableWidget component in multiple pages or projects and you will always see the same grid generated for you. -->

<p>运行该项目，您会看到使用我们的 TableWidget 组件生成的天气预报表格。现在，您可以在多个页面或项目中重用该 TableWidget 组件，并且将会看到始终为您生成相同的表格。</p>

<p><img src="/assets/images/202109/Blazor-Component-Rendered-from-Class-Library.jpg" alt="Blazor-Component-Rendered-from-Class-Library" /></p>

<!--Consuming Images from Razor Class Library-->

<h2 id="使用-razor-类库中的图片">使用 Razor 类库中的图片</h2>

<!--Razor class libraries can expose static assets such as images and these assets can be consumed by the Blazor apps that consume the library. Let’s add an image **blazor_logo.jpg** in the **wwwroot/images** folder of our **MyComponentsLibrary** project. To use this image inside a Blazor component, add a component with the name **BlazorLogo.razor** in the **MyComponentsLibrary** project.-->

<p>Razor 类库可以公开静态资源（比如图片），并且这些资源可以由使用该库的 Blazor 应用程序使用。让我们在 <strong>MyComponentsLibrary</strong> 项目的 <strong>wwwroot/images</strong> 文件夹中添加一个图片 <strong>blazor_logo.jpg</strong>。为了在 Blazor 组件中使用此图片，请在 <strong>MyComponentsLibrary</strong> 项目中添加一个名为 <strong>BlazorLogo.razor</strong> 的组件。</p>

<p><img src="/assets/images/202109/Add-an-Image-in-Razor-Class-Library.jpg" alt="Add-an-Image-in-Razor-Class-Library" /></p>

<!--Add the **blazor_logo.jpg** image inside the **BlazorLogo.razor** component using the simple **img** tag.-->

<p>使用简单的 <strong>img</strong> 标签将 <strong>blazor_logo.jpg</strong> 图片添加到 <strong>BlazorLogo.razor</strong> 组件中。</p>

<p><b>BlazorLogo.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"images/blazor_logo.jpg"</span> <span class="na">alt=</span><span class="s">"Blazor Logo"</span><span class="nt">/&gt;</span>
</code></pre></div></div>

<!--To use the *BlazorLogo.razor* components in the Blazor app, open the Index.razor page from the Blazor demo app we created above and directly use the BlazorLogo components as shown in the code snippet below.-->

<p>要在 Blazor 应用程序中使用此 <em>BlazorLogo.razor</em> 组件，请从我们上面创建的 Blazor 示例应用中打开 <em>Index.razor</em> 页面，然后直接使用 BlazorLogo 组件，如下面的代码片段所示。</p>

<p><b>Index.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h1&gt;</span>Hello, Blazor!<span class="nt">&lt;/h1&gt;</span>
 
<span class="nt">&lt;BlazorLogo&gt;&lt;/BlazorLogo&gt;</span>
</code></pre></div></div>

<!--Run the project and you will notice that the image is not rendered as you expected. This is because the relative path of the image **images/blazor_logo.jpg** is not accessible from outside the class library project.-->

<p>运行该项目，您会注意到图片并未如预期那样显示出来。这是因为无法从类库项目外访问图片的相对路径 <strong>images/blazor_logo.jpg</strong>。</p>

<p><img src="/assets/images/202109/Fail-to-Access-Static-Contents-from-Razor-Class-Library.jpg" alt="Fail-to-Access-Static-Contents-from-Razor-Class-Library" /></p>

<!--To fix the above problem, you need to use a special path syntax given below:-->

<p>要解决上述问题，您需要使用下面给出的特殊路径语法：</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_content/{Razor Class Library Name}/{Path to file}
</code></pre></div></div>

<!--In the above syntax, the **{Razor Class Library Name}** is the placeholder for the class library name e.g. MyComponentsLibrary. The **{Path to file}** is the path to file under **wwwroot** folder.-->

<p>在上面的语法中，<strong>{Razor Class Library Name}</strong> 是类库名称的占位符（例如 MyComponentsLibrary），<strong>{Path to file}</strong> 是 <strong>wwwroot</strong> 文件夹下的文件路径。</p>

<!--Let’s fix our image path using the special syntax describe above-->

<p>让我们使用上面描述的特殊语法来修复我们的图片路径。</p>

<p><b>BlazorLogo.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"_content/MyComponentsLibrary/images/blazor_logo.jpg"</span> <span class="na">alt=</span><span class="s">"Blazor Logo"</span><span class="nt">/&gt;</span>
</code></pre></div></div>

<!--Run the project again and this time you will notice that the image is rendered as expected.-->

<p>再次运行项目，这次您会注意到图片按预期显示了。</p>

<p><img src="/assets/images/202109/Correct-Way-to-Access-Static-Contents-from-Razor-Class-Library.jpg" alt="Correct-Way-to-Access-Static-Contents-from-Razor-Class-Library" /></p>

<!--We can also access the images from Razor class libraries directly using the above special syntax. For example, the code snippet below will display images from the Razor class library using the **BlazorLogo** component as well as using the **img** tag. Notice the same special syntax is used with **img** tag in the Blazor app to access the image available in **MyComponentsLibrary**-->

<p>我们还可以使用上述特殊语法直接访问 Razor 类库中的图片。例如，下面的代码片段将使用 <strong>BlazorLogo</strong> 组件以及 <strong>img</strong> 标签显示 Razor 类库中的图片。请注意，Blazor 应用中的 <strong>img</strong> 标签使用相同的特殊语法来访问 <strong>MyComponentsLibrary</strong> 中的图片。</p>

<p><b>Index.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h1&gt;</span>Hello, Blazor!<span class="nt">&lt;/h1&gt;</span>
 
<span class="nt">&lt;h4&gt;</span>Showing Image from a Component available inside Class Library<span class="nt">&lt;/h4&gt;</span>
 
<span class="nt">&lt;BlazorLogo&gt;&lt;/BlazorLogo&gt;</span>
 
<span class="nt">&lt;h4&gt;</span>Showing Image directly from Class Library<span class="nt">&lt;/h4&gt;</span>
 
<span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"_content/MyComponentsLibrary/images/blazor_logo.jpg"</span> <span class="na">alt=</span><span class="s">"Blazor Logo"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<!--Run the project once again and this time you should see the same image rendered twice using both approaches.-->

<p>再次运行该项目，这次您应该会看到相同的图片使用了两种不同的方式显示两次。</p>

<p><img src="/assets/images/202109/Different-Ways-to-Access-Static-Contents-from-Razor-Class-Library.jpg" alt="Different-Ways-to-Access-Static-Contents-from-Razor-Class-Library" /></p>

<h2 id="使用-razor-类库中样式表">使用 Razor 类库中样式表</h2>

<!--We can also add stylesheets in Razor class libraries and the styles defined in those stylesheets can be used by Blazor apps. Let’s add a stylesheet **components.css** inside **wwwroot/css** folder-->

<p>我们还可以在 Razor 类库中添加样式表，并且 Blazor 应用程序可以使用这些样式表中定义的样式。 让我们在 <strong>wwwroot/css</strong> 文件夹中添加一个样式表 <strong>components.css</strong>。</p>

<p><img src="/assets/images/202109/Add-Stylesheet-in-Razor-Class-Library.jpg" alt="Add-Stylesheet-in-Razor-Class-Library" /></p>

<!--For the demonstration purpose, let’s add some styles related to **img** tag-->

<p>出于演示目的，让我们添加一些与 <strong>img</strong> 标签相关的样式。</p>

<p><b>components.css</b></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">img</span>
<span class="p">{</span>
    <span class="nl">background-color</span><span class="p">:</span> <span class="no">lightgreen</span><span class="p">;</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="no">black</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<!--To include the **components.css** file in our Blazor app, we can use the same special syntax we saw above. Open the **_Host.cshtml** file available in our Blazor server app and include the **components.css** file inside the **head** tag using the following **link** tag.-->

<p>要在我们的 Blazor 应用程序中包含 <strong>components.css</strong> 文件，我们可以使用与上面看到的相同的特殊语法。 打开 Blazor Server 应用程序中的 <strong>_Host.cshtml</strong> 文件，并使用以下 <strong>link</strong> 标签将 <strong>components.css</strong> 文件包含在 <strong>head</strong> 标签内。</p>

<p><b>_Host.cshtml</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;head&gt;</span>
    <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span> <span class="nt">/&gt;</span>
    ...
     
    <span class="nt">&lt;link</span> <span class="na">href=</span><span class="s">"~/_content/MyComponentsLibrary/css/components.css"</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/head&gt;</span>
</code></pre></div></div>

<!--Run the project and you will notice that the styles related to **img** tag we defined in the **components.css** file are applied to all the images of the projects.-->

<p>运行项目，您会发现我们在 <strong>components.css</strong> 文件中定义的与 <strong>img</strong> 标签相关的样式被应用到了项目中的所有图片上。</p>

<p><img src="/assets/images/202109/Apply-CSS-Styles-from-Razor-Class-Library.jpg" alt="Apply-CSS-Styles-from-Razor-Class-Library" /></p>

<h2 id="总结">总结</h2>

<!--I have covered very basic examples of reusable components in this post but you can see the power of the Razor class library. Developers can create some cool and amazing libraries of reusable Blazor components that can not only add some advanced features to your project but also increase the development speed. Many component vendors already developed some open source and commercial libraries such as MatBlazor, Radzen, Syncfusion, etc.-->

<p>我在本文中介绍了可重用组件的非常基础的示例，但您可以借此领会到 Razor 类库的强大功能。开发者可以创建一些又酷又炫的可重用 Blazor 组件库，这些库不仅可以为您的项目添加一些高级功能，还可以提升开发速度。许多组件供应商已经开发了一些开源和商业库，例如 <a href="https://www.matblazor.com/">MatBlazor</a><sup id="fnref:Mat" role="doc-noteref"><a href="#fn:Mat" class="footnote" rel="footnote">3</a></sup>、<a href="https://blazor.radzen.com/">Radzen</a><sup id="fnref:Rad" role="doc-noteref"><a href="#fn:Rad" class="footnote" rel="footnote">4</a></sup>、<a href="https://www.syncfusion.com/blazor-components">Syncfusion</a><sup id="fnref:Syn" role="doc-noteref"><a href="#fn:Syn" class="footnote" rel="footnote">5</a></sup> 等。</p>

<p><br /></p>

<p>相关阅读：</p>

<ul>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/05/a-beginners-guide-to-blazor-server-and-webassembly-applications.html">Blazor Server 和 WebAssembly 应用程序入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/12/a-beginners-guide-to-blazor-components.html">Blazor 组件入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/19/a-developers-guide-to-blazor-data-binding.html">Blazor 数据绑定开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/26/a-developers-guide-to-blazor-event-handling.html">Blazor 事件处理开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/02/communication-between-blazor-components-using-eventcallback.html">Blazor 组件之间使用 EventCallback 进行通信</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/09/a-developers-guide-to-blazor-routing-and-navigation.html">Blazor 路由及导航开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components.html">Blazor 模板化组件开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps.html">Blazor Server 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/30/making-http-requests-in-blazor-webassembly-apps.html">Blazor WebAssembly 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/09/06/a-developers-guide-to-blazor-component-libraries.html">Blazor 组件库开发指南</a></li>
</ul>

<blockquote>
  <p>作者 ： Waqas Anwar<br />
翻译 ： <a href="https://ittranslator.cn/">技术译站</a><br />
链接 ： <a href="https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-webassembly-apps">英文原文</a></p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-component-libraries">https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-component-libraries</a> A Developer’s Guide To Blazor Component Libraries <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:download" role="doc-endnote">
      <p><a href="https://github.com/ezzylearning/BlazorClassLibraryDemo">https://github.com/ezzylearning/BlazorClassLibraryDemo</a> 下载源码 <a href="#fnref:download" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:Mat" role="doc-endnote">
      <p><a href="https://www.matblazor.com/">https://www.matblazor.com/</a> <a href="#fnref:Mat" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:Rad" role="doc-endnote">
      <p><a href="https://blazor.radzen.com/">https://blazor.radzen.com/</a> <a href="#fnref:Rad" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:Syn" role="doc-endnote">
      <p><a href="https://www.syncfusion.com/blazor-components">https://www.syncfusion.com/blazor-components</a> <a href="#fnref:Syn" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><category term="DotNet" /><category term="Blazor" /><summary type="html"><![CDATA[翻译自 Waqas Anwar 2021年5月21日的文章 《A Developer’s Guide To Blazor Component Libraries》 1 https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-component-libraries A Developer’s Guide To Blazor Component Libraries &#8617;]]></summary></entry><entry><title type="html">Blazor WebAssembly 应用程序中进行 HTTP 请求</title><link href="https://ittranslator.cn/dotnet/csharp/2021/08/30/making-http-requests-in-blazor-webassembly-apps.html" rel="alternate" type="text/html" title="Blazor WebAssembly 应用程序中进行 HTTP 请求" /><published>2021-08-30T00:10:10+08:00</published><updated>2021-08-30T00:10:10+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2021/08/30/making-http-requests-in-blazor-webassembly-apps</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2021/08/30/making-http-requests-in-blazor-webassembly-apps.html"><![CDATA[<blockquote>
  <p>翻译自 Waqas Anwar 2021年5月13日的文章 <a href="https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-webassembly-apps">《Making HTTP Requests in Blazor WebAssembly Apps》</a> <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>
</blockquote>

<p><img src="/assets/images/202108/Making-HTTP-Requests-in-Blazor-WebAssembly-Apps.jpg" alt="Making-HTTP-Requests-in-Blazor-WebAssembly-Apps" /></p>

<!--In my previous post Making HTTP Requests in Blazor Server Apps, I covered different techniques of making HTTP requests in Blazor Server apps where you have access to all .NET libraries and components. If you are creating a Blazor WebAssembly App, then your code is running on the client within the browser sandbox and your options are somehow limited. In this tutorial, I will show you how you can make HTTP requests from Blazor WebAssembly Apps.-->

<p>在我的前篇文章<a href="https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps.html">《Blazor Server 应用程序中进行 HTTP 请求》</a>中，我介绍了在 Blazor Server 应用程序中进行 HTTP 请求的相关技术，在 Blazor Server App 中您可以访问所有的 .NET 类库和组件。但如果您创建的是 Blazor WebAssembly 应用程序，那么您的代码将在客户端的浏览器沙箱中运行，您的选择在某种程度上会受到限制。在本教程中，我将向您展示如何在 Blazor WebAssembly 应用程序进行 HTTP 请求。</p>

<!-- Overview of HttpClient in Blazor WebAssembly Apps -->

<h2 id="blazor-webassembly-应用程序中的-httpclient-概述">Blazor WebAssembly 应用程序中的 HttpClient 概述</h2>

<!--Blazor WebAssembly apps call web APIs using a preconfigured HttpClient service. This preconfigured HttpClient is implemented using the use browser Fetch API  and has some limitations. HttpClient can also use Blazor JSON helpers or HttpRequestMessage object to make API calls. By default, the API call requests can only be made to the same server of origin but you can call third-party APIs available on other servers if they support Cross-origin resource sharing (CORS).-->

<p>Blazor WebAssembly 应用程序使用预置的 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.httpclient">HttpClient</a> 服务调用 Web API。这个预置的 HttpClient 是使用浏览器的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API">Fetch API</a><sup id="fnref:fetch" role="doc-noteref"><a href="#fn:fetch" class="footnote" rel="footnote">2</a></sup> 实现的，会有一些限制。HttpClient 还可以使用 Blazor JSON 帮助程序或 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.httprequestmessage">HttpRequestMessage</a> 对象进行 API 调用。默认情况下，您只能向同源服务器发送 API 调用请求，不过如果第三方 API 支持跨域资源共享(CORS)的话，您也可以调用其他服务器上的 API。</p>

<!--The System.Net.Http.Json namespace provides extension methods for HttpClient that perform automatic serialization and deserialization using System.Text.Json. These extension methods send requests to a Web API URI and process the response accordingly. The common methods include:-->

<p>命名空间 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.json">System.Net.Http.Json</a> 为使用 System.Text.Json 执行自动序列化和反序列化的 HttpClient 提供了扩展方法。这些扩展方法将请求发送到一个 Web API URI 并处理相应的响应。常用的方法有：</p>

<!--GetFromJsonAsync: Sends an HTTP GET request and parses the JSON response body to create an object.-->
<!--PostAsJsonAsync: Sends a POST request to the specified URI containing the value serialized as JSON in the request body.-->
<!--PutAsJsonAsync: Sends an HTTP PUT request, including JSON-encoded content.-->

<ul>
  <li><strong>GetFromJsonAsync</strong>：发送 HTTP GET 请求，并将 JSON 响应正文解析成一个对象。</li>
  <li><strong>PostAsJsonAsync</strong>：将 POST 请求发送到指定的 URI，并在请求正文中载有序列化为 JSON 的 <code class="language-plaintext highlighter-rouge">value</code>。</li>
  <li><strong>PutAsJsonAsync</strong>：发送 HTTP PUT 请求，其中包含 JSON 编码的内容。</li>
</ul>

<!--To understand how to use these methods along with HttpClient, we need to create two projects. The first project will be a Web API project that will expose a Web API for clients. The second project will be Blazor WebAssembly App that will make HTTP requests to a Web API created in the first project.-->

<p>要理解如何将这些方法与 HttpClient 一起使用，我们需要创建两个项目。第一个项目是一个 Web API 项目，它向客户端公开一个 Web API。第二个项目是 Blazor WebAssembly 应用程序，它向第一个项目中创建的 Web API 发送 HTTP 请求。</p>

<!-- Implementing an ASP.NET Core Web API -->

<h2 id="实现一个-aspnet-core-web-api">实现一个 ASP.NET Core Web API</h2>

<!--In this section, we will implement a Web API with Cross-origin resource sharing (CORS) support so that this API can be called by Blazor WebAssembly apps. Create a new Web API project **BlazorClientWebAPI** in Visual Studio 2019. We will create a simple API that will return the list of products so let’s first create a **Models** folder in the project and add the following **Product** class to it.-->

<p>在本节中，我们将实现一个支持跨域资源共享 (CORS) 的 Web API，以便 Blazor WebAssembly 应用程序可以调用此 API。在 Visual Studio 2019 中创建一个新的 Web API 项目 <strong>BlazorClientWebAPI</strong>。我们将创建一个简单的 API 来返回产品列表，所以首先要在项目中创建一个 <strong>Models</strong> 文件夹，并在其中添加如下的 <strong>Product</strong> 类。</p>

<p><b>Product.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Product</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">decimal</span> <span class="n">Price</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!--Next, create a **Controllers** folder and add the following **ProductsController** in it. The controller is simply returning some fake product data from the **GetProducts** method.-->

<p>接下来，创建一个 <strong>Controllers</strong> 文件夹并在其中添加下面的 <strong>ProductsController</strong>。该控制器简单地从 <strong>GetProducts</strong> 方法返回一些模拟的产品数据。</p>

<p><b>ProductsController.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"api/[controller]"</span><span class="p">)]</span>
<span class="p">[</span><span class="n">ApiController</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ProductsController</span> <span class="p">:</span> <span class="n">ControllerBase</span>
<span class="p">{</span>
    <span class="p">[</span><span class="n">HttpGet</span><span class="p">]</span>
    <span class="k">public</span> <span class="n">IActionResult</span> <span class="nf">GetProducts</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">products</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;()</span>
        <span class="p">{</span>
            <span class="k">new</span> <span class="nf">Product</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span>
                <span class="n">Name</span> <span class="p">=</span> <span class="s">"Wireless Mouse"</span><span class="p">,</span>
                <span class="n">Price</span> <span class="p">=</span> <span class="m">29.99</span><span class="n">m</span>
            <span class="p">},</span>
            <span class="k">new</span> <span class="nf">Product</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">2</span><span class="p">,</span>
                <span class="n">Name</span> <span class="p">=</span> <span class="s">"HP Headphone"</span><span class="p">,</span>
                <span class="n">Price</span> <span class="p">=</span> <span class="m">79.99</span><span class="n">m</span>
            <span class="p">},</span>
            <span class="k">new</span> <span class="nf">Product</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">3</span><span class="p">,</span>
                <span class="n">Name</span> <span class="p">=</span> <span class="s">"Sony Keyboard"</span><span class="p">,</span>
                <span class="n">Price</span> <span class="p">=</span> <span class="m">119.99</span><span class="n">m</span>
            <span class="p">}</span>
        <span class="p">};</span>
 
        <span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">products</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!--If you will run your project and try to access the API using the URI **api/products** in the browser, you should be able to see the product data returned in JSON format.-->

<p>现在如果您运行该项目，并尝试在浏览器中使用 URI <strong>api/products</strong> 访问该 API，您应该能看到以 JSON 格式返回的产品数据。</p>

<p><img src="/assets/images/202108/Products-API-Response-for-Blazor-WebAssembly-HttpClient.jpg" alt="Products-API-Response-for-Blazor-WebAssembly-HttpClient" /></p>

<h2 id="在-aspnet-core-web-api-中启用-cors">在 ASP.NET Core Web API 中启用 CORS</h2>

<!--By default, browser security doesn’t allow a web page to make requests to a different domain other than the one from where the web page is served. This restriction is called the same-origin policy. If we want Blazor WebAssembly Apps or other client apps to consume the above Web API then we have to enable cross-origin resource sharing (CORS). Open the **Startup.cs** file and call the **AddCors** method in the **ConfigureServices** method.-->

<p>默认情况下，浏览器安全性不允许一个网页向除提供该网页的域之外的其他域发送请求。这种约束称之为同源策略。如果我们希望 Blazor WebAssembly 应用程序或其他客户端应用程序使用上述 Web API，那么我们必须启用跨域资源共享 (CORS)。打开 <em>Startup.cs</em> 文件，并在 <strong>ConfigureServices</strong> 方法中调用 <strong>AddCors</strong> 方法。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddCors</span><span class="p">(</span><span class="n">policy</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="n">policy</span><span class="p">.</span><span class="nf">AddPolicy</span><span class="p">(</span><span class="s">"CorsPolicy"</span><span class="p">,</span> <span class="n">opt</span> <span class="p">=&gt;</span> <span class="n">opt</span>
            <span class="p">.</span><span class="nf">AllowAnyOrigin</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">AllowAnyHeader</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">AllowAnyMethod</span><span class="p">());</span>
    <span class="p">});</span>
 
    <span class="n">services</span><span class="p">.</span><span class="nf">AddControllers</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<!--Also add the following line in the Configure method of Startup.cs file-->

<p>同时，在 <em>Startup.cs</em> 文件的 <strong>Configure</strong> 方法中添加以下代码行。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">UseCors</span><span class="p">(</span><span class="s">"CorsPolicy"</span><span class="p">);</span>
</code></pre></div></div>

<!--For detailed information on CORS with ASP.NET Core apps, see Enable Cross-Origin Requests (CORS) in ASP.NET Core.-->

<p>有关使用 ASP.NET Core 应用程序的 CORS 的详细信息，请参阅 <a href="https://docs.microsoft.com/zh-cn/aspnet/core/security/cors">《Enable Cross-Origin Requests (CORS) in ASP.NET Core》</a><sup id="fnref:CORS" role="doc-noteref"><a href="#fn:CORS" class="footnote" rel="footnote">3</a></sup>。</p>

<h2 id="实现-blazor-webassembly-应用程序">实现 Blazor WebAssembly 应用程序</h2>

<!--Add a new Blazor WebAssembly App project **BlazorClientWebAPIsDemo** in the same solution in which you created the above Web API project.-->

<p>在创建上述 Web API 项目的同一解决方案中添加一个新的 Blazor WebAssembly 应用程序项目 <strong>BlazorClientWebAPIsDemo</strong>。</p>

<!--The first thing we need to make sure of is that we have the reference of **System.Net.Http.Json** in the project file. If it’s not available then you can add the reference.-->

<p>我们需要确保的第一件事是，在项目文件中有 <strong>System.Net.Http.Json</strong> 的引用。如果没有，那么您可以添加该引用。</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk.BlazorWebAssembly"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;PropertyGroup&gt;</span>
      <span class="nt">&lt;TargetFramework&gt;</span>net5.0<span class="nt">&lt;/TargetFramework&gt;</span>
   <span class="nt">&lt;/PropertyGroup&gt;</span>
   <span class="nt">&lt;ItemGroup&gt;</span>
      <span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.Components.WebAssembly"</span> <span class="na">Version=</span><span class="s">"5.0.1"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.Components.WebAssembly.DevServer"</span> <span class="na">Version=</span><span class="s">"5.0.1"</span> <span class="na">PrivateAssets=</span><span class="s">"all"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"System.Net.Http.Json"</span> <span class="na">Version=</span><span class="s">"5.0.0"</span> <span class="nt">/&gt;</span>
   <span class="nt">&lt;/ItemGroup&gt;</span>
<span class="nt">&lt;/Project&gt;</span>
</code></pre></div></div>

<!--Next, we need to configure the HttpClient service in **Program.cs** file. Make sure to provide the base address of the Web APIs you want to call from Blazor WebAssembly Apps-->

<p>接下来，我们需要在 <em>Program.cs</em> 文件中配置 HttpClient 服务。确保提供了要从 Blazor WebAssembly 应用程序调用的 Web API 的基地址。</p>

<p><b>Program.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="n">WebAssemblyHostBuilder</span><span class="p">.</span><span class="nf">CreateDefault</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
    <span class="n">builder</span><span class="p">.</span><span class="n">RootComponents</span><span class="p">.</span><span class="n">Add</span><span class="p">&lt;</span><span class="n">App</span><span class="p">&gt;(</span><span class="s">"#app"</span><span class="p">);</span>
 
    <span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddScoped</span><span class="p">(</span><span class="n">sp</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="n">HttpClient</span>
    <span class="p">{</span>
        <span class="n">BaseAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"http://localhost:5000/api/"</span><span class="p">)</span>
    <span class="p">});</span> 
 
    <span class="k">await</span> <span class="n">builder</span><span class="p">.</span><span class="nf">Build</span><span class="p">().</span><span class="nf">RunAsync</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<!--To consume the products API, let’s create a **Products.razor** component in the **Pages** folder. The view is very straightforward as it is simply iterating the list of products and displaying them using a simple HTML table.-->

<p>为了使用产品 API，我们在 <strong>Pages</strong> 文件夹中创建一个 <strong>Products.razor</strong> 组件。该视图非常简单，因为它只是迭代产品列表并使用简单的 HTML 表格来显示它们。</p>

<p><b>Products.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/products"
 
<span class="nt">&lt;h1&gt;</span>Products<span class="nt">&lt;/h1&gt;</span>
 
@if (products == null)
{
    <span class="nt">&lt;p&gt;&lt;em&gt;</span>Loading...<span class="nt">&lt;/em&gt;&lt;/p&gt;</span>
}
else
{
    <span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"table"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;thead&gt;</span>
            <span class="nt">&lt;tr&gt;</span>
                <span class="nt">&lt;th&gt;</span>Id<span class="nt">&lt;/th&gt;</span>
                <span class="nt">&lt;th&gt;</span>Name<span class="nt">&lt;/th&gt;</span>
                <span class="nt">&lt;th&gt;</span>Price<span class="nt">&lt;/th&gt;</span>
            <span class="nt">&lt;/tr&gt;</span>
        <span class="nt">&lt;/thead&gt;</span>
        <span class="nt">&lt;tbody&gt;</span>
            @foreach (var forecast in products)
            {
                <span class="nt">&lt;tr&gt;</span>
                    <span class="nt">&lt;td&gt;</span>@forecast.Id<span class="nt">&lt;/td&gt;</span>
                    <span class="nt">&lt;td&gt;</span>@forecast.Name<span class="nt">&lt;/td&gt;</span>
                    <span class="nt">&lt;td&gt;</span>@forecast.Price<span class="nt">&lt;/td&gt;</span>
                <span class="nt">&lt;/tr&gt;</span>
            }
        <span class="nt">&lt;/tbody&gt;</span>
    <span class="nt">&lt;/table&gt;</span>
}
</code></pre></div></div>

<!--Create a **Products.razor.cs** code-behind file and inject the configured **HttpClient** instance in the class as a private member. Finally, use the **GetFromJsonAsync** method to call the products API.-->

<p>创建一个代码隐藏文件 <strong>Products.razor.cs</strong>，并将配置的 <strong>HttpClient</strong> 实例作为私有成员注入到该类中。最后，使用 <strong>GetFromJsonAsync</strong> 方法调用产品 API。</p>

<p><b>Products.razor.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">Products</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;</span> <span class="n">products</span><span class="p">;</span>
 
    <span class="p">[</span><span class="n">Inject</span><span class="p">]</span>
    <span class="k">private</span> <span class="n">HttpClient</span> <span class="n">Http</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
    <span class="k">protected</span> <span class="k">override</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">OnInitializedAsync</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">products</span> <span class="p">=</span> <span class="k">await</span> <span class="n">Http</span><span class="p">.</span><span class="n">GetFromJsonAsync</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;&gt;(</span><span class="s">"products"</span><span class="p">);</span>
    <span class="p">}</span> 
<span class="p">}</span>
</code></pre></div></div>

<!--You also need to create a local copy of the **Product** class in the Blazor WebAssembly project to deserialized the results of the products API into a list of product objects.-->

<p>您还需要在 Blazor WebAssembly 项目中创建一个 <strong>Product</strong> 类的本地副本，以将产品 API 的结果反序列化为产品对象列表。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Product</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">decimal</span> <span class="n">Price</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!--Run the project and you will see the page with products loaded from a backend Web API.-->

<p>运行该项目，您将看到从后端 Web API 加载了产品的页面。</p>

<p><img src="/assets/images/202108/Makking-HTTP-Requests-to-Web-API-from-Blazor-WebAssembly-App.jpg" alt="Makking-HTTP-Requests-to-Web-API-from-Blazor-WebAssembly-App" /></p>

<p><br /></p>

<p>相关阅读：</p>

<ul>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/05/a-beginners-guide-to-blazor-server-and-webassembly-applications.html">Blazor Server 和 WebAssembly 应用程序入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/12/a-beginners-guide-to-blazor-components.html">Blazor 组件入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/19/a-developers-guide-to-blazor-data-binding.html">Blazor 数据绑定开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/26/a-developers-guide-to-blazor-event-handling.html">Blazor 事件处理开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/02/communication-between-blazor-components-using-eventcallback.html">Blazor 组件之间使用 EventCallback 进行通信</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/09/a-developers-guide-to-blazor-routing-and-navigation.html">Blazor 路由及导航开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components.html">Blazor 模板化组件开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps.html">Blazor Server 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/30/making-http-requests-in-blazor-webassembly-apps.html">Blazor WebAssembly 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/09/06/a-developers-guide-to-blazor-component-libraries.html">Blazor 组件库开发指南</a></li>
</ul>

<blockquote>
  <p>作者 ： Waqas Anwar<br />
翻译 ： <a href="https://ittranslator.cn/">技术译站</a><br />
链接 ： <a href="https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-webassembly-apps">英文原文</a></p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-webassembly-apps">https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-webassembly-apps</a> Making HTTP Requests in Blazor WebAssembly Apps <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:fetch" role="doc-endnote">
      <p><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API">https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API</a> Fetch API <a href="#fnref:fetch" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:CORS" role="doc-endnote">
      <p><a href="https://docs.microsoft.com/zh-cn/aspnet/core/security/cors">https://docs.microsoft.com/zh-cn/aspnet/core/security/cors</a> <a href="#fnref:CORS" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><category term="DotNet" /><category term="Blazor" /><summary type="html"><![CDATA[翻译自 Waqas Anwar 2021年5月13日的文章 《Making HTTP Requests in Blazor WebAssembly Apps》 1 https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-webassembly-apps Making HTTP Requests in Blazor WebAssembly Apps &#8617;]]></summary></entry><entry><title type="html">Blazor Server 应用程序中进行 HTTP 请求</title><link href="https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps.html" rel="alternate" type="text/html" title="Blazor Server 应用程序中进行 HTTP 请求" /><published>2021-08-23T00:10:10+08:00</published><updated>2021-08-23T00:10:10+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps.html"><![CDATA[<blockquote>
  <p>翻译自 Waqas Anwar 2021年5月4日的文章 <a href="https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-server-apps">《Making HTTP Requests in Blazor Server Apps》</a> <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>
</blockquote>

<p><img src="/assets/images/202108/Making-HTTP-Requests-in-Blazor-Server-Apps.jpg" alt="Making-HTTP-Requests-in-Blazor-Server-Apps" /></p>

<!-- Blazor server apps use the standard ASP.NET Core application and they execute .NET code on the server. We can access any .NET library or server-side feature in these apps in the same way as we use in ASP.NET Core web applications. One such feature is to use HTTP Client instances to make HTTP requests to third-party Web APIs. In this tutorial, I will show you different ways to create HTTP Client instances. I will also show you how to consume a third-party API to fetch and display data in Blazor Server Apps. -->

<p>Blazor Server 应用使用标准的 ASP.NET Core 应用程序，在服务端执行 .NET 代码。在 Blazor Server 应用程序中，我们可以像在 ASP.NET Core Web 应用程序中那样，使用相同的方式访问任意 .NET 库或服务端功能。这其中的一项功能是，使用 HTTP Client 实例向第三方 Web API 发送 HTTP 请求。在本教程中，我将向您展示创建 HTTP Client 实例的不同方法。另外，我还会向您展示如何在 Blazor Server 应用程序中使用第三方 API 来获取和显示数据。</p>

<p><a href="https://github.com/ezzylearning/BlazorServerWebAPIsDemo">下载源码</a><sup id="fnref:download" role="doc-noteref"><a href="#fn:download" class="footnote" rel="footnote">2</a></sup></p>

<h2 id="第三方-web-api-概览">第三方 Web API 概览</h2>

<!-- We will develop a Blazor server app that will allow the user to input a country code and a year on a Blazor page component and then we will call a third party API to fetch the list of public holidays of that particular country in that particular year. The third-party API we will consume is Nager.Date which is a worldwide public holidays API. -->

<p>我们将开发一个 Blazor Server 应用程序，该应用允许用户在 Blazor 页面组件上输入国家代码和年份，然后我们将调用第三方 API 以获取指定国家和年份的公共假期列表。我们使用的第三方 API 是 <a href="https://date.nager.at/">Nager.Date</a><sup id="fnref:nager" role="doc-noteref"><a href="#fn:nager" class="footnote" rel="footnote">3</a></sup>，它是一个全球公共假期 API。</p>

<p style="text-align:center">
<a href="https://date.nager.at/" target="_blank"><img style="background-color:#fff;" loading="lazy" src="/assets/images/202108/Nager.Date-World-Wide-Public-Holidays-API.png" alt="Nager.Date-World-Wide-Public-Holidays-API" /></a>
</p>

<!-- It is a very simple API and you can easily test this API in Postman by entering the following URL. -->

<p>这是一个非常简单的 API，您可以轻松地在 Postman 中输入以下 URL 测试此 API。</p>

<p><a href="https://date.nager.at/api/v2/PublicHolidays/2021/CN">https://date.nager.at/api/v2/PublicHolidays/2021/CN</a></p>

<!-- The response of this API is the list of public holidays in JSON format as shown below: -->

<p>该 API 的响应是 JSON 格式的公共假期列表，如下所示：</p>

<p><img src="/assets/images/202108/World-Wide-Public-Holidays-API-in-Postman.png" alt="World-Wide-Public-Holidays-API-in-Postman" /></p>

<h2 id="从-blazor-sever-应用程序开始">从 Blazor Sever 应用程序开始</h2>

<!-- Create a Blazor Server App in Visual Studio 2019 and create a folder named Models. Add the following two model classes in the Models folder to map Holidays API request and response shown above. -->

<p>在 Visual Studio 2019 中创建一个 Blazor Server 应用程序，并新建一个名为 <strong>Models</strong> 的文件夹。在 <strong>Models</strong> 文件夹中添加以下两个模型类，以映射上述 Holidays API 的请求和响应。</p>

<p><b>HolidayRequestModel.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">HolidayRequestModel</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">CountryCode</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Year</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><b>HolidayResponseModel.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">HolidayResponseModel</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">LocalName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">Date</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">CountryCode</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="n">Global</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- Next, create a new Razor component HolidaysExplorer.razor in the Pages folder along with its code-behind file HolidaysExplorer.razor.cs. If you want to learn more about Razor components and code-behind files then you can read my post A Beginner’s Guide to Blazor Components. -->

<p>接下来，在 <strong>Pages</strong> 文件夹中创建一个新的 Razor 组件 <strong>HolidaysExplorer.razor</strong> 及其代码隐藏文件 <strong>HolidaysExplorer.razor.cs</strong>。如果您想了解有关 Razor 组件和代码隐藏文件的更多知识，可以阅读我的文章<a href="https://ittranslator.cn/dotnet/csharp/2021/07/12/a-beginners-guide-to-blazor-components.html">《Blazor 组件入门指南》</a>。</p>

<p><b>HolidaysExplorer.razor.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">HolidaysExplorer</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">HolidayRequestModel</span> <span class="n">HolidaysModel</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HolidayRequestModel</span><span class="p">();</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;</span> <span class="n">Holidays</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;();</span>
 
    <span class="p">[</span><span class="n">Inject</span><span class="p">]</span>
    <span class="k">protected</span> <span class="n">IHolidaysApiService</span> <span class="n">HolidaysApiService</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
    <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">HandleValidSubmit</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">Holidays</span> <span class="p">=</span> <span class="k">await</span> <span class="n">HolidaysApiService</span><span class="p">.</span><span class="nf">GetHolidays</span><span class="p">(</span><span class="n">HolidaysModel</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- The **HolidaysModel** field is an instance of the **HolidayRequestModel** class that will help us in creating a simple form to ask the user the Country Code and the Year. The following code snippet shows the Blazor form created using the **HolidaysModel** object. The **HandleValidSubmit** method is configured with the Blazor Form’s **OnValidSubmit** event and it will be called when the user will submit the form. -->

<p><strong>HolidaysModel</strong> 字段是 <strong>HolidayRequestModel</strong> 类的一个实例，它将帮助我们创建一个简单的表单来向用户询问国家代码和年份。下面的代码片段显示了使用 <strong>HolidaysModel</strong> 对象创建的 Blazor 表单，其中 <strong>HandleValidSubmit</strong> 方法是使用 Blazor Form 的 <strong>OnValidSubmit</strong> 事件配置的，用户提交表单时该方法将被调用。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;EditForm</span> <span class="na">Model=</span><span class="s">"@HolidaysModel"</span> <span class="na">OnValidSubmit=</span><span class="s">"@HandleValidSubmit"</span> <span class="na">class=</span><span class="s">"form-inline"</span><span class="nt">&gt;</span>
     
   <span class="nt">&lt;label</span> <span class="na">class=</span><span class="s">"ml-2"</span><span class="nt">&gt;</span>Country Code:<span class="nt">&lt;/label&gt;</span>
   <span class="nt">&lt;InputText</span> <span class="na">id=</span><span class="s">"CountryCode"</span> <span class="err">@</span><span class="na">bind-Value=</span><span class="s">"HolidaysModel.CountryCode"</span> <span class="na">class=</span><span class="s">"form-control"</span> <span class="nt">/&gt;</span>
     
   <span class="nt">&lt;label</span> <span class="na">class=</span><span class="s">"ml-2"</span><span class="nt">&gt;</span>Year:<span class="nt">&lt;/label&gt;</span>
   <span class="nt">&lt;InputNumber</span> <span class="na">id=</span><span class="s">"Year"</span> <span class="err">@</span><span class="na">bind-Value=</span><span class="s">"HolidaysModel.Year"</span> <span class="na">class=</span><span class="s">"form-control"</span> <span class="nt">/&gt;</span>
     
   <span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary ml-2"</span> <span class="na">type=</span><span class="s">"submit"</span><span class="nt">&gt;</span>Submit<span class="nt">&lt;/button&gt;</span>
     
<span class="nt">&lt;/EditForm&gt;</span>
</code></pre></div></div>

<!-- The **Holidays** list will be used to display the holidays returned from the third-party API. We need to generate a simple bootstrap table by iterating the holidays with a simple `@foreach` loop. -->

<p><strong>Holidays</strong> 列表用来显示从第三方 API 返回的假期。我们需要使用一个 <code class="language-plaintext highlighter-rouge">@foreach</code> 循环迭代返回的假期来生成一个简单的 bootstrap 表格。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@if (Holidays.Count &gt; 0)
{
    <span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"table table-bordered table-striped table-sm"</span><span class="nt">&gt;</span>
       <span class="nt">&lt;thead&gt;</span>
          <span class="nt">&lt;tr&gt;</span>
             <span class="nt">&lt;th&gt;</span>Date<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th&gt;</span>Name<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th&gt;</span>Local Name<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th&gt;</span>Country Code<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th&gt;</span>Global<span class="nt">&lt;/th&gt;</span>
          <span class="nt">&lt;/tr&gt;</span>
       <span class="nt">&lt;/thead&gt;</span>
       <span class="nt">&lt;tbody&gt;</span>
          @foreach (var item in Holidays)
          {
              <span class="nt">&lt;tr&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.Date.Value.ToShortDateString()<span class="nt">&lt;/td&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.Name<span class="nt">&lt;/td&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.LocalName<span class="nt">&lt;/td&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.CountryCode<span class="nt">&lt;/td&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.Global<span class="nt">&lt;/td&gt;</span>
              <span class="nt">&lt;/tr&gt;</span>
          }
       <span class="nt">&lt;/tbody&gt;</span>
    <span class="nt">&lt;/table&gt;</span>
}
</code></pre></div></div>

<!-- The complete code of HolidaysExplorer.razor view is shown below. -->

<p><strong>HolidaysExplorer.razor</strong> 视图的完整代码如下：</p>

<p><b>HolidaysExplorer.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/"
<span class="nt">&lt;h3&gt;</span>Holidays Explorer<span class="nt">&lt;/h3&gt;</span>
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
 
<span class="nt">&lt;EditForm</span> <span class="na">Model=</span><span class="s">"@HolidaysModel"</span> <span class="na">OnValidSubmit=</span><span class="s">"@HandleValidSubmit"</span> <span class="na">class=</span><span class="s">"form-inline"</span><span class="nt">&gt;</span>
 
   <span class="nt">&lt;label</span> <span class="na">class=</span><span class="s">"ml-2"</span><span class="nt">&gt;</span>Country Code:<span class="nt">&lt;/label&gt;</span>
   <span class="nt">&lt;InputText</span> <span class="na">id=</span><span class="s">"CountryCode"</span> <span class="err">@</span><span class="na">bind-Value=</span><span class="s">"HolidaysModel.CountryCode"</span> <span class="na">class=</span><span class="s">"form-control"</span> <span class="nt">/&gt;</span>
 
   <span class="nt">&lt;label</span> <span class="na">class=</span><span class="s">"ml-2"</span><span class="nt">&gt;</span>Year:<span class="nt">&lt;/label&gt;</span>
   <span class="nt">&lt;InputNumber</span> <span class="na">id=</span><span class="s">"Year"</span> <span class="err">@</span><span class="na">bind-Value=</span><span class="s">"HolidaysModel.Year"</span> <span class="na">class=</span><span class="s">"form-control"</span> <span class="nt">/&gt;</span>
 
   <span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary ml-2"</span> <span class="na">type=</span><span class="s">"submit"</span><span class="nt">&gt;</span>Submit<span class="nt">&lt;/button&gt;</span>
 
<span class="nt">&lt;/EditForm&gt;</span>
 
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
@if (Holidays.Count &gt; 0)
{
    <span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"table table-bordered table-striped table-sm"</span><span class="nt">&gt;</span>
       <span class="nt">&lt;thead&gt;</span>
          <span class="nt">&lt;tr&gt;</span>
             <span class="nt">&lt;th&gt;</span>Date<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th&gt;</span>Name<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th&gt;</span>Local Name<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th&gt;</span>Country Code<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th&gt;</span>Global<span class="nt">&lt;/th&gt;</span>
          <span class="nt">&lt;/tr&gt;</span>
       <span class="nt">&lt;/thead&gt;</span>
       <span class="nt">&lt;tbody&gt;</span>
          @foreach (var item in Holidays)
          {
              <span class="nt">&lt;tr&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.Date.Value.ToShortDateString()<span class="nt">&lt;/td&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.Name<span class="nt">&lt;/td&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.LocalName<span class="nt">&lt;/td&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.CountryCode<span class="nt">&lt;/td&gt;</span>
                 <span class="nt">&lt;td&gt;</span>@item.Global<span class="nt">&lt;/td&gt;</span>
              <span class="nt">&lt;/tr&gt;</span>
          }
       <span class="nt">&lt;/tbody&gt;</span>
    <span class="nt">&lt;/table&gt;</span>
}
</code></pre></div></div>

<!-- If you will run the app at this point, you will see a simple HTML form without any holidays. This is because the method **HandleValidSubmit** is empty and we are not calling any API to fetch holiday data yet. -->

<p>此时如果您运行该应用程序，您将看到一个不显示任何假期的简单 HTML 表单。这是因为方法 <strong>HandleValidSubmit</strong> 是空的，我们还未调用任何 API 来获取假期数据。</p>

<p><img src="/assets/images/202108/Simple-Form-using-Blazor-Form-Component.jpg" alt="Simple-Form-using-Blazor-Form-Component" /></p>

<!-- Creating HttpClient using IHttpClientFactory in Blazor Server Apps -->

<h2 id="在-blazor-server-应用程序中使用-ihttpclientfactory-创建-httpclient">在 Blazor Server 应用程序中使用 IHttpClientFactory 创建 HttpClient</h2>

<!-- There are different ways to consume third-party APIs in Blazor server apps using HttpClient so let’s start with a basic example in which we will create HttpClient object using IHttpClientFactory. -->

<p>在 Blazor Server 应用程序中使用 HttpClient 请求第三方 API 有多种不同的方式，让我们从一个基础的示例开始，在该示例中我们使用 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.ihttpclientfactory"><code class="language-plaintext highlighter-rouge">IHttpClientFactory</code></a> 创建 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.httpclient"><code class="language-plaintext highlighter-rouge">HttpClient</code></a> 对象。</p>

<!-- Create a **Services** folder in the project and create the following **IHolidaysApiService** interface.  The interface has just one method **GetHolidays** that takes **HolidayRequestModel** as a parameter and returns the list of **HolidayResponseModel** objects. -->

<p>在项目中创建一个 <strong>Services</strong> 文件夹，并创建如下的 <strong>IHolidaysApiService</strong> 接口。该接口只有一个方法 <strong>GetHolidays</strong>，它以 <strong>HolidayRequestModel</strong> 作为参数并返回 <strong>HolidayResponseModel</strong> 对象的列表。</p>

<p><b>IHolidaysApiService.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IHolidaysApiService</span>
<span class="p">{</span>
    <span class="n">Task</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;</span> <span class="nf">GetHolidays</span><span class="p">(</span><span class="n">HolidayRequestModel</span> <span class="n">holidaysRequest</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- Next, create a class **HolidaysApiService** in the **Services** folder and implement the above interface. -->

<p>接下来，在 <strong>Services</strong> 文件夹中创建一个 <strong>HolidaysApiService</strong> 类，实现上面的接口。</p>

<p><b>HolidaysApiService.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">HolidaysApiService</span> <span class="p">:</span> <span class="n">IHolidaysApiService</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IHttpClientFactory</span> <span class="n">_clientFactory</span><span class="p">;</span>
 
    <span class="k">public</span> <span class="nf">HolidaysApiService</span><span class="p">(</span><span class="n">IHttpClientFactory</span> <span class="n">clientFactory</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_clientFactory</span> <span class="p">=</span> <span class="n">clientFactory</span><span class="p">;</span>
    <span class="p">}</span>
 
    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;</span> <span class="nf">GetHolidays</span><span class="p">(</span><span class="n">HolidayRequestModel</span> <span class="n">holidaysRequest</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;();</span>
 
        <span class="kt">var</span> <span class="n">url</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"https://date.nager.at/api/v2/PublicHolidays/{0}/{1}"</span><span class="p">,</span> 
            <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">Year</span><span class="p">,</span> <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">CountryCode</span><span class="p">);</span>
 
        <span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Get</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
        <span class="n">request</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"Accept"</span><span class="p">,</span> <span class="s">"application/vnd.github.v3+json"</span><span class="p">);</span>
 
        <span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="n">_clientFactory</span><span class="p">.</span><span class="nf">CreateClient</span><span class="p">();</span>
 
        <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
 
        <span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">stringResponse</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
 
            <span class="n">result</span> <span class="p">=</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;(</span><span class="n">stringResponse</span><span class="p">,</span>
                <span class="k">new</span> <span class="nf">JsonSerializerOptions</span><span class="p">()</span> <span class="p">{</span> <span class="n">PropertyNamingPolicy</span> <span class="p">=</span> <span class="n">JsonNamingPolicy</span><span class="p">.</span><span class="n">CamelCase</span> <span class="p">});</span>
        <span class="p">}</span>
        <span class="k">else</span>
        <span class="p">{</span>
            <span class="n">result</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;().</span><span class="nf">ToList</span><span class="p">();</span>
        <span class="p">}</span>
 
        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- In the GetHolidays method above, we first created a URL for a third party API and appended the country code and year parameters in the URL. -->

<p>在上面的 GetHolidays 方法中，我们首先为第三方 API 创建了一个 URL，并将国家代码和年份参数添加到 URL 中。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">url</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"https://date.nager.at/api/v2/PublicHolidays/{0}/{1}"</span><span class="p">,</span> <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">Year</span><span class="p">,</span> <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">CountryCode</span><span class="p">);</span>
</code></pre></div></div>

<!-- Next, we created HttpRequestMessage object and configured it to send HTTP GET request to third part API URL. -->

<p>接下来，我们创建了 HttpRequestMessage 对象并配置它以向第三方 API URL 发送 HTTP GET 请求。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Get</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
<span class="n">request</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"Accept"</span><span class="p">,</span> <span class="s">"application/vnd.github.v3+json"</span><span class="p">);</span>
</code></pre></div></div>

<!-- An IHttpClientFactory can be requested using dependency injection (DI) and this is why we are injecting it in the constructor of the above class. The following line is using IHttpClientFactory to create an HttpClient instance. -->

<p>可以使用<a href="https://www.ezzylearning.net/tutorial/a-step-by-step-guide-to-asp-net-core-dependency-injection">依赖注入 (DI)</a> 请求一个 IHttpClientFactory，这正是我们将其注入到前面类的构造函数的原因。下面这行代码使用 IHttpClientFactory 创建了一个 HttpClient 实例。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="n">_clientFactory</span><span class="p">.</span><span class="nf">CreateClient</span><span class="p">();</span>
</code></pre></div></div>

<!-- Once we have the HttpClient object available, we are simply calling its **SendAsync** method to send an HTTP GET request to -->

<p>有了 HttpClient 对象之后，我们简单地调用它的 <strong>SendAsync</strong> 方法来发送一个 HTTP GET 请求。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
</code></pre></div></div>

<!-- If the API call is successful, we are reading the response as a string using the following line. -->

<p>如果 API 调用成功，我们使用下面这行代码将其响应读取为字符串。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">stringResponse</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
</code></pre></div></div>

<!-- Finally, we are deserializing the response using the **Deserialize** method of **JsonSerializer** class. -->

<p>最后，我们使用 <strong>JsonSerializer</strong> 类的 <strong>Deserialize</strong> 方法反序列化该响应。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">result</span> <span class="p">=</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;(</span><span class="n">stringResponse</span><span class="p">,</span> 
   <span class="k">new</span> <span class="nf">JsonSerializerOptions</span><span class="p">()</span> <span class="p">{</span> <span class="n">PropertyNamingPolicy</span> <span class="p">=</span> <span class="n">JsonNamingPolicy</span><span class="p">.</span><span class="n">CamelCase</span> <span class="p">});</span>
</code></pre></div></div>

<!-- Before we test our app, we need to register HolidaysApiService in the **Startup.cs** file. We also need to register **IHttpClientFactory** using the **AddHttpClient** method. -->

<p>在测试该应用程序之前，我们需要在 <strong>Startup.cs</strong> 文件中注册 HolidaysApiService 服务。我们还需要调用 <strong>AddHttpClient</strong> 方法注册 <strong>IHttpClientFactory</strong>。</p>

<p><b>Startup.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddRazorPages</span><span class="p">();</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddServerSideBlazor</span><span class="p">();</span>
 
    <span class="n">services</span><span class="p">.</span><span class="n">AddSingleton</span><span class="p">&lt;</span><span class="n">IHolidaysApiService</span><span class="p">,</span> <span class="n">HolidaysApiService</span><span class="p">&gt;();</span>
 
    <span class="n">services</span><span class="p">.</span><span class="nf">AddHttpClient</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- Run the application and provide any country code and year in the text fields. Clicking the Submit button should invoke our **GetHolidays** method in the background and you should be able to see the list of public holidays as shown below. -->

<p>运行应用程序并在文本框中提供任意国家代码和年份。点击 <strong>Submit</strong> 按钮就会在后台调用我们的 <strong>GetHolidays</strong> 方法，然后您应该能看到如下所示的公共假期列表。</p>

<p><img src="/assets/images/202108/Making-HTTP-Requests-in-Blazor-Server-Apps-1.jpg" alt="Making-HTTP-Requests-in-Blazor-Server-Apps-1" /></p>

<!-- Creating Named HttpClient objects in Blazor Server Apps -->

<h2 id="在-blazor-server-应用程序中创建命名-httpclient-对象">在 Blazor Server 应用程序中创建<em>命名 HttpClient</em> 对象</h2>

<!-- The above example is good for scenarios where you are refactoring an existing application and you want to create HttpClient objects in some methods using IHttpClientFactory without affecting the entire application. If you are creating a new application or you want to centralize the way HttpClient objects are created, then you have to use named HTTP clients. -->

<p>上面的示例适用于您正在重构现有的应用程序，希望在不影响整个应用程序的情况下，在某些方法中使用 IHttpClientFactory 创建 HttpClient 对象的场景。如果您要创建一个全新的应用程序，或者您想要将创建 HttpClient 对象的方式集中化，那么您必须使用<strong>命名 HttpClient</strong>。</p>

<!-- Following are the benefits of creating named HTTP clients: -->

<p>下面是创建命名 HTTP 客户端的好处：</p>

<!-- We can give each HttpClient a name and specify all configurations related to HttpClient at the application startup instead of having configurations scattered throughout the application.
We can configure the named HttpClient once and reuse it multiple times for calling APIs of a particular API provider.
We can configure multiple named HttpClient objects with different configurations depending upon the usage of these clients in different areas of the application. -->

<ol>
  <li>我们可以为每个 HttpClient 命名，并在应用程序启动时指定与 HttpClient 相关的所有配置，而不是将配置分散在整个应用程序当中。</li>
  <li>我们可以只配置一次命名的 HttpClient，并多次重用它调用一个特定 API 提供者的所有 API。</li>
  <li>我们可以根据这些客户端在应用程序不同区域的使用情况，配置多个不同配置的命名 HttpClient 对象。</li>
</ol>

<!-- We can specify a named client in the **ConfigureServices** method of **Startup.cs** file using the name **AddHttpClient** method we used above. -->

<p>我们可以在 <strong>Startup.cs</strong> 文件的 <strong>ConfigureServices</strong> 方法中，使用前面用过的名为 <strong>AddHttpClient</strong> 方法指定一个命名的 HttpClient。</p>

<p><b>Startup.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddRazorPages</span><span class="p">();</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddServerSideBlazor</span><span class="p">();</span>
 
    <span class="n">services</span><span class="p">.</span><span class="n">AddSingleton</span><span class="p">&lt;</span><span class="n">IHolidaysApiService</span><span class="p">,</span> <span class="n">HolidaysApiService</span><span class="p">&gt;();</span>
 
    <span class="n">services</span><span class="p">.</span><span class="nf">AddHttpClient</span><span class="p">(</span><span class="s">"HolidaysApi"</span><span class="p">,</span> <span class="n">c</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="n">c</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"https://date.nager.at/"</span><span class="p">);</span>
        <span class="n">c</span><span class="p">.</span><span class="n">DefaultRequestHeaders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"Accept"</span><span class="p">,</span> <span class="s">"application/vnd.github.v3+json"</span><span class="p">);</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- We need to specify the name of the client e.g. **HolidaysApi** and we can also configure the **BaseAddress**, **DefaultRequestHeaders**, and other properties as shown above. -->

<p>我们需要指定客户端的名称（例如 <strong>HolidaysApi</strong>），我们还可以配置如上所示的 <strong>BaseAddress</strong>、<strong>DefaultRequestHeaders</strong> 和其他属性。</p>

<!-- Once the named HttpClient is configured, we can now create HttpClient objects throughout the application by using the same **CreateClient** method but this time we need to specify which named client e.g. **HolidaysApi** we want to create. -->

<p>配置了命名 HttpClient 之后，我们可以使用相同的 <strong>CreateClient</strong> 方法在整个应用程序中创建 HttpClient 对象，不过这次我们需要指定想要创建哪个已命名的客户端（例如 <strong>HolidaysApi</strong>）。</p>

<p><b>HolidaysApiService.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">HolidaysApiService</span> <span class="p">:</span> <span class="n">IHolidaysApiService</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IHttpClientFactory</span> <span class="n">_clientFactory</span><span class="p">;</span>
 
    <span class="k">public</span> <span class="nf">HolidaysApiService</span><span class="p">(</span><span class="n">IHttpClientFactory</span> <span class="n">clientFactory</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_clientFactory</span> <span class="p">=</span> <span class="n">clientFactory</span><span class="p">;</span>
    <span class="p">}</span>
 
    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;</span> <span class="nf">GetHolidays</span><span class="p">(</span><span class="n">HolidayRequestModel</span> <span class="n">holidaysRequest</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;();</span>
 
        <span class="kt">var</span> <span class="n">url</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"api/v2/PublicHolidays/{0}/{1}"</span><span class="p">,</span> 
            <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">Year</span><span class="p">,</span> <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">CountryCode</span><span class="p">);</span>
 
        <span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Get</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
 
        <span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="n">_clientFactory</span><span class="p">.</span><span class="nf">CreateClient</span><span class="p">(</span><span class="s">"HolidaysApi"</span><span class="p">);</span>
 
        <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
 
        <span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">stringResponse</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
 
            <span class="n">result</span> <span class="p">=</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;(</span><span class="n">stringResponse</span><span class="p">,</span>
                <span class="k">new</span> <span class="nf">JsonSerializerOptions</span><span class="p">()</span> <span class="p">{</span> <span class="n">PropertyNamingPolicy</span> <span class="p">=</span> <span class="n">JsonNamingPolicy</span><span class="p">.</span><span class="n">CamelCase</span> <span class="p">});</span>
        <span class="p">}</span>
        <span class="k">else</span>
        <span class="p">{</span>
            <span class="n">result</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;().</span><span class="nf">ToList</span><span class="p">();</span>
        <span class="p">}</span>
 
        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- The name e.g. **HolidaysApi** we mentioned in the **CreateClient** method must match with the name we configured in **Startup.cs** file. Each time a CreateClient method is called, a new instance of HttpClient is created for us. -->

<p>我们在 <strong>CreateClient</strong> 方法中传递的名称（比如 <strong>HolidaysApi</strong>）必须与我们在 <strong>Startup.cs</strong> 文件中配置的名称一致。每次调用 <strong>CreateClient</strong> 方法时，都会为我们创建一个新的 <strong>HttpClient</strong> 实例。</p>

<!-- We also don’t need to specify the API hostname in the Request URL because we already specified the based address in Startup.cs file. -->

<p>另外，我们不需要在请求的 URL 中指定 API 主机名称，因为我们在 <em>Startup.cs</em> 文件中已经指定过基地址了。</p>

<!-- Run the application once again and provide the country code and year values and you should be able to see the list of public holidays. -->

<p>再次运行应用程序并提供国家代码和年份值，您应该能看到以下公共假期列表。</p>

<p><img src="/assets/images/202108/Making-HTTP-Requests-in-Blazor-Server-Apps-1.jpg" alt="Making-HTTP-Requests-in-Blazor-Server-Apps-1" /></p>

<h2 id="在-blazor-server-应用程序中创建类型化-httpclient-对象">在 Blazor Server 应用程序中创建<em>类型化 HttpClient</em> 对象</h2>

<!-- The third option for creating and using HttpClient objects is to use Typed clients. These clients have the following benefits: -->

<p>创建和使用 HttpClient 对象的第三种选择是使用类型化客户端。这种客户端具有以下好处：</p>

<!-- 1. They provide the same capabilities as named clients without the need to use strings as keys.
2. They provide IntelliSense and compiler help when consuming clients.
3. They provide a single location to configure and interact with a particular HttpClient. For example, we can configure a typed HttpClient specific to a particular endpoint of Facebook API, and that HttpClient can encapsulate all the logic required to use that particular endpoint.
4. They work with Dependency Inject (DI) and can be injected where required. -->

<ol>
  <li>它们提供与命名客户端同样的功能，但无需使用字符串作为键。</li>
  <li>它们在使用客户端时提供智能感知和编译器帮助。</li>
  <li>它们提供了一个单一的存储单元来配置特定的 HttpClient 并与之交互。例如，我们可以配置针对 Facebook API 的一个特定终端的一个类型化 HttpClient，而且该 HttpClient 可以封装使用该特定终端所需的所有逻辑。</li>
  <li>它们与依赖注入 (DI) 一起使用，可以在需要的地方注入。</li>
</ol>

<!-- To configure a typed HTTPClient, we need to register it in **Startup.cs** file using the same **AddHttpClient** method but this time, we need to pass our service name **HolidaysApiService** as the type. -->

<p>要配置类型化的 HTTPClient，我们需要在 <strong>Startup.cs</strong> 文件中使用相同的 <strong>AddHttpClient</strong> 方法注册它，但这一次，我们需要传递我们的服务名称 <strong>HolidaysApiService</strong> 作为它的类型。</p>

<p><b>Startup.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddRazorPages</span><span class="p">();</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddServerSideBlazor</span><span class="p">();</span>
 
    <span class="n">services</span><span class="p">.</span><span class="n">AddSingleton</span><span class="p">&lt;</span><span class="n">IHolidaysApiService</span><span class="p">,</span> <span class="n">HolidaysApiService</span><span class="p">&gt;();</span>
 
    <span class="n">services</span><span class="p">.</span><span class="n">AddHttpClient</span><span class="p">&lt;</span><span class="n">HolidaysApiService</span><span class="p">&gt;();</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- In the code snippet above, the HTTP client and our service HolidaysApiService both will be registered as transient client and service. This will allow us to pass the HttpClient in the constructor of the service as shown in the following code snippet. Notice, how the HttpClient is exposed as the public property of the service. -->

<p>在上面的代码片段中，HTTP 客户端和我们的服务 HolidaysApiService 都将注册为瞬时客户端和服务。这将允许我们在服务的构造函数中传递 HttpClient，如以下代码片段所示。请注意，HttpClient 是如何公开为服务的 <code class="language-plaintext highlighter-rouge">public</code> 属性的。</p>

<p><b>HolidaysApiService.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">HolidaysApiService</span> <span class="p">:</span> <span class="n">IHolidaysApiService</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">HttpClient</span> <span class="n">Client</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
 
    <span class="k">public</span> <span class="nf">HolidaysApiService</span><span class="p">(</span><span class="n">HttpClient</span> <span class="n">client</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">client</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"https://date.nager.at/"</span><span class="p">);</span>
        <span class="n">client</span><span class="p">.</span><span class="n">DefaultRequestHeaders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"Accept"</span><span class="p">,</span> <span class="s">"application/vnd.github.v3+json"</span><span class="p">);</span>
        <span class="n">Client</span> <span class="p">=</span> <span class="n">client</span><span class="p">;</span>
    <span class="p">}</span>
 
    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;</span> <span class="nf">GetHolidays</span><span class="p">(</span><span class="n">HolidayRequestModel</span> <span class="n">holidaysRequest</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;();</span>
 
        <span class="kt">var</span> <span class="n">url</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"api/v2/PublicHolidays/{0}/{1}"</span><span class="p">,</span>
            <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">Year</span><span class="p">,</span> <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">CountryCode</span><span class="p">);</span>
 
        <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">Client</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span><span class="n">url</span><span class="p">);</span>
 
        <span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">stringResponse</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
 
            <span class="n">result</span> <span class="p">=</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;(</span><span class="n">stringResponse</span><span class="p">,</span>
                <span class="k">new</span> <span class="nf">JsonSerializerOptions</span><span class="p">()</span> <span class="p">{</span> <span class="n">PropertyNamingPolicy</span> <span class="p">=</span> <span class="n">JsonNamingPolicy</span><span class="p">.</span><span class="n">CamelCase</span> <span class="p">});</span>
        <span class="p">}</span>
        <span class="k">else</span>
        <span class="p">{</span>
            <span class="n">result</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;().</span><span class="nf">ToList</span><span class="p">();</span>
        <span class="p">}</span>
 
        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- The configuration for a typed client can be specified during registration in the **ConfigureServices** method of **Startup.cs** file, rather than in the typed client’s constructor. -->

<p>类型化客户端的配置也可以不在类型化客户端的构造函数中指定，而在注册期间在 <strong>Startup.cs</strong> 文件的 <strong>ConfigureServices</strong> 方法中指定。</p>

<p><b>Startup.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddRazorPages</span><span class="p">();</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddServerSideBlazor</span><span class="p">();</span> 
 
    <span class="n">services</span><span class="p">.</span><span class="n">AddHttpClient</span><span class="p">&lt;</span><span class="n">IHolidaysApiService</span><span class="p">,</span> <span class="n">HolidaysApiService</span><span class="p">&gt;(</span><span class="n">c</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="n">c</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"https://date.nager.at/"</span><span class="p">);</span>
        <span class="n">c</span><span class="p">.</span><span class="n">DefaultRequestHeaders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"Accept"</span><span class="p">,</span> <span class="s">"application/vnd.github.v3+json"</span><span class="p">);</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- If you are using this technique, then you don’t need to register your service separately. You can remove the following line from the ConfigureServices method. -->

<p>如果您使用的是这种方式，则无需单独注册您的服务。您可以从 ConfigureServices 方法中删除下面这行代码。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="n">AddSingleton</span><span class="p">&lt;</span><span class="n">IHolidaysApiService</span><span class="p">,</span> <span class="n">HolidaysApiService</span><span class="p">&gt;();</span>
</code></pre></div></div>

<!-- The HttpClient object can be encapsulated within a typed client rather than exposed as a public property. We can then use this client internally in any method of service. -->

<p>可以将 HttpClient 对象密封在一个类型化客户端中，而不公开为 <code class="language-plaintext highlighter-rouge">public</code> 属性。然后，我们可以在服务内部的任意方法中使用这个客户端。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">HolidaysApiService</span> <span class="p">:</span> <span class="n">IHolidaysApiService</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">HttpClient</span> <span class="n">_httpClient</span><span class="p">;</span>
 
    <span class="k">public</span> <span class="nf">HolidaysApiService</span><span class="p">(</span><span class="n">HttpClient</span> <span class="n">client</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_httpClient</span> <span class="p">=</span> <span class="n">client</span><span class="p">;</span>
    <span class="p">}</span>
 
    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;</span> <span class="nf">GetHolidays</span><span class="p">(</span><span class="n">HolidayRequestModel</span> <span class="n">holidaysRequest</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;();</span>
 
        <span class="kt">var</span> <span class="n">url</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"api/v2/PublicHolidays/{0}/{1}"</span><span class="p">,</span>
            <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">Year</span><span class="p">,</span> <span class="n">holidaysRequest</span><span class="p">.</span><span class="n">CountryCode</span><span class="p">);</span>
 
        <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span><span class="n">url</span><span class="p">);</span>
 
        <span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">stringResponse</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
 
            <span class="n">result</span> <span class="p">=</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;&gt;(</span><span class="n">stringResponse</span><span class="p">,</span>
                <span class="k">new</span> <span class="nf">JsonSerializerOptions</span><span class="p">()</span> <span class="p">{</span> <span class="n">PropertyNamingPolicy</span> <span class="p">=</span> <span class="n">JsonNamingPolicy</span><span class="p">.</span><span class="n">CamelCase</span> <span class="p">});</span>
        <span class="p">}</span>
        <span class="k">else</span>
        <span class="p">{</span>
            <span class="n">result</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="n">HolidayResponseModel</span><span class="p">&gt;().</span><span class="nf">ToList</span><span class="p">();</span>
        <span class="p">}</span>
 
        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- Run the application once again and provide the country code and year values and you should be able to see the list of public holidays. -->

<p>再次运行应用程序，并提供国家代码和年份值，您应该能够看到以下公共假期列表。</p>

<p><img src="/assets/images/202108/Using-HTTP-Client-to-Call-Third-Party-APIs-in-Blazor.jpg" alt="Using-HTTP-Client-to-Call-Third-Party-APIs-in-Blazor" /></p>

<h2 id="总结">总结</h2>

<!-- In this tutorial, I covered different techniques of creating and using HTTP clients in Blazor Server Apps. Most of the techniques mentioned here can also be used in ASP.NET Core applications because Blazor Server Apps are built on top of ASP.NET Core infrastructure. In my next post 《Making HTTP Requests in Blazor WebAssembly Apps》, I will try to cover the creation and usage of HTTP clients in Blazor WebAssembly Apps. -->

<p>在本文中，我介绍了在 Blazor Server 应用程序中创建和使用 HTTP 客户端的不同技术。这里提到的大部分技术也可以在 ASP.NET Core 应用程序中使用，因为 Blazor Server 应用程序是构建在 ASP.NET Core 基础架构之上的。在我的下篇文章 <a href="https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-webassembly-apps">《Making HTTP Requests in Blazor WebAssembly Apps》</a> 中，我将尝试介绍 HTTP 客户端在 Blazor WebAssembly 应用程序中的创建和使用。</p>

<p><br /></p>

<p>相关阅读：</p>

<ul>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/05/a-beginners-guide-to-blazor-server-and-webassembly-applications.html">Blazor Server 和 WebAssembly 应用程序入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/12/a-beginners-guide-to-blazor-components.html">Blazor 组件入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/19/a-developers-guide-to-blazor-data-binding.html">Blazor 数据绑定开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/26/a-developers-guide-to-blazor-event-handling.html">Blazor 事件处理开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/02/communication-between-blazor-components-using-eventcallback.html">Blazor 组件之间使用 EventCallback 进行通信</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/09/a-developers-guide-to-blazor-routing-and-navigation.html">Blazor 路由及导航开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components.html">Blazor 模板化组件开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps.html">Blazor Server 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/30/making-http-requests-in-blazor-webassembly-apps.html">Blazor WebAssembly 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/09/06/a-developers-guide-to-blazor-component-libraries.html">Blazor 组件库开发指南</a></li>
</ul>

<blockquote>
  <p>作者 ： Waqas Anwar<br />
翻译 ： <a href="https://ittranslator.cn/">技术译站</a><br />
链接 ： <a href="https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-server-apps">英文原文</a></p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-server-apps">https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-server-apps</a> Making HTTP Requests in Blazor Server Apps <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:download" role="doc-endnote">
      <p><a href="https://github.com/ezzylearning/BlazorServerWebAPIsDemo">https://github.com/ezzylearning/BlazorServerWebAPIsDemo</a> <a href="#fnref:download" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:nager" role="doc-endnote">
      <p><a href="https://date.nager.at/">https://date.nager.at/</a> <a href="#fnref:nager" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><category term="DotNet" /><category term="Blazor" /><summary type="html"><![CDATA[翻译自 Waqas Anwar 2021年5月4日的文章 《Making HTTP Requests in Blazor Server Apps》 1 https://www.ezzylearning.net/tutorial/making-http-requests-in-blazor-server-apps Making HTTP Requests in Blazor Server Apps &#8617;]]></summary></entry><entry><title type="html">Blazor 模板化组件开发指南</title><link href="https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components.html" rel="alternate" type="text/html" title="Blazor 模板化组件开发指南" /><published>2021-08-16T00:10:10+08:00</published><updated>2021-08-16T00:10:10+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components.html"><![CDATA[<blockquote>
  <p>翻译自 Waqas Anwar 2021年4月15日的文章 <a href="https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-templated-components">《A Developer’s Guide To Blazor Templated Components》</a> <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>
</blockquote>

<p><img src="/assets/images/202108/A-Developers-Guide-To-Blazor-Templated-Components.jpg" alt="A-Developers-Guide-To-Blazor-Templated-Components" /></p>

<!-- In one of my previous posts A Beginner’s Guide to Blazor Components, I covered the component parameters and showed you how to pass data to Blazor components as parameters to customize their functionality. In this post, I will go one step further and will show you how to pass one or more UI templates as parameters into a different type of Blazor components called Templated components. -->

<p>在我之前的一篇文章 <a href="https://ittranslator.cn/dotnet/csharp/2021/07/12/a-beginners-guide-to-blazor-components.html">Blazor 组件入门指南</a>中，我介绍了组件参数，并向您展示了如何将数据作为参数传递给 Blazor 组件以定制化其功能。在这篇文章中，我将更进一步向您展示，如何将一个或多个 UI 模板作为参数传递给一个称之为<em>模板化组件</em>的不同类型的 Blazor 组件。</p>

<!-- https://docs.microsoft.com/zh-cn/aspnet/core/blazor/components/templated-components -->

<p><a href="https://github.com/ezzylearning/BlazorTemplatedComponentDemo">下载源码</a><sup id="fnref:source" role="doc-noteref"><a href="#fn:source" class="footnote" rel="footnote">2</a></sup></p>

<h2 id="blazor-模板化组件概述">Blazor 模板化组件概述</h2>

<!-- Blazor templated component is a type of component that accepts one or more UI templates as parameters. This facilitates component reusability because you just need to create a templated component once and then every page where that component is used can provide its UI template which can be rendered by the templated component as per the page requirements. -->

<p>Blazor 模板化组件是一种接受将一个或多个 UI 模板作为参数的组件。这有助于组件的可重用性，因为您只需要创建一次模板化组件，然后使用该组件的每个页面都可以提供其 UI 模板，模板化组件可以根据页面需求渲染此 UI 模板。</p>

<p><img src="/assets/images/202108/Blazor-Templated-Component.png" alt="Blazor-Templated-Component" /></p>

<!-- The examples of templated component include: -->

<p>本文中的模板化组件示例包括：</p>

<!-- - A table component that allows a user to specify the templates for a table header, rows, and footer.
- A widget component that allows a user to render different widgets with the same look and feel but different contents.
- A list component that allows the user to specify a template for rendering the list items like bullets or numbers.
- A list component that allows user to display data in list, grid, or cards view -->

<ol>
  <li>一个允许用户指定表格表头、行和页脚模板的表格组件。</li>
  <li>一个允许用户呈现具有相同外观和体验而具有不同内容的小部件组件。</li>
  <li>一个允许用户指定一个模板来呈现项目符号或编号等列表项的列表组件。</li>
  <li>一个允许用户以列表、网格或卡片视图来显示数据的列表组件。</li>
</ol>

<!-- When we create a parameter of any Blazor component, we commonly specify its type as string, int, or any other built-in .NET data type. To create a templated component, we create component parameters of type `RenderFragment` or `RenderFragment<T>`. RenderFragment allows us to provide a segment of UI that can be rendered by templated components. -->

<p>当我们创建 Blazor 组件的一个参数时，我们通常将其类型指定为 <code class="language-plaintext highlighter-rouge">string</code>、<code class="language-plaintext highlighter-rouge">int</code> 或者其他内置 .NET 数据类型。为了创建一个模板化组件，我们需要创建类型为 <code class="language-plaintext highlighter-rouge">RenderFragment</code> 或 <code class="language-plaintext highlighter-rouge">RenderFragment&lt;T&gt;</code> 的组件参数。<a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.renderfragment">RenderFragment</a> 允许我们提供一个可以由模板化组件渲染的 UI 内容片段（作为一个委托实现，将其内容写入到 RenderTreeBuilder）。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
<span class="k">public</span> <span class="n">RenderFragment</span> <span class="n">HeaderTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<!-- `RenderFragment<T>` go one step further and allows us to pass the parameter of type T which can be used to customize the output of the templated component. -->

<p><code class="language-plaintext highlighter-rouge">RenderFragment&lt;T&gt;</code> 更进一步，允许我们传入参数的类型 <code class="language-plaintext highlighter-rouge">T</code>，可以用它来自定义模板化组件的输出。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
<span class="k">public</span> <span class="n">RenderFragment</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">RowTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>
<!-- Getting Started with a Real World Example -->

<h2 id="从一个实例开始">从一个实例开始</h2>

<!-- To understand the templated components in detail, I have decided to build a TableWidget templated component that will allow us to customize the table header, rows, and footer in different formats. Let’s create a new Blazor Server App and add the basic functionality to rendering some data in table format before we create our first templated component. -->

<p>为了详细了解模板化组件，我决定构建一个 <strong>TableWidget</strong> 模板化组件，它允许我们自定义不同格式的表头、行和页脚。在创建第一个模板化组件之前，我们先来创建一个新的 Blazor Server 应用程序并添加其基本功能，以表格格式呈现一些数据。</p>

<!-- Create a **Data** folder in Blazor Server App and add the following two model classes in the **Data** folder. -->

<p>在 Blazor Server 应用程序中创建一个 <em>Data</em> 文件夹，并在 <em>Data</em> 文件夹中添加以下两个模型类。</p>

<p><b>Product.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Product</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Title</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">decimal</span> <span class="n">Price</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Quantity</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><b>Order.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Order</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">OrderNo</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="n">DateTime</span> <span class="n">OrderDate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Status</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">decimal</span> <span class="n">OrderTotal</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- Create a Services folder in the project and add the following IProductService and ProductService in the Services folder. For this tutorial, I am just returning some fake data to generate the table. -->

<p>在项目中创建一个 <em>Services</em> 文件夹，并在 <em>Services</em> 文件夹中添加如下的 <code class="language-plaintext highlighter-rouge">IProductService</code> 和 <code class="language-plaintext highlighter-rouge">ProductService</code>。在本教程中，我仅返回一些用于生成表格的模拟数据。</p>

<p><b>IProductService.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IProductService</span>
<span class="p">{</span>
    <span class="n">List</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;</span> <span class="nf">GetTopSellingProducts</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><b>ProductService.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ProductService</span> <span class="p">:</span> <span class="n">IProductService</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;</span> <span class="nf">GetTopSellingProducts</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;()</span>
        <span class="p">{</span>
            <span class="k">new</span> <span class="nf">Product</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span>
                <span class="n">Title</span> <span class="p">=</span> <span class="s">"Wireless Mouse"</span><span class="p">,</span>
                <span class="n">Price</span> <span class="p">=</span> <span class="m">29.99</span><span class="n">m</span><span class="p">,</span>
                <span class="n">Quantity</span> <span class="p">=</span> <span class="m">3</span>
            <span class="p">},</span>
            <span class="k">new</span> <span class="nf">Product</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">2</span><span class="p">,</span>
                <span class="n">Title</span> <span class="p">=</span> <span class="s">"HP Headphone"</span><span class="p">,</span>
                <span class="n">Price</span> <span class="p">=</span> <span class="m">79.99</span><span class="n">m</span><span class="p">,</span>
                <span class="n">Quantity</span> <span class="p">=</span> <span class="m">4</span>
            <span class="p">},</span>
            <span class="k">new</span> <span class="nf">Product</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">3</span><span class="p">,</span>
                <span class="n">Title</span> <span class="p">=</span> <span class="s">"Sony Keyboard"</span><span class="p">,</span>
                <span class="n">Price</span> <span class="p">=</span> <span class="m">119.99</span><span class="n">m</span><span class="p">,</span>
                <span class="n">Quantity</span> <span class="p">=</span> <span class="m">5</span>
            <span class="p">}</span>
        <span class="p">};</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- Next, create IOrderService and OrderService in the same Services folder and add some fake order data to generate a table. -->

<p>接下来，在同一 <em>Services</em> 文件夹中创建 <code class="language-plaintext highlighter-rouge">IOrderService</code> 和 <code class="language-plaintext highlighter-rouge">OrderService</code> 并添加一些用于生成表格的模拟订单数据。</p>

<p><b>IOrderService.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IOrderService</span>
<span class="p">{</span>
    <span class="n">List</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;</span> <span class="nf">GetLatestOrders</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><b>OrderService.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">OrderService</span> <span class="p">:</span> <span class="n">IOrderService</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;</span> <span class="nf">GetLatestOrders</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;()</span>
        <span class="p">{</span>
            <span class="k">new</span> <span class="nf">Order</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span> 
                <span class="n">OrderNo</span> <span class="p">=</span> <span class="s">"12345"</span><span class="p">,</span>
                <span class="n">OrderDate</span> <span class="p">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">Today</span><span class="p">.</span><span class="nf">AddDays</span><span class="p">(-</span><span class="m">2</span><span class="p">),</span>
                <span class="n">Status</span> <span class="p">=</span> <span class="s">"Pending"</span><span class="p">,</span>
                <span class="n">OrderTotal</span> <span class="p">=</span> <span class="m">399.99</span><span class="n">m</span>
            <span class="p">},</span>
            <span class="k">new</span> <span class="nf">Order</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">2</span><span class="p">,</span>
                <span class="n">OrderNo</span> <span class="p">=</span> <span class="s">"67890"</span><span class="p">,</span>
                <span class="n">OrderDate</span> <span class="p">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">Today</span><span class="p">.</span><span class="nf">AddDays</span><span class="p">(-</span><span class="m">5</span><span class="p">),</span>
                <span class="n">Status</span> <span class="p">=</span> <span class="s">"Completed"</span><span class="p">,</span>
                <span class="n">OrderTotal</span> <span class="p">=</span> <span class="m">199.99</span><span class="n">m</span>
            <span class="p">},</span>
            <span class="k">new</span> <span class="nf">Order</span><span class="p">()</span>
            <span class="p">{</span>
                <span class="n">Id</span> <span class="p">=</span> <span class="m">3</span><span class="p">,</span>
                <span class="n">OrderNo</span> <span class="p">=</span> <span class="s">"13579"</span><span class="p">,</span>
                <span class="n">OrderDate</span> <span class="p">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">Today</span><span class="p">.</span><span class="nf">AddDays</span><span class="p">(-</span><span class="m">7</span><span class="p">),</span>
                <span class="n">Status</span> <span class="p">=</span> <span class="s">"Completed"</span><span class="p">,</span>
                <span class="n">OrderTotal</span> <span class="p">=</span> <span class="m">249.99</span><span class="n">m</span>
            <span class="p">}</span>
        <span class="p">};</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- We need to inject the above services in Blazor components using the dependency injection and for that purpose, we need to register the above services in Startup.cs file. If you want to learn more about dependency injection you can read my article A Step by Step Guide to ASP.NET Core Dependency Injection -->

<p>我们需要使用依赖注入将上述服务注入到 Blazor 组件中，为此，我们需要在 <em>Startup.cs</em> 文件中注册上述服务。如果您想了解关于依赖注入的更多知识，可以阅读我的文章 <a href="https://www.ezzylearning.net/tutorial/a-step-by-step-guide-to-asp-net-core-dependency-injection">A Step by Step Guide to ASP.NET Core Dependency Injection</a><sup id="fnref:DI" role="doc-noteref"><a href="#fn:DI" class="footnote" rel="footnote">3</a></sup>。</p>

<p><b>Startup.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddRazorPages</span><span class="p">();</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddServerSideBlazor</span><span class="p">();</span>
    <span class="n">services</span><span class="p">.</span><span class="n">AddSingleton</span><span class="p">&lt;</span><span class="n">WeatherForecastService</span><span class="p">&gt;();</span>
 
    <span class="n">services</span><span class="p">.</span><span class="n">AddScoped</span><span class="p">&lt;</span><span class="n">IOrderService</span><span class="p">,</span> <span class="n">OrderService</span><span class="p">&gt;();</span>
    <span class="n">services</span><span class="p">.</span><span class="n">AddScoped</span><span class="p">&lt;</span><span class="n">IProductService</span><span class="p">,</span> <span class="n">ProductService</span><span class="p">&gt;();</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- Next, create a Blazor components Dashboard.razor and its corresponding code-behind file Dashboard.razor.cs in the project Pages folder. If you are not familiar with Blazor components and code-behind files then read my post A Beginner’s Guide to Blazor Components. -->

<p>接下来，在项目 <em>Pages</em> 文件夹中创建 Blazor 组件 <em>Dashboard.razor</em> 及其对应的代码隐藏文件 <em>Dashboard.razor.cs</em>。如果您不熟悉 Blazor 组件及代码隐藏文件，请阅读我的文章 <a href="https://ittranslator.cn/dotnet/csharp/2021/07/12/a-beginners-guide-to-blazor-components.html">Blazor 组件入门指南</a>。</p>

<!-- The *Dashboard.razor.cs* file will inject both **IOrderService** and **IProductService** in the code-behind file of the component and then we will use the methods `GetLatestOrders` and `GetTopSellingProducts` to populate our local **Orders** and **Products** lists. -->

<p>组件的代码隐藏文件 <em>Dashboard.razor.cs</em> 中同时注入了 <strong>IOrderService</strong> 和 <strong>IProductService</strong>，然后我们将使用 <code class="language-plaintext highlighter-rouge">GetLatestOrders</code> 和 <code class="language-plaintext highlighter-rouge">GetTopSellingProducts</code> 方法来填充我们的本地 <strong>Orders</strong> 和 <strong>Products</strong> 列表。</p>

<p><b>Dashboard.razor.cs</b></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">Dashboard</span>
<span class="p">{</span>
    <span class="p">[</span><span class="n">Inject</span><span class="p">]</span>
    <span class="k">private</span> <span class="n">IOrderService</span> <span class="n">OrderService</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
    <span class="p">[</span><span class="n">Inject</span><span class="p">]</span>
    <span class="k">private</span> <span class="n">IProductService</span> <span class="n">ProductService</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;</span> <span class="n">Orders</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;</span> <span class="n">Products</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
    <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnInitialized</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">Orders</span> <span class="p">=</span> <span class="n">OrderService</span><span class="p">.</span><span class="nf">GetLatestOrders</span><span class="p">();</span>
        <span class="n">Products</span> <span class="p">=</span> <span class="n">ProductService</span><span class="p">.</span><span class="nf">GetTopSellingProducts</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- The razor component view file will simply run **foreach** loops on **Orders** and **Products** and will generate the HTML tables. -->

<p>Razor 组件视图文件将简单地在 <strong>Orders</strong> 和 <strong>Products</strong> 上运行 <strong>foreach</strong> 循环，并生成 HTML 表格。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/dashboard"
<span class="nt">&lt;h1&gt;</span>Dashboard<span class="nt">&lt;/h1&gt;</span>
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"col"</span><span class="nt">&gt;</span>
      @if (Orders != null)
      {
          <span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"table table-striped table-bordered"</span><span class="nt">&gt;</span>
             <span class="nt">&lt;thead</span> <span class="na">class=</span><span class="s">"thead-dark"</span><span class="nt">&gt;</span>
                <span class="nt">&lt;tr&gt;</span>
                   <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Order<span class="nt">&lt;/th&gt;</span>
                   <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Date<span class="nt">&lt;/th&gt;</span>
                   <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Status<span class="nt">&lt;/th&gt;</span>
                   <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Total<span class="nt">&lt;/th&gt;</span>
                <span class="nt">&lt;/tr&gt;</span>
             <span class="nt">&lt;/thead&gt;</span>
             <span class="nt">&lt;tbody&gt;</span>
                @foreach (var order in Orders)
                {
                    <span class="nt">&lt;tr&gt;</span>
                       <span class="nt">&lt;td&gt;</span>@order.OrderNo<span class="nt">&lt;/td&gt;</span>
                       <span class="nt">&lt;td&gt;</span>@order.OrderDate.ToShortDateString()<span class="nt">&lt;/td&gt;</span>
                       <span class="nt">&lt;td&gt;</span>@order.Status<span class="nt">&lt;/td&gt;</span>
                       <span class="nt">&lt;td&gt;</span>@order.OrderTotal<span class="nt">&lt;/td&gt;</span>
                    <span class="nt">&lt;/tr&gt;</span>
                }
             <span class="nt">&lt;/tbody&gt;</span>
          <span class="nt">&lt;/table&gt;</span>
      }
   <span class="nt">&lt;/div&gt;</span>
   <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"col"</span><span class="nt">&gt;</span>
      @if (Products != null)
      {
          <span class="nt">&lt;h3&gt;</span>Top Selling Products<span class="nt">&lt;/h3&gt;</span>
          <span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"table table-striped table-bordered"</span><span class="nt">&gt;</span>
             <span class="nt">&lt;thead</span> <span class="na">class=</span><span class="s">"thead-dark"</span><span class="nt">&gt;</span>
                <span class="nt">&lt;tr&gt;</span>
                   <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Title<span class="nt">&lt;/th&gt;</span>
                   <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Price<span class="nt">&lt;/th&gt;</span>
                   <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Quantity<span class="nt">&lt;/th&gt;</span>
                <span class="nt">&lt;/tr&gt;</span>
             <span class="nt">&lt;/thead&gt;</span>
             <span class="nt">&lt;tbody&gt;</span>
                @foreach (var product in Products)
                {
                    <span class="nt">&lt;tr&gt;</span>
                       <span class="nt">&lt;td&gt;</span>@product.Title<span class="nt">&lt;/td&gt;</span>
                       <span class="nt">&lt;td&gt;</span>@product.Price<span class="nt">&lt;/td&gt;</span>
                       <span class="nt">&lt;td&gt;</span>@product.Quantity<span class="nt">&lt;/td&gt;</span>
                    <span class="nt">&lt;/tr&gt;</span>
                }
             <span class="nt">&lt;/tbody&gt;</span>
          <span class="nt">&lt;/table&gt;</span>
      }
   <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<!-- If you will run the project, you will see the following two tables rendered on the page. -->

<p>此时如果您运行项目，将在页面上看到以下两个表格。</p>

<p><img src="/assets/images/202108/Display-Data-without-Templated-Components.png" alt="Display-Data-without-Templated-Components" /></p>

<!-- So far, we haven’t created any templated component but you can feel that we need one soon because both orders and products tables are shown above have almost the same look and feel and we are duplicating lots of HTML in the **foreach** loops above to generate these two tables. It is a good idea to create a templated component and then reuse that component to generate both of the above tables and still be able to customize the headers and data rows rendered by those tables. Let’s create our first templated component called the **TableWidget** component. -->

<p>截至目前，我们尚没有创建任何模板化组件，但您会感觉到我们很快将需要一个，因为上面显示的订单和产品表格几乎都具有相同的外观和体验，并且我们在上面的 <strong>foreach</strong> 循环中复制了大量的 HTML 来生成这两张表格。一个好注意是，创建一个模板化组件，然后重用该组件来生成上述两张表格，并且仍然能够自定义它们显示的表头和数据行。让我们来创建我们的第一个模板化组件，命名为 <strong>TableWidget</strong> 组件。</p>

<h2 id="创建-blazor-模板化组件">创建 Blazor 模板化组件</h2>

<!-- Add a new Razor component TableWidget.razor in the Shared folder and add the following code in it. -->

<p>在 <strong>Shared</strong> 文件夹中新建一个 Razor 组件 <em>TableWidget.razor</em>，并在其中添加以下代码：</p>

<p><b>TableWidget.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@typeparam TItem
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;h3&gt;</span>@Title<span class="nt">&lt;/h3&gt;</span>
<span class="nt">&lt;table</span> <span class="na">class=</span><span class="s">"table table-striped table-bordered"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;thead</span> <span class="na">class=</span><span class="s">"thead-dark"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;tr&gt;</span>
         @HeaderTemplate
      <span class="nt">&lt;/tr&gt;</span>
   <span class="nt">&lt;/thead&gt;</span>
   <span class="nt">&lt;tbody&gt;</span>
      @foreach (var item in Items)
      {
      <span class="nt">&lt;tr&gt;</span>
         @RowTemplate(item)
      <span class="nt">&lt;/tr&gt;</span>
      }
   <span class="nt">&lt;/tbody&gt;</span>
   <span class="nt">&lt;tfoot&gt;</span>
      <span class="nt">&lt;tr&gt;</span>
         @FooterTemplate
      <span class="nt">&lt;/tr&gt;</span>
   <span class="nt">&lt;/tfoot&gt;</span>
<span class="nt">&lt;/table&gt;</span>
@code {
    [Parameter]
    public string Title { get; set; }
 
    [Parameter]
    public RenderFragment HeaderTemplate { get; set; }
 
    [Parameter]
    public RenderFragment<span class="nt">&lt;TItem&gt;</span> RowTemplate { get; set; }
 
    [Parameter]
    public RenderFragment FooterTemplate { get; set; }
 
    [Parameter]
    public IReadOnlyList<span class="nt">&lt;TItem&gt;</span> Items { get; set; }
}
</code></pre></div></div>

<!-- Our TableWidget component has the following three templates. -->

<p>我们的 TableWidget 组件包含以下三个模板：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
<span class="k">public</span> <span class="n">RenderFragment</span> <span class="n">HeaderTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
<span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
<span class="k">public</span> <span class="n">RenderFragment</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span> <span class="n">RowTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
 
<span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
<span class="k">public</span> <span class="n">RenderFragment</span> <span class="n">FooterTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<!-- The **HeaderTemplate** will allow users to render any UI template in the header of the table. This template is used to render the table header cells within **thead** element. -->

<p><strong>HeaderTemplate</strong> 允许用户在表格的表头中呈现任意 UI 模板。此模板用于在 <strong>thead</strong> 元素内渲染表格表头的单元格。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;thead</span> <span class="na">class=</span><span class="s">"thead-dark"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;tr&gt;</span>
      @HeaderTemplate
   <span class="nt">&lt;/tr&gt;</span>
<span class="nt">&lt;/thead&gt;</span>
</code></pre></div></div>

<!-- The **FooterTemplate** is similar to HeaderTemplate and it will allow users to render any UI template in the footer of the table. This template is used to render the table footer cells within **tfoot** element. -->

<p><strong>FooterTemplate</strong> 与 <strong>HeaderTemplate</strong> 类似，它允许用户在表格的页脚中呈现任意 UI 模板。此模板用于在 <strong>tfoot</strong> 元素内渲染表格页脚的单元格。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;tfoot&gt;</span>
   <span class="nt">&lt;tr&gt;</span>
      @FooterTemplate
   <span class="nt">&lt;/tr&gt;</span>
<span class="nt">&lt;/tfoot&gt;</span>
</code></pre></div></div>

<!-- The **RowTemplate** is of type `RanderFragment<TItem>` and it will allow users to render the UI template using any .NET type. The type is not fixed and declared as a generic type using the **@typeparam** directives on top of the component. -->

<p><strong>RowTemplate</strong> 的类型为 <code class="language-plaintext highlighter-rouge">RanderFragment&lt;TItem&gt;</code>，它允许用户使用任意的 .NET 类型渲染 UI 模板。该类型不是固定的，而是使用组件顶部的 <strong>@typeparam</strong> 指令声明为一个泛型类型。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@typeparam TItem
</code></pre></div></div>

<!-- We also created a collection of TItem objects in our component so that we can iterate over the collection and generate our table rows -->

<p>我们还在组件中创建了一个 <code class="language-plaintext highlighter-rouge">TItem</code> 对象的集合，以便我们可以迭代该集合生成表格的行。</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
<span class="k">public</span> <span class="n">IReadOnlyList</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span> <span class="n">Items</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<!-- The type of objects we will pass in our UI template will render using the following **foreach** loop. You will shortly see how this will help us to render both Products and Order tables using the same TableWidget component. -->

<p>我们将要传入 UI 模板中的 <code class="language-plaintext highlighter-rouge">TItem</code> 类型的对象会使用以下 <strong>foreach</strong> 循环进行渲染。您很快就会看到这将如何帮助我们使用相同的 TableWidget 组件同时渲染产品和订单表格。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;tbody&gt;</span>
   @foreach (var item in Items)
   {
       <span class="nt">&lt;tr&gt;</span>
          @RowTemplate(item)
       <span class="nt">&lt;/tr&gt;</span>
   }
<span class="nt">&lt;/tbody&gt;</span>
</code></pre></div></div>

<!-- Different Ways to Use Blazor Templated Component -->

<h2 id="使用-blazor-模板化组件的不同方式">使用 Blazor 模板化组件的不同方式</h2>

<!-- It is now time to see our TableWidget component in action and there are different ways we can use this component. Replace the Recent Orders table we generated above with the following TableWidget component. -->

<p>现在是时候来实践一下我们的 TableWidget 组件了，我们可以通过不同的方式使用这个组件。用下面的 TableWidget 组件替换我们前面生成的 <em>Recent Orders</em> 表格。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"col"</span><span class="nt">&gt;</span>
   @if (Orders != null)
   {
       <span class="nt">&lt;TableWidget</span> <span class="na">Title=</span><span class="s">"Recent Orders"</span> <span class="na">Items=</span><span class="s">"Orders"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;HeaderTemplate&gt;</span>
             <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Order<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Date<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Status<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Total<span class="nt">&lt;/th&gt;</span>
          <span class="nt">&lt;/HeaderTemplate&gt;</span>
          <span class="nt">&lt;RowTemplate&gt;</span>
             <span class="nt">&lt;td&gt;</span>@context.OrderNo<span class="nt">&lt;/td&gt;</span>
             <span class="nt">&lt;td&gt;</span>@context.OrderDate.ToShortDateString()<span class="nt">&lt;/td&gt;</span>
             <span class="nt">&lt;td&gt;</span>@context.Status<span class="nt">&lt;/td&gt;</span>
             <span class="nt">&lt;td&gt;</span>@context.OrderTotal<span class="nt">&lt;/td&gt;</span>
          <span class="nt">&lt;/RowTemplate&gt;</span>
       <span class="nt">&lt;/TableWidget&gt;</span>
   }
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<!-- In the above code snippet, the **Items** property is initialized with the **Orders** list we received from our service. Then we decided to use **HeaderTemplate** and **RowTemplate** to generate the header and footer of the table. You may be thinking from where the **context** came from. The **context** is an implicit parameter available to all component arguments of Type `RenderFragment<T>`. We can use **context** to access the properties of the object we are dealing with. In the above example, **context** will supply the order information to the template. -->

<p>在上面的代码片段中，<strong>Items</strong> 属性是使用我们的从服务获取的 <strong>Orders</strong> 列表进行初始化的。然后我们选择使用 <strong>HeaderTemplate</strong> 和 <strong>RowTemplate</strong> 来生成表格的表头和数据行。您可能在想 <strong>context</strong> 是从哪里来的？<strong>context</strong> 是一个隐式参数，所有类型为 <code class="language-plaintext highlighter-rouge">RenderFragment&lt;T&gt;</code> 的组件参数都可以使用。我们可以使用 <strong>context</strong> 访问我们正在处理对象的属性。在上面的示例中，<strong>context</strong> 将向模板提供订单信息。</p>

<!-- If you will run the project, you will see the following two tables rendered on the page. The Recent Orders table is now generated using our TableWidget component. -->

<p>如果此时您运行项目，会在页面上看到以下两个表格。现在，最近的订单（Recent Orders）表格是使用我们的 TableWidget 组件生成的了。</p>

<p><img src="/assets/images/202108/Display-Data-without-Templated-Components.png" alt="Display-Data-without-Templated-Components" /></p>

<!-- Let’s reuse our **TableWidget** component and this time generate the Top Selling Products table. This time, we are passing it Products list and we also specified our own **Context="product"** which means we can now access product properties using **product** instead of implicit parameter **context**. -->

<p>让我们重用 <strong>TableWidget</strong> 组件来生成热卖产品（Top Selling Products）表格。这一次，我们传递了 Products 列表给它，还指定了我们自己的 <strong>Context=”product”</strong>，这意味着现在我们可以使用 <strong>product</strong> 取代隐式参数 <strong>context</strong> 来访问产品的属性。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"col"</span><span class="nt">&gt;</span>
   @if (Products != null)
   {
       <span class="nt">&lt;TableWidget</span> <span class="na">Title=</span><span class="s">"Top Selling Products"</span> <span class="na">Items=</span><span class="s">"Products"</span> <span class="na">Context=</span><span class="s">"product"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;HeaderTemplate&gt;</span>
             <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Title<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Price<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Quantity<span class="nt">&lt;/th&gt;</span>
          <span class="nt">&lt;/HeaderTemplate&gt;</span>
          <span class="nt">&lt;RowTemplate&gt;</span>
             <span class="nt">&lt;td&gt;</span>@product.Title<span class="nt">&lt;/td&gt;</span>
             <span class="nt">&lt;td&gt;</span>@product.Price<span class="nt">&lt;/td&gt;</span>
             <span class="nt">&lt;td&gt;</span>@product.Quantity<span class="nt">&lt;/td&gt;</span>
          <span class="nt">&lt;/RowTemplate&gt;</span>
       <span class="nt">&lt;/TableWidget&gt;</span>
   }
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<!-- You are allow allowed to specify the Context at template level as shown in the example where the **Context="product"** is added to **RowTemplate**. -->

<p>您还可以在模板级别指定上下文（Context），如下面的示例所示，其中将 <strong>Context=”product”</strong> 添加到了 <strong>RowTemplate</strong>。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;TableWidget</span> <span class="na">Title=</span><span class="s">"Top Selling Products"</span> <span class="na">Items=</span><span class="s">"Products"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;HeaderTemplate&gt;</span>
      <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Title<span class="nt">&lt;/th&gt;</span>
      <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Price<span class="nt">&lt;/th&gt;</span>
      <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Quantity<span class="nt">&lt;/th&gt;</span>
   <span class="nt">&lt;/HeaderTemplate&gt;</span>
   <span class="nt">&lt;RowTemplate</span> <span class="na">Context=</span><span class="s">"product"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;td&gt;</span>@product.Title<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;td&gt;</span>@product.Price<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;td&gt;</span>@product.Quantity<span class="nt">&lt;/td&gt;</span>
   <span class="nt">&lt;/RowTemplate&gt;</span>
<span class="nt">&lt;/TableWidget&gt;</span>
</code></pre></div></div>

<!-- If you will run the project, you will see the following two tables rendered on the page but this time we know that these two tables are rendered using our TableWidget templated component. This example clearly shows that the same templated component can be used to generate different types of UI and it can render the different types of objects as per our app requirement. -->

<p>现在如果您运行该项目，您将看到页面上显示了以下两个表格，但是我们知道这次这两个表格是使用我们的模板化组件 TableWidget 渲染的。该示例清楚地演示了，同一个模板化组件可用于生成不同类型的 UI，并且可以根据我们的应用程序需求渲染不同类型的对象。</p>

<p><img src="/assets/images/202108/Display-Data-without-Templated-Components.png" alt="Display-Data-without-Templated-Components" /></p>

<!-- Let’s reuse our TableWidget component with two more examples that will show the same Recent Orders and Top Selling Products with slightly different layouts -->

<p>下面让我们通过另外两个例子重用一下我们的 TableWidget 组件，它们将显示同样的最近订单（Recent Orders）和热销产品（Top Selling Products），但布局略有改变。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"col"</span><span class="nt">&gt;</span>
      @if (Orders != null)
      {
          <span class="nt">&lt;TableWidget</span> <span class="na">Title=</span><span class="s">"Recent Orders"</span> <span class="na">Items=</span><span class="s">"Orders"</span><span class="nt">&gt;</span>
             <span class="nt">&lt;HeaderTemplate&gt;</span>
                <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span> <span class="na">colspan=</span><span class="s">"2"</span><span class="nt">&gt;</span>Order Details<span class="nt">&lt;/th&gt;</span>
                <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Status<span class="nt">&lt;/th&gt;</span>
                <span class="nt">&lt;th</span> <span class="na">scope=</span><span class="s">"col"</span><span class="nt">&gt;</span>Total<span class="nt">&lt;/th&gt;</span>
             <span class="nt">&lt;/HeaderTemplate&gt;</span>
             <span class="nt">&lt;RowTemplate</span> <span class="na">Context=</span><span class="s">"order"</span><span class="nt">&gt;</span>
                <span class="nt">&lt;td</span> <span class="na">colspan=</span><span class="s">"2"</span><span class="nt">&gt;</span>
                   <span class="nt">&lt;b&gt;</span>Order No: <span class="nt">&lt;/b&gt;</span>@order.OrderNo
                   <span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
                   <span class="nt">&lt;b&gt;</span>Order Date: <span class="nt">&lt;/b&gt;</span>@order.OrderDate.ToShortDateString()
                <span class="nt">&lt;/td&gt;</span>
                <span class="nt">&lt;td&gt;</span>@order.Status<span class="nt">&lt;/td&gt;</span>
                <span class="nt">&lt;td&gt;</span>@order.OrderTotal<span class="nt">&lt;/td&gt;</span>
             <span class="nt">&lt;/RowTemplate&gt;</span>
          <span class="nt">&lt;/TableWidget&gt;</span>
      }
   <span class="nt">&lt;/div&gt;</span>
   <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"col"</span><span class="nt">&gt;</span>
      @if (Products != null)
      {
          <span class="nt">&lt;TableWidget</span> <span class="na">Title=</span><span class="s">"Top Selling Products"</span> <span class="na">Items=</span><span class="s">"Products"</span> <span class="na">TItem=</span><span class="s">”Product”</span><span class="nt">&gt;</span>
             <span class="nt">&lt;RowTemplate</span> <span class="na">Context=</span><span class="s">"product"</span><span class="nt">&gt;</span>
                <span class="nt">&lt;td&gt;</span>
                   <span class="nt">&lt;h2&gt;</span>@product.Title<span class="nt">&lt;/h2&gt;</span>
                   <span class="nt">&lt;h4&gt;&lt;b&gt;</span>@product.Price.ToString("C")<span class="nt">&lt;/b&gt;&lt;/h4&gt;</span>
                <span class="nt">&lt;/td&gt;</span>
             <span class="nt">&lt;/RowTemplate&gt;</span>
             <span class="nt">&lt;FooterTemplate&gt;</span>
                <span class="nt">&lt;td</span> <span class="na">class=</span><span class="s">"text-right"</span><span class="nt">&gt;&lt;b&gt;</span>Last 30 Days<span class="nt">&lt;/b&gt;&lt;/td&gt;</span>
             <span class="nt">&lt;/FooterTemplate&gt;</span>
          <span class="nt">&lt;/TableWidget&gt;</span>
      }
   <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<!-- When using generic-typed components, the type parameter is inferred if possible. However, we have the option to explicitly specify the type with an attribute that has a name matching the type parameter, which is **TItem** in the above example. -->

<p>在使用泛型类型组件时，会尽可能推断类型参数。不过，我们可以选择使用一个特性来显式指定类型，该特性的名称与类型参数相同，在上面的示例中是 <strong>TItem</strong>。</p>

<!-- If you will run the project, you will see all four tables rendered on the page using the same TableWidget templated component. -->

<p>此时如果您运行该项目，您将在页面上看到使用同一个 TableWidget 模板化组件渲染的全部四个表格。</p>

<p><img src="/assets/images/202108/Display-Data-with-Blazor-Templated-Component.jpg" alt="Display-Data-with-Blazor-Templated-Component" /></p>

<!-- Creating a Generic Templated Component -->

<h2 id="创建通用模板化组件">创建通用模板化组件</h2>

<!-- Our **TableWidget** component is good and we have already seen different examples it can be reused but the problem with that component is that it only generates HTML tables. What if we want to create an even more generic component that can be reused to generate any type of UI e.g. tables, cards, bullets, etc. We can create such a component easily by removing all markup from the templated component. Let’s create a generic **ListWidget** component to see one such component in action. -->

<p>我们的 <strong>TableWidget</strong> 组件很好，我们已见识了重用它的多个示例，但该组件的问题是它只生成了 HTML 表格。如果我们想要创建一个更通用的组件，可以重用它来生成任何类型的 UI（比如：表格、卡片、项目符号等）。我们可以通过从模板化组件中删除所有的标签来轻松地创建这样一个组件。让我们来创建一个通用的 <strong>ListWidget</strong> 组件，来实战练习一下这种组件。</p>

<!-- Create a new ListWidget.razor component in the **Shared** folder and add the following code in it. This time, we have no markup in the component and we just have an **ItemTemplate** in the **foreach** loop. This means we are free to generate any type of list using this ListWidget component. -->

<p>在 <strong>Shared</strong> 文件夹中创建一个新的 <em>ListWidget.razor</em> 组件，并在其中添加以下代码。这次在组件中没有 HTML 标签，在 <strong>foreach</strong> 循环中仅有一个 <strong>ItemTemplate</strong>。这意味着我们可以使用这个 ListWidget 组件自由地生成任意类型的列表。</p>

<p><b>ListWidget.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@typeparam TItem
 
@foreach (var item in Items)
{
    @ItemTemplate(item)
}
 
@code {
    [Parameter]
    public RenderFragment<span class="nt">&lt;TItem&gt;</span> ItemTemplate { get; set; }
 
    [Parameter]
    public IReadOnlyList<span class="nt">&lt;TItem&gt;</span> Items { get; set; }
}
</code></pre></div></div>

<!-- Let’s say we want to generate the bootstrap list using this ListWidget component so we can do this using the following code snippet. -->

<p>假如我们想要使用这个 ListWidget 组件生成 bootstrap 列表，那么我们可以使用下面的代码段来实现这一操作。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ul</span> <span class="na">class=</span><span class="s">"list-group"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"list-group-item d-flex justify-content-between align-items-center active"</span><span class="nt">&gt;</span>
      Latest Products
   <span class="nt">&lt;/li&gt;</span>
   <span class="nt">&lt;ListWidget</span> <span class="na">Items=</span><span class="s">"Products"</span> <span class="na">Context=</span><span class="s">"product"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;ItemTemplate&gt;</span>
         <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"list-group-item d-flex justify-content-between align-items-center"</span><span class="nt">&gt;</span>
            @product.Title
            <span class="nt">&lt;b&gt;</span>@product.Price.ToString("C")<span class="nt">&lt;/b&gt;</span>
            <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"badge badge-primary badge-pill"</span><span class="nt">&gt;</span>
            @product.Quantity
            <span class="nt">&lt;/span&gt;</span>
         <span class="nt">&lt;/li&gt;</span>
      <span class="nt">&lt;/ItemTemplate&gt;</span>
   <span class="nt">&lt;/ListWidget&gt;</span>
<span class="nt">&lt;/ul&gt;</span>
</code></pre></div></div>

<!-- Run the project and you will see the same list of products are now generated as bootstrap list component. -->

<p>运行该项目，您将看到以 bootstrap 列表方式生成的相同产品的列表。</p>

<p><img src="/assets/images/202108/Blazor-Generic-Templated-Component-Example-1.jpg" alt="Blazor-Generic-Templated-Component-Example 1" /></p>

<!-- Now let’s say you have another page where the list of products need to be displayed differently using the **div** and **a** tags so once again you can reuse same ListWidget component and this time generate markup like the following: -->

<p>现在，假设您有另一个页面，其中需要使用 <strong>div</strong> 和 <strong>a</strong> 标签以不同形式展示产品列表，那么您可以再次重用相同的 ListWidget 组件，这次生成如下标记：</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"list-group"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;a</span> <span class="na">class=</span><span class="s">"list-group-item d-flex justify-content-between align-items-center active"</span><span class="nt">&gt;</span>
   Latest Products
   <span class="nt">&lt;/a&gt;</span>
   <span class="nt">&lt;ListWidget</span> <span class="na">Items=</span><span class="s">"Products"</span> <span class="na">Context=</span><span class="s">"product"</span> <span class="na">TItem=</span><span class="s">"Product"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;ItemTemplate&gt;</span>
         <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">class=</span><span class="s">"list-group-item list-group-item-action flex-column align-items-start"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"d-flex w-100 justify-content-between"</span><span class="nt">&gt;</span>
               <span class="nt">&lt;h5</span> <span class="na">class=</span><span class="s">"mb-1"</span><span class="nt">&gt;&lt;b&gt;</span>@product.Title<span class="nt">&lt;/b&gt;&lt;/h5&gt;</span>
               <span class="nt">&lt;small</span> <span class="na">class=</span><span class="s">"text-muted"</span><span class="nt">&gt;</span>@product.Quantity units left<span class="nt">&lt;/small&gt;</span>
            <span class="nt">&lt;/div&gt;</span>
            <span class="nt">&lt;p</span> <span class="na">class=</span><span class="s">"mb-1"</span><span class="nt">&gt;</span>@product.Price.ToString("C")<span class="nt">&lt;/p&gt;</span>
         <span class="nt">&lt;/a&gt;</span>
      <span class="nt">&lt;/ItemTemplate&gt;</span>
   <span class="nt">&lt;/ListWidget&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<!-- Run the project and you will see output similar to the following. -->

<p>运行该项目，您将看到类似以下内容的输出。</p>

<p><img src="/assets/images/202108/Blazor-Generic-Templated-Component-Example-2.jpg" alt="Blazor-Generic-Templated-Component-Example 2" /></p>

<h2 id="总结">总结</h2>

<!-- In this tutorial, I gave you an overview of the Blazor templated component and we created two types of templated components. Next, we have seen several examples of reusing both the TableWidget and ListWidget components to generate different types of markup. I have to admit that the templated components are a wonderful addition to the Blazor developer’s toolbox and using these components we can create some amazing reusable components. -->

<p>在本教程中，我概述了 Blazor 模板化组件，并创建了两种类型的模板化组件。然后，我们实践了几个重用 TableWidget 和 ListWidget 组件来生成不同类型标记的例子。我不得不承认，模板化组件是 Blazor 开发者工具箱中的一个很好的补充，我们可以使用这些组件创建一些令人惊叹的可重用组件。</p>

<p><br /></p>

<p>相关阅读：</p>

<ul>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/05/a-beginners-guide-to-blazor-server-and-webassembly-applications.html">Blazor Server 和 WebAssembly 应用程序入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/12/a-beginners-guide-to-blazor-components.html">Blazor 组件入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/19/a-developers-guide-to-blazor-data-binding.html">Blazor 数据绑定开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/26/a-developers-guide-to-blazor-event-handling.html">Blazor 事件处理开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/02/communication-between-blazor-components-using-eventcallback.html">Blazor 组件之间使用 EventCallback 进行通信</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/09/a-developers-guide-to-blazor-routing-and-navigation.html">Blazor 路由及导航开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components.html">Blazor 模板化组件开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps.html">Blazor Server 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/30/making-http-requests-in-blazor-webassembly-apps.html">Blazor WebAssembly 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/09/06/a-developers-guide-to-blazor-component-libraries.html">Blazor 组件库开发指南</a></li>
</ul>

<blockquote>
  <p>作者 ： Waqas Anwar<br />
翻译 ： <a href="https://ittranslator.cn/">技术译站</a><br />
链接 ： <a href="https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-routing-and-navigation">英文原文</a></p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-templated-components">https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-templated-components</a> A Developer’s Guide To Blazor Templated Components <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:source" role="doc-endnote">
      <p><a href="https://github.com/ezzylearning/BlazorTemplatedComponentDemo">https://github.com/ezzylearning/BlazorTemplatedComponentDemo</a> 下载源码 <a href="#fnref:source" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:DI" role="doc-endnote">
      <p><a href="https://www.ezzylearning.net/tutorial/a-step-by-step-guide-to-asp-net-core-dependency-injection">https://www.ezzylearning.net/tutorial/a-step-by-step-guide-to-asp-net-core-dependency-injection</a> A Step by Step Guide to ASP.NET Core Dependency Injection <a href="#fnref:DI" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><category term="DotNet" /><category term="Blazor" /><summary type="html"><![CDATA[翻译自 Waqas Anwar 2021年4月15日的文章 《A Developer’s Guide To Blazor Templated Components》 1 https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-templated-components A Developer’s Guide To Blazor Templated Components &#8617;]]></summary></entry><entry><title type="html">Blazor 路由及导航开发指南</title><link href="https://ittranslator.cn/dotnet/csharp/2021/08/09/a-developers-guide-to-blazor-routing-and-navigation.html" rel="alternate" type="text/html" title="Blazor 路由及导航开发指南" /><published>2021-08-09T00:10:10+08:00</published><updated>2021-08-09T00:10:10+08:00</updated><id>https://ittranslator.cn/dotnet/csharp/2021/08/09/a-developers-guide-to-blazor-routing-and-navigation</id><content type="html" xml:base="https://ittranslator.cn/dotnet/csharp/2021/08/09/a-developers-guide-to-blazor-routing-and-navigation.html"><![CDATA[<blockquote>
  <p>翻译自 Waqas Anwar 2021年4月2日的文章 <a href="https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-routing-and-navigation">《A Developer’s Guide To Blazor Routing and Navigation》</a> <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>
</blockquote>

<p><img src="/assets/images/202108/A-Developers-Guide-To-Blazor-Routing-and-Navigation.jpg" alt="A-Developers-Guide-To-Blazor-Routing-and-Navigation" /></p>

<!-- Inspecting incoming request URLs and route them to appropriate views or pages are the basic features of every single-page app (SPA) framework. Blazor Server and WebAssembly apps also support routing using some built-in components and services. In this tutorial, I will cover everything you need to learn about implementing routing in Blazor apps. -->

<p>检查传入的请求 URL 并将它们导航到对应的视图或页面是每个单页应用程序 (SPA) 框架的基本功能。Blazor Server 和 WebAssembly 应用程序也同样支持使用一些内置组件和服务进行路由。在本教程中，我将向您介绍在 Blazor 应用程序中实现路由所需了解的所有内容。</p>

<h2 id="blazor-应用程序中的路由配置">Blazor 应用程序中的路由配置</h2>

<!-- Before we start creating routes for different Blazor components/pages we need to see how the Blazor Server apps are integrated into ASP.NET Core Endpoint Routing. Blazor Server apps communicate with the clients over SignalR connections and to accept incoming connections for Blazor components we call the **MapBlazorHub** method in **Startup.cs** file **Configure** method as follows: -->

<p>在开始为不同的 Blazor 组件/页面创建路由之前，我们需要了解如何将 Blazor Server 应用程序集成到 <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-5.0">ASP.NET Core Endpoint 路由</a>中。Blazor Server 应用程序通过 SignalR 连接与客户端进行通信，为了接受 Blazor 组件传入的连接，我们在 <strong>Startup.cs</strong> 文件的 <strong>Configure</strong> 方法中调用了 <strong>MapBlazorHub</strong> 方法，如下所示：</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">UseEndpoints</span><span class="p">(</span><span class="n">endpoints</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="n">endpoints</span><span class="p">.</span><span class="nf">MapBlazorHub</span><span class="p">();</span>
    <span class="n">endpoints</span><span class="p">.</span><span class="nf">MapFallbackToPage</span><span class="p">(</span><span class="s">"/_Host"</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<!-- The default configuration routes all requests to a Razor page which acts as the host for the server-side part of the Blazor Server app. By convention, this host page is **_Host.cshtml** and it is available in the **Pages** folder of the app. The route specified in the host file is called a fallback route and has very low priority in route matching which means this route is used when no other route matches. -->

<p>默认配置将所有请求都转发到一个 Razor 页面，该页面扮演 Blazor Server 应用程序服务端主机的角色。按照惯例，该主页是 <strong>_Host.cshtml</strong>，它位于应用程序的 <strong>Pages</strong> 文件夹中。该主文件中指定的路由称之为应急路由，在路由匹配中具有极低的优先级，这意味着当没有其他路由匹配时，才会使用该路由。</p>

<h2 id="blazor-路由组件介绍">Blazor 路由组件介绍</h2>

<!-- The **Router** component is one of the built-in components in Blazor and it is used in the **App** component of Blazor apps. This component enables routing in Blazor apps and supplies route data corresponding to the current navigation state. This component intercepts the incoming requests and renders the page that matches the requested URL. -->

<p><a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.router">Router</a><sup id="fnref:router" role="doc-noteref"><a href="#fn:router" class="footnote" rel="footnote">2</a></sup> 组件是 Blazor 中的内置组件之一，用在 Blazor 应用程序的 <strong>App</strong> 组件之中。该组件启用了 Blazor 应用程序中的路由，并提供与当前导航状态相对应的路由数据。它拦截传入的请求并呈现与请求 URL 相匹配的页面。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Router</span> <span class="na">AppAssembly=</span><span class="s">"@typeof(Program).Assembly"</span> <span class="na">PreferExactMatches=</span><span class="s">"@true"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Found</span> <span class="na">Context=</span><span class="s">"routeData"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;RouteView</span> <span class="na">RouteData=</span><span class="s">"@routeData"</span> <span class="na">DefaultLayout=</span><span class="s">"@typeof(MainLayout)"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/Found&gt;</span>
    <span class="nt">&lt;NotFound&gt;</span>
        <span class="nt">&lt;LayoutView</span> <span class="na">Layout=</span><span class="s">"@typeof(MainLayout)"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;p&gt;</span>Sorry, there's nothing at this address.<span class="nt">&lt;/p&gt;</span>
        <span class="nt">&lt;/LayoutView&gt;</span>
    <span class="nt">&lt;/NotFound&gt;</span>
<span class="nt">&lt;/Router&gt;</span>
</code></pre></div></div>

<!-- The following tables show the properties of the Router component. -->

<p>下表显示了 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.router">Router 组件</a>的属性。</p>

<table>
  <thead>
    <tr>
      <th>属性</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>AdditionalAssemblies</td>
      <td>获取或设置其他程序集的集合，这些程序集应在搜索可与 URI 匹配的组件时搜索。</td>
    </tr>
    <tr>
      <td>AppAssembly</td>
      <td>获取或设置应在其中搜索与 URI 匹配的组件的程序集。</td>
    </tr>
    <tr>
      <td>Found</td>
      <td>获取或设置当为请求的路由找到匹配项时要显示的内容。</td>
    </tr>
    <tr>
      <td>Navigating</td>
      <td>获取或设置异步导航正在进行时显示的内容。</td>
    </tr>
    <tr>
      <td>NotFound</td>
      <td>获取或设置当没有为请求的路由找到匹配项时要显示的内容。</td>
    </tr>
    <tr>
      <td>OnNavigateAsync</td>
      <td>获取或设置在导航到新页之前应调用的处理程序。</td>
    </tr>
  </tbody>
</table>

<!-- When Blazor components (.razor) are compiled their generated C# classes are saved in **obj\Debug\net5.0\Razor\Pages** folder -->

<p>当编译 Blazor 组件 (.razor) 时，它们生成的 C# 类会保存在 <strong>obj\Debug\net5.0\Razor\Pages</strong> 文件夹中。</p>

<p><img src="/assets/images/202108/Blazor-Componnts-Compiled-into-Classes.png" alt="Blazor-Componnts-Compiled-into-Classes" /></p>

<!-- If you will open any of the compiled files you will notice that after compilation, all the components with an **@page** directive have generated a class with the RouteAttribute attribute. -->

<p>如果您打开任意一个已编译的文件，将会注意到在编译之后，所有带有 <strong>@page</strong> 指令的组件都生成了一个带有 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routeattribute">RouteAttribute</a> 特性的类。</p>

<p><img src="/assets/images/202108/RouteAttribute-added-to-all-Blazor-Components-generated-classes.png" alt="RouteAttribute-added-to-all-Blazor-Components-generated-classes" /></p>

<!-- When the app starts, the assembly specified by the **AppAssembly** property is scanned to gather the route information from all classes that have RouteAttribute specified on them. -->

<p>当应用程序启动时，会扫描通过 <strong>AppAssembly</strong> 属性指定的程序集，从所有指定了 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routeattribute">RouteAttribute</a> 特性的类中收集路由信息。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Router</span> <span class="na">AppAssembly=</span><span class="s">"@typeof(Program).Assembly"</span> <span class="na">PreferExactMatches=</span><span class="s">"@true"</span><span class="nt">&gt;</span>
</code></pre></div></div>

<!-- If you have created separate component class libraries and you want the app to scan and load routes from those assemblies, then you can use **AdditionalAssemblies** property that accepts a collection of [Assembly](https://docs.microsoft.com/zh-cn/dotnet/api/system.reflection.assembly) objects. -->

<p>如果您创建了独立的组件类库，并希望应用程序从这些程序集中扫描和加载路由，那么您可以使用 <strong>AdditionalAssemblies</strong> 属性来接受一个 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/system.reflection.assembly">Assembly</a> 对象集合。</p>

<p><img src="/assets/images/202108/Blazor-App-Additional-Routes-from-External-Assemblies.png" alt="Blazor-App-Additional-Routes-from-External-Assemblies" /></p>

<!-- Here is an example of loading route information from two routable components Component1 and Component2 defined in component class libraries. -->

<p>下面是一个从定义在组件类库中的两个可路由组件（<em>Component1</em> 和 <em>Component2</em>）加载路由信息的示例。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Router</span> <span class="na">AppAssembly=</span><span class="s">"@typeof(Program).Assembly"</span> <span class="na">PreferExactMatches=</span><span class="s">"@true"</span>
        <span class="na">AdditionalAssemblies=</span><span class="s">"new[] { typeof(Component1).Assembly, typeof(Component2).Assembly }"</span><span class="nt">&gt;</span> 
<span class="nt">&lt;/Router&gt;</span>
</code></pre></div></div>

<!-- At runtime, the **RouteView** component receives the **RouteData** from the **Router** along with any route parameters and renders the specified component with the layout defined in the component. If no layout is defined, then it uses the layout specified by the **DefaultLayout** property. The default layout is normally the **MainLayout** component available in the **Shared** folder but you can also create and specify a custom layout. -->

<p>在运行时，<a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routeview"><strong>RouteView</strong></a> 组件从 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.router"><strong>Router</strong></a> 接收 <a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routedata"><strong>RouteData</strong></a> 以及任意路由参数，并使用组件中定义的布局渲染指定的组件。如果未定义布局，则使用 <strong>DefaultLayout</strong> 属性指定的布局。默认的布局通常是 <strong>Shared</strong> 文件夹中的 <strong>MainLayout</strong> 组件，不过您也可以创建并指定一个自定义布局。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;RouteView</span> <span class="na">RouteData=</span><span class="s">"@routeData"</span> <span class="na">DefaultLayout=</span><span class="s">"@typeof(MainLayout)"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<!-- The **Found** template is used to display the contents of the matching route is found as you can see in the example below where a matching route is found and a Counter page is rendered in the browser. -->

<p><strong>Found</strong> 模板用于在找到匹配的路由时显示其内容，正如您在下图中所看到的那样，其中找到了一个匹配路由，并在浏览器中呈现了一个 Counter 页面。</p>

<p><img src="/assets/images/202108/Blazor-App-Counter-Page-Route.png" alt="Blazor-App-Counter-Page-Route" /></p>

<!-- The **NotFound** template is used to display the contents if no matching route is found. By default, the NotFound template is just displaying a message as shown in the screenshot below. -->

<p><strong>NotFound</strong> 模板用于在没有找到匹配的路由时显示内容。默认情况下，<strong>NotFound</strong> 模板仅显示一条消息，如下面的截图所示。</p>

<p><img src="/assets/images/202108/Blazor-App-Default-Error-Page-Contents.png" alt="Blazor-App-Default-Error-Page-Contents" /></p>

<!-- We can also create a custom error layout and page and display a custom error page. Let’s create a new custom Layout with the name **ErrorLayout.razor** in the **Shared** folder. -->

<p>我们还可以创建自定义错误的布局和页面，以显示自定义错误页面。让我们在 <strong>Shared</strong> 文件夹中创建一个新的名为 <strong>ErrorLayout.razor</strong> 的自定义布局。</p>

<p><b>ErrorLayout.razor</b></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@inherits LayoutComponentBase
 
<span class="nt">&lt;main</span> <span class="na">role=</span><span class="s">"main"</span> <span class="na">class=</span><span class="s">"container"</span><span class="nt">&gt;</span> 
    <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"text-center"</span><span class="nt">&gt;</span>
        @Body
    <span class="nt">&lt;/div&gt;</span> 
<span class="nt">&lt;/main&gt;</span>
</code></pre></div></div>

<!-- <b>ErrorLayout.razor</b> -->

<!-- Then change the **Layout** property of the **LayoutView** component to **ErrorLayout** and change the contents inside the **LayoutView** as follows -->

<p>然后将 <strong>LayoutView</strong> 组件的 <strong>Layout</strong> 属性改为 <strong>ErrorLayout</strong>，并将 <strong>LayoutView</strong> 里的内容修改如下：</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Router</span> <span class="na">AppAssembly=</span><span class="s">"@typeof(Program).Assembly"</span> <span class="na">PreferExactMatches=</span><span class="s">"@true"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Found</span> <span class="na">Context=</span><span class="s">"routeData"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;RouteView</span> <span class="na">RouteData=</span><span class="s">"@routeData"</span> <span class="na">DefaultLayout=</span><span class="s">"@typeof(MainLayout)"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/Found&gt;</span>
    <span class="nt">&lt;NotFound&gt;</span>
        <span class="nt">&lt;LayoutView</span> <span class="na">Layout=</span><span class="s">"@typeof(ErrorLayout)"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;h1</span> <span class="na">class=</span><span class="s">"display-1"</span><span class="nt">&gt;</span>404<span class="nt">&lt;/h1&gt;</span>
            <span class="nt">&lt;h1</span> <span class="na">class=</span><span class="s">"display-4"</span><span class="nt">&gt;</span>Not Found<span class="nt">&lt;/h1&gt;</span>
            <span class="nt">&lt;p</span> <span class="na">class=</span><span class="s">"lead"</span><span class="nt">&gt;</span>
                Oops! Looks like this page doesn't exist.
            <span class="nt">&lt;/p&gt;</span>
        <span class="nt">&lt;/LayoutView&gt;</span>
    <span class="nt">&lt;/NotFound&gt;</span>
<span class="nt">&lt;/Router&gt;</span>
</code></pre></div></div>

<!-- If you will run the app in the browser and will try to access a URL that is not specified anywhere in the application then you will see a custom 404 error page as shown below. -->

<p>现在，如果您在浏览器中运行应用程序，并尝试访问一个未在应用中任何位置指定过的 URL，那么您将会看到一个自定义的 404 错误页面，如下所示。</p>

<p><img src="/assets/images/202108/Blazor-App-Custom-Error-Page-Layout.png" alt="Blazor-App-Custom-Error-Page-Layout" /></p>

<!-- All Blazor apps should explicitly set the **PreferExactMatches** attribute to @true so that route matching prefers exact matches rather than wildcards. As per Microsoft official docs, this attribute will not be available from .NET 6 onward and the router will always prefer exact matches. -->

<p>所有 Blazor 应用程序都应将 <strong>PreferExactMatches</strong> 特性显式地设置为 <code class="language-plaintext highlighter-rouge">@true</code>，以便路由匹配更倾向于精确匹配，而不是通配符匹配。根据 Microsoft 官方文档，此特性从 .NET 6 开始将不可用，路由器将总是更倾向于精确匹配。</p>

<h2 id="定义路由参数和约束">定义路由、参数和约束</h2>

<!-- Before we learn how to define routes for Blazor components, we need to make sure that we have the following `base` tag available on every page to resolve the URLs correctly. If you are creating Blazor Server App then you can add this tag in the head section of **Pages/_Host.cshtml** file and for Blazor WebAssembly apps this tag can be added in **wwwroot/index.html** file. -->

<p>在我们学习如何为 Blazor 组件定义路由之前，我们需要确保下面的 <code class="language-plaintext highlighter-rouge">base</code> 标签在每个页面都可用，以便正确地解析 URL。如果创建的是 Blazor Server 应用程序，那么您可以将此标签添加到 <strong>Pages/_Host.cshtml</strong> 文件的 <code class="language-plaintext highlighter-rouge">head</code> 部分，如果是 Blazor WebAssembly 应用程序，则可以将此标签添加到 <strong>wwwroot/index.html</strong> 文件中。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;base</span> <span class="na">href=</span><span class="s">"~/"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<!-- To define routes, we can use the @page directive as shown in the Counter component example below. -->

<p>要定义路由，我们可以使用 <strong>@page</strong> 指令，如下面的 Counter 组件示例所示。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/counter"
 
<span class="nt">&lt;h1&gt;</span>Counter<span class="nt">&lt;/h1&gt;</span>
 
<span class="nt">&lt;p&gt;</span>Current count: @currentCount<span class="nt">&lt;/p&gt;</span>
 
<span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="err">@</span><span class="na">onclick=</span><span class="s">"IncrementCount"</span><span class="nt">&gt;</span>Click me<span class="nt">&lt;/button&gt;</span>
 
@code {
    private int currentCount = 0;
 
    private void IncrementCount()
    {
        currentCount++;
    }
}
</code></pre></div></div>

<!-- We can now access the counter component using the **/counter** URL. -->

<p>现在我们就可以使用 <strong>/counter</strong> URL 访问 Counter 组件了。</p>

<p><img src="/assets/images/202108/Blazor-App-Counter-Page-Route.png" alt="Blazor-App-Counter-Page-Route" /></p>

<!-- We are also allowed to define multiple route templates using multiple **@page** directives as shown in the example below. -->

<p>我们还可以使用多个 <strong>@page</strong> 指令定义多个路由模板，如下面例所示。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/counter"
@page "/mycounter"
</code></pre></div></div>

<!-- This means that now the same Counter component can also be accessed using **/mycounter** URL: -->

<p>这意味着现在也可以使用 <strong>/mycounter</strong> URL 访问同一个 Counter 组件：</p>

<p><img src="/assets/images/202108/Blazor-App-Counter-Page-with-Second-Route.png" alt="Blazor-App-Counter-Page-with-Second-Route" /></p>

<!-- It is very common practice to pass data from one page to another page using the route parameters and Blazor route templates support parameters. The route parameter names are case insensitive and once we have the route parameter defined the router automatically populates the corresponding component property with the same name. For example, in the following code snippet, we define a route parameter **title** and also created a corresponding property **Title** in the component. This property will populate with the value of route parameter text automatically. We are then displaying the Title property as a heading of the page inside the **h1** element. -->

<p>使用路由参数将数据从一个页面传递到另一个页面是十分常见的做法，Blazor 路由模板支持路由参数。路由参数名称不区分大小写，一旦我们定义了路由参数，路由器就会自动填充对应的具有相同名称的组件属性。例如，在下面的代码片段中，我们在组件中定义了一个路由参数 <strong>title</strong>，并创建了一个对应的属性 <strong>Title</strong>。此属性将自动使用路由参数文本的值填充。然后，我们在 <code class="language-plaintext highlighter-rouge">h1</code> 元素中显示 Title 属性作为页面的标题。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/counter/{title}"
 
<span class="nt">&lt;h1&gt;</span>@Title<span class="nt">&lt;/h1&gt;</span>
 
<span class="nt">&lt;p&gt;</span>Current count: @currentCount<span class="nt">&lt;/p&gt;</span>
 
<span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="err">@</span><span class="na">onclick=</span><span class="s">"IncrementCount"</span><span class="nt">&gt;</span>Click me<span class="nt">&lt;/button&gt;</span>
 
@code {
    private int currentCount = 0;
 
    [Parameter]
    public string Title { get; set; }
 
    private void IncrementCount()
    {
        currentCount++;
    }
}
</code></pre></div></div>

<!-- Run the app and try to specify any string in the address bar after the /counter/ and you will see the route parameter value displayed as page heading. -->

<p>运行应用程序，并尝试在地址栏中 <strong>/counter/</strong> 之后指定任意的字符串，您将看到路由参数的值会显示为页面标题。</p>

<p><img src="/assets/images/202108/Blazor-App-with-Route-Parameter.png" alt="Blazor-App-with-Route-Parameter" /></p>

<!-- We are also allowed to define optional route parameters as shown in the example below where the title is an optional parameter as it has the question mark **(?)** after the parameter name. If we will not provide the value of this route parameter, the parameter will initialize with the default value of Counter in the **OnInitialized** method. -->

<p>我们还可以定义可选的路由参数，如下例所示，其中 <code class="language-plaintext highlighter-rouge">title</code> 是可选参数，因为在此参数名称后面带有问号 <strong>(?)</strong>。假如我们不提供此路由参数的值，该参数将在 <strong>OnInitialized</strong> 方法中使用默认值 <em>Counter</em> 进行初始化。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/counter/{title?}"
 
<span class="nt">&lt;h1&gt;</span>@Title<span class="nt">&lt;/h1&gt;</span>
 
<span class="nt">&lt;p&gt;</span>Current count: @currentCount<span class="nt">&lt;/p&gt;</span>
 
<span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="err">@</span><span class="na">onclick=</span><span class="s">"IncrementCount"</span><span class="nt">&gt;</span>Click me<span class="nt">&lt;/button&gt;</span>
 
@code {
    private int currentCount = 0;
 
    [Parameter]
    public string Title { get; set; }
 
    protected override void OnInitialized()
    {
        Title = Title ?? "Counter";
    }
 
    private void IncrementCount()
    {
        currentCount++;
    }
}
</code></pre></div></div>

<!-- Blazor also supports route constraints that enforce the type matching on a route. In the code snippet below, I created a route parameter `start` with an `int` type which means now I can only provide the integer value for this route parameter. The counter will now start with the value specified in the route parameter. -->

<p>Blazor 还支持路由约束，在路由上强制类型匹配。在下面的代码片段中，我创建了一个 <code class="language-plaintext highlighter-rouge">int</code> 类型的路由参数 <code class="language-plaintext highlighter-rouge">start</code>，这意味着现在我只能为此路由参数提供整数值。计数器现在将以路由参数中指定的值开始计数。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/counter/{start:int}"
 
<span class="nt">&lt;h1&gt;</span>Counter<span class="nt">&lt;/h1&gt;</span>
 
<span class="nt">&lt;p&gt;</span>Current count: @Start<span class="nt">&lt;/p&gt;</span>
 
<span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="err">@</span><span class="na">onclick=</span><span class="s">"IncrementCount"</span><span class="nt">&gt;</span>Click me<span class="nt">&lt;/button&gt;</span>
 
@code { 
    [Parameter]
    public int Start { get; set; }
 
    private void IncrementCount()
    {
        Start++;
    }
}
</code></pre></div></div>

<!-- Run the app in the browser and specify any integer value in the URL such as **/counter/4** and you will see the counter will start incrementing from this starting value. -->

<p>在浏览器中运行应用程序，并在 URL 中指定任一整数值，比如 <strong>/counter/4</strong>，您会看到计数器将以该起始值递增。</p>

<p><img src="/assets/images/202108/Blazor-App-with-Route-Parameter-Constraint.png" alt="Blazor-App-with-Route-Parameter-Constraint" /></p>

<!-- The following table shows the types supported by Blazor route constraints. -->

<p>下表显示了 Blazor 路由约束支持的类型。</p>

<table>
  <thead>
    <tr>
      <th>约束</th>
      <th>示例</th>
      <th>匹配项示例</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">bool</code></td>
      <td><code class="language-plaintext highlighter-rouge">{active:bool}</code></td>
      <td><code class="language-plaintext highlighter-rouge">true</code>,<code class="language-plaintext highlighter-rouge">FALSE</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">datetime</code></td>
      <td><code class="language-plaintext highlighter-rouge">{dob:datetime}</code></td>
      <td><code class="language-plaintext highlighter-rouge">2016-12-31</code>, <code class="language-plaintext highlighter-rouge">2016-12-31 7:32pm</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">decimal</code></td>
      <td><code class="language-plaintext highlighter-rouge">{price:decimal}</code></td>
      <td><code class="language-plaintext highlighter-rouge">49.99</code>, <code class="language-plaintext highlighter-rouge">-1,000.01</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">double</code></td>
      <td><code class="language-plaintext highlighter-rouge">{weight:double}</code></td>
      <td><code class="language-plaintext highlighter-rouge">1.234</code>, <code class="language-plaintext highlighter-rouge">-1,001.01e8</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">float</code></td>
      <td><code class="language-plaintext highlighter-rouge">{weight:float}</code></td>
      <td><code class="language-plaintext highlighter-rouge">1.234</code>, <code class="language-plaintext highlighter-rouge">-1,001.01e8</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">guid</code></td>
      <td><code class="language-plaintext highlighter-rouge">{id:guid}</code></td>
      <td><code class="language-plaintext highlighter-rouge">CD2C1638-1638-72D5-1638-DEADBEEF1638</code>, <code class="language-plaintext highlighter-rouge">{CD2C1638-1638-72D5-1638-DEADBEEF1638}</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">int</code></td>
      <td><code class="language-plaintext highlighter-rouge">{id:int}</code></td>
      <td><code class="language-plaintext highlighter-rouge">123456789</code>, <code class="language-plaintext highlighter-rouge">-123456789</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">long</code></td>
      <td><code class="language-plaintext highlighter-rouge">{ticks:long}</code></td>
      <td><code class="language-plaintext highlighter-rouge">123456789</code>, <code class="language-plaintext highlighter-rouge">-123456789</code></td>
    </tr>
  </tbody>
</table>

<!-- Multiple route parameters can also be defined as shown in the example below where we defined **start** and **increment** as **int** type parameters. -->

<p>还可以定义多个路由参数，如下例所示，我们将 <code class="language-plaintext highlighter-rouge">start</code> 和 <code class="language-plaintext highlighter-rouge">increment</code> 定义为 <code class="language-plaintext highlighter-rouge">int</code> 类型的参数。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/counter/{start:int}/{increment:int}"
 
<span class="nt">&lt;h1&gt;</span>Counter<span class="nt">&lt;/h1&gt;</span>
 
<span class="nt">&lt;p&gt;</span>Current count: @Start<span class="nt">&lt;/p&gt;</span>
 
<span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="err">@</span><span class="na">onclick=</span><span class="s">"IncrementCount"</span><span class="nt">&gt;</span>Click me<span class="nt">&lt;/button&gt;</span>
 
@code { 
    [Parameter]
    public int Start { get; set; }
 
    [Parameter]
    public int Increment { get; set; }
 
    private void IncrementCount()
    {
        Start+=Increment;
    }
}
</code></pre></div></div>

<!-- Run the app and specified both `start` and `increment` values in the address URL as shown below and you will notice that not only the counter will start with the value `2` but it will also increment by `3` every time you will click the **Click me** button. -->

<p>如下所示，运行应用程序并在 URL 地址中指定 <code class="language-plaintext highlighter-rouge">start</code> 和 <code class="language-plaintext highlighter-rouge">increment</code> 的值，您会注意到，当您每次点击 <strong>Click me</strong> 按钮时，计数器不仅会以数字 <code class="language-plaintext highlighter-rouge">2</code> 开始计数，而且会以 <code class="language-plaintext highlighter-rouge">3</code> 递增。</p>

<p><img src="/assets/images/202108/Blazor-App-with-Multiple-Route-Parameter-and-Constraints.png" alt="Blazor-App-with-Multiple-Route-Parameter-and-Constraints" /></p>

<h2 id="blazor-navigationmanager-服务概述">Blazor NavigationManager 服务概述</h2>

<!-- The *NavigationManager* service allows us to manage URIs and navigation in C# code. The NavigationManager class has the following common properties, methods, and events. -->

<p><a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.navigationmanager"><strong>NavigationManager</strong></a> 服务允许我们在 C# 代码中管理 URI 和导航。<em>NavigationManager</em> 类具有以下常见的属性、方法和事件。</p>

<table>
  <thead>
    <tr>
      <th>名称</th>
      <th>类型</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>BaseUri</td>
      <td>属性</td>
      <td>获取或设置当前的基 URI。BaseUri 始终表示为字符串形式的绝对 URI，以斜杠结尾。 通常，这与文档中 <code class="language-plaintext highlighter-rouge">&lt;base&gt;</code> 元素的 <code class="language-plaintext highlighter-rouge">href</code> 特性相对应。</td>
    </tr>
    <tr>
      <td>Uri</td>
      <td>属性</td>
      <td>获取或设置当前 URI。 Uri 始终以字符串形式表示为绝对 URI。</td>
    </tr>
    <tr>
      <td>NavigateTo</td>
      <td>方法</td>
      <td>导航到指定 URI。</td>
    </tr>
    <tr>
      <td>ToAbsoluteUri</td>
      <td>方法</td>
      <td>将相对 URI 转换为绝对 URI。</td>
    </tr>
    <tr>
      <td>ToBaseRelativePath</td>
      <td>方法</td>
      <td>给定基 URI (比如，前面的 BaseUri 的返回值)，将绝对 URI 转换为相对于基 URI 前缀的 URI。</td>
    </tr>
    <tr>
      <td>LocationChanged</td>
      <td>事件</td>
      <td>当导航位置变化时触发的事件。</td>
    </tr>
  </tbody>
</table>

<!-- Let’s create a page to see some of the above properties and methods in action. Create a new Blazor component and inject the NavigationManager service using the @inject directive. Try to print the Uri and BaseUri properties on the page to see what type of URIs they return. -->

<p>让我们来创建一个页面，查看一下以上属性和方法的一些实际行为。创建一个新的 Blazor 组件并使用 <code class="language-plaintext highlighter-rouge">@inject</code> 指令注入 NavigationManager 服务。 尝试在页面上打印出 Uri 和 BaseUri 属性，来查看一下它们返回的是什么类型的 URI。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/navigationmanager"
@inject NavigationManager nvm
 
<span class="nt">&lt;h3&gt;</span>Navigation Manager<span class="nt">&lt;/h3&gt;</span>
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
 
<span class="nt">&lt;p&gt;</span>@nvm.Uri<span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;p&gt;</span>@nvm.BaseUri<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<!-- Run the app and you will see output similar to the following in the browser. The **Uri** property will display the current absolute URI of the page whereas the **BaseUri** property will display the current base URI. -->

<p>运行应用程序，您将在浏览器中看到类似以下内容的输出。<strong>Uri</strong> 属性显示当前页面的绝对 URI，而 <strong>BaseUri</strong> 属性显示当前的基 URI。</p>

<p><img src="/assets/images/202108/Blazor-App-NavigationManager-Properties.png" alt="Blazor-App-NavigationManager-Properties" /></p>

<!-- Add two buttons **Home Page** and **Counter Page** on the page and add their onclick event handler methods in the **@code** block. Inside the event handler methods, we can use the **NavigateTo** method to redirect the user to different Blazor components from the C# code. -->

<p>在页面上添加两个按钮 <strong>Home Page</strong> 和 <strong>Counter Page</strong>，并在 <strong>@code</strong> 代码块中添加它们的 <code class="language-plaintext highlighter-rouge">onclick</code> 事件处理方法。在事件处理方法中，我们可以在 C# 代码中使用 <strong>NavigateTo</strong> 方法将用户重定向到其它的 Blazor 组件。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@page "/navigationmanager"
@inject NavigationManager nvm
 
<span class="nt">&lt;h3&gt;</span>Navigation Manager<span class="nt">&lt;/h3&gt;</span>
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
 
<span class="nt">&lt;p&gt;</span>@nvm.Uri<span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;p&gt;</span>@nvm.BaseUri<span class="nt">&lt;/p&gt;</span>
 
<span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="err">@</span><span class="na">onclick=</span><span class="s">"GoToHome"</span><span class="nt">&gt;</span>
    Home Page
<span class="nt">&lt;/button&gt;</span>
 
<span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="err">@</span><span class="na">onclick=</span><span class="s">"GoToCounter"</span><span class="nt">&gt;</span>
    Counter Page
<span class="nt">&lt;/button&gt;</span>
 
@code {
 
    private void GoToHome()
    {
        nvm.NavigateTo("/");
    }
 
    private void GoToCounter()
    {
        nvm.NavigateTo("counter");
    }
}
</code></pre></div></div>

<!-- Run the app and try to click both buttons, you will be able to navigate to home and counter pages as expected. -->

<p>运行应用程序并试着点击这两个按钮，将按预期的那样，您可以导航到主页和计数器页面。</p>

<p><img src="/assets/images/202108/Blazor-App-NavigationManager-NavigateTo-Method.png" alt="Blazor-App-NavigationManager-NavigateTo-Method" /></p>

<!-- If you don’t want to handle navigation programmatically and want to generate hyperlinks in HTML then you can use Blazor NavLink component. The NavLink component is similar to HTML `<a>` element with some cool features. It automatically toggles the **active** class with the element if the **href** attribute value matches with the current URL. This allows us to apply different styles on the currently selected link. You can see the usage of this component in **Shared/NavMenu.razor** file -->

<p>如果不想以编程方式处理导航，而想在 HTML 中生成超链接，则可以使用 Blazor <a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.navlink"><strong>NavLink</strong></a> 组件。 NavLink 组件类似于 HTML 中的 <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> 元素，具有一些很酷的功能。如果 NavLink 的 <strong>href</strong> 特性值与当前的 URL 相匹配，则会自动切换该元素的 <strong>active</strong> CSS 类（class）。这就使得我们可以在当前选中的链接上应用不同的样式。您可以在 <strong>Shared/NavMenu.razor</strong> 文件中看到这个组件的用法。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"@NavMenuCssClass"</span> <span class="err">@</span><span class="na">onclick=</span><span class="s">"ToggleNavMenu"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;ul</span> <span class="na">class=</span><span class="s">"nav flex-column"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"nav-item px-3"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;NavLink</span> <span class="na">class=</span><span class="s">"nav-link"</span> <span class="na">href=</span><span class="s">""</span> <span class="na">Match=</span><span class="s">"NavLinkMatch.All"</span><span class="nt">&gt;</span>
                <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"oi oi-home"</span> <span class="na">aria-hidden=</span><span class="s">"true"</span><span class="nt">&gt;&lt;/span&gt;</span> Home
            <span class="nt">&lt;/NavLink&gt;</span>
        <span class="nt">&lt;/li&gt;</span>
        <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"nav-item px-3"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;NavLink</span> <span class="na">class=</span><span class="s">"nav-link"</span> <span class="na">href=</span><span class="s">"counter"</span><span class="nt">&gt;</span>
                <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"oi oi-plus"</span> <span class="na">aria-hidden=</span><span class="s">"true"</span><span class="nt">&gt;&lt;/span&gt;</span> Counter
            <span class="nt">&lt;/NavLink&gt;</span>
        <span class="nt">&lt;/li&gt;</span>
        <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"nav-item px-3"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;NavLink</span> <span class="na">class=</span><span class="s">"nav-link"</span> <span class="na">href=</span><span class="s">"fetchdata"</span><span class="nt">&gt;</span>
                <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"oi oi-list-rich"</span> <span class="na">aria-hidden=</span><span class="s">"true"</span><span class="nt">&gt;&lt;/span&gt;</span> Fetch data
            <span class="nt">&lt;/NavLink&gt;</span>
        <span class="nt">&lt;/li&gt;</span> 
    <span class="nt">&lt;/ul&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<!-- The component also has a Match property that can be set to one of the following: -->

<!-- 获取或设置一个值，该值表示 URL 匹配行为。 -->

<p><a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.navlink"><strong>NavLink</strong></a> 组件还有一个 <strong>Match</strong> 属性，可以设置为以下选项之一：</p>

<ul>
  <li><a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.navlinkmatch#Microsoft_AspNetCore_Components_Routing_NavLinkMatch_All">NavLinkMatch.All</a>：指定当 NavLink 与整个当前 URL 匹配时应处于活动状态。</li>
  <li><a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.navlinkmatch#Microsoft_AspNetCore_Components_Routing_NavLinkMatch_Prefix">NavLinkMatch.Prefix</a>（默认值）：指定当 NavLink 与当前 URL 的任意前缀匹配时应处于活动状态。</li>
</ul>

<blockquote>
  <p>Match 属性：获取或设置一个值，该值表示 URL 匹配行为。</p>
</blockquote>

<h2 id="总结">总结</h2>

<!-- In this tutorial, I tried to cover many routing features available in Blazor apps and also covered different routing related components and services available to developers. I hope you will be able to define routes, parameters, and constraints with more confidence now. If you liked this tutorial, please share it with others to spread the knowledge. -->

<p>在本教程中，我尝试介绍 Blazor 应用程序中的多种路由功能，还介绍了开发者可用的与路由相关的一些组件和服务。我希望您现在能够更熟练地定义路由、参数和约束。如果您喜欢本教程，请与他人分享以传播知识。</p>

<p><br /></p>

<p>相关阅读：</p>

<ul>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/05/a-beginners-guide-to-blazor-server-and-webassembly-applications.html">Blazor Server 和 WebAssembly 应用程序入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/12/a-beginners-guide-to-blazor-components.html">Blazor 组件入门指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/19/a-developers-guide-to-blazor-data-binding.html">Blazor 数据绑定开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/07/26/a-developers-guide-to-blazor-event-handling.html">Blazor 事件处理开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/02/communication-between-blazor-components-using-eventcallback.html">Blazor 组件之间使用 EventCallback 进行通信</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/09/a-developers-guide-to-blazor-routing-and-navigation.html">Blazor 路由及导航开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/16/a-developers-guide-to-blazor-templated-components.html">Blazor 模板化组件开发指南</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/23/making-http-requests-in-blazor-server-apps.html">Blazor Server 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/08/30/making-http-requests-in-blazor-webassembly-apps.html">Blazor WebAssembly 应用程序中进行 HTTP 请求</a></li>
  <li><a href="https://ittranslator.cn/dotnet/csharp/2021/09/06/a-developers-guide-to-blazor-component-libraries.html">Blazor 组件库开发指南</a></li>
</ul>

<blockquote>
  <p>作者 ： Waqas Anwar<br />
翻译 ： <a href="https://ittranslator.cn/">技术译站</a><br />
链接 ： <a href="https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-routing-and-navigation">英文原文</a></p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-routing-and-navigation">https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-routing-and-navigation</a> A Developer’s Guide To Blazor Routing and Navigation <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:router" role="doc-endnote">
      <p><a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.router">https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.router</a> <a href="#fnref:router" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>技术译民</name></author><category term="dotnet" /><category term="csharp" /><category term="DotNet" /><category term="Blazor" /><summary type="html"><![CDATA[翻译自 Waqas Anwar 2021年4月2日的文章 《A Developer’s Guide To Blazor Routing and Navigation》 1 https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-routing-and-navigation A Developer’s Guide To Blazor Routing and Navigation &#8617;]]></summary></entry></feed>