日本語版
最新ニュース
企業

Episerver/Optimizely CMS 12 に Hangfire を追加する

2024年7月31日 スタニスワフ・ショコウスキー著 タグ: バックグラウンドジョブ (1) エピサーバー (4) ハンファイア (1) 仕事 (1) 最適化された (4) Episerver/Optimizely には、内部 EPI ジョブと単純なカスタム ケースに対応するビルド ScheduledJobs サポートが含まれていますが、アプリが大きくなり、必要なジョブの数が増えると、専用のソリューションでは機能しなくなります。 Hangfireとは何か、なぜ使うのか Hangfire は、バックグラウンド ジョブ (つまり、リクエスト処理パイプラインに入れたくない操作) の作成、処理、管理に役立つオープン ソース フレームワークです。 大量通知/ニュースレター; xml、csv、json からのバッチインポート。 アーカイブの作成; Webhook の起動。 ユーザーの削除; さまざまなグラフを構築する。 画像/ビデオ処理; 一時ファイルを消去します。…

Episerver/Optimizely CMS 12 に Hangfire を追加する

1722515631
2024-07-31 12:57:48

2024年7月31日

スタニスワフ・ショコウスキー著

タグ:


バックグラウンドジョブ
(1)


エピサーバー
(4)


ハンファイア
(1)


仕事
(1)


最適化された
(4)

Episerver/Optimizely には、内部 EPI ジョブと単純なカスタム ケースに対応するビルド ScheduledJobs サポートが含まれていますが、アプリが大きくなり、必要なジョブの数が増えると、専用のソリューションでは機能しなくなります。

Hangfireとは何か、なぜ使うのか

Hangfire は、バックグラウンド ジョブ (つまり、リクエスト処理パイプラインに入れたくない操作) の作成、処理、管理に役立つオープン ソース フレームワークです。

  • 大量通知/ニュースレター;
  • xml、csv、json からのバッチインポート。
  • アーカイブの作成;
  • Webhook の起動。
  • ユーザーの削除;
  • さまざまなグラフを構築する。
  • 画像/ビデオ処理;
  • 一時ファイルを消去します。
  • 定期的な自動レポート。
  • データベースのメンテナンス。

また、商用利用も無料です (追加機能は有料)。再試行ポリシー、さまざまな種類のジョブ、スケールアップもサポートされています。また、ジョブと呼ばれるシリアル化可能なメソッドを使用できることも非常に便利です。

から ハングファイア

Foundation プロジェクトを例に、いくつかの手順で Hangfire を Episerver/Optimizely 12 に追加する

まず、次のコマンドを使用して、Hangfire および Hangfire.Console Nuget パッケージをプロジェクトにインストールします。

dotnet add package Hangfire --version 1.8.14
dotnet add package Hangfire.Console --version 1.4.3

セキュリティが最優先なので、まずは管理者だけがアクセスできるように認証フィルターを追加しましょう。

using Hangfire.Annotations;
using Hangfire.Dashboard;

namespace Foundation.Features.Hangfire;

public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
{
    public bool Authorize([NotNull] DashboardContext context)
    {
        return EPiServer.Security.PrincipalInfo.CurrentPrincipal.IsInRole("CmsAdmins");
    }
}

CMS UI 内にネストされた優れた Hangfire UI を使用するには、そのためのコントローラーを作成する必要があります。

namespace Foundation.Features.Hangfire;

[Authorize(Roles = "CmsAdmin,WebAdmins,Administrators")]
[Route("[controller]")]
public class HangfireCmsController : Controller
{
    [Route("[action]")]
    public ActionResult Index()
    {
        return View();
    }
}

次に、コントローラーのビューを追加しましょう。Hangfire UI には iframe を使用するので、EPI ナビゲーションを維持します。

@using EPiServer.Framework.Web.Resources
@using EPiServer.Shell.Navigation

@{
    Layout = string.Empty;
}


 lang="en">


    </span>Hangfire Dashboard<span class="nt"/>
    @ClientResources.RenderResources("ShellCore")
    @ClientResources.RenderResources("ShellCoreLightTheme")

    <span class="nt"/>
        <span class="nt">html</span><span class="o">,</span>
        <span class="nt">body</span><span class="o">,</span>
        <span class="nc">.iframe-container</span> <span class="p">{</span>
            <span class="nl">height</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="nt">iframe</span> <span class="p">{</span>
            <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
            <span class="nl">height</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="nt"/>
<span class="nt"/>

<span class="nt"/>
    @Html.CreatePlatformNavigationMenu()
    <span class="nt"><div> <span class="err">@</span><span class="na">Html.ApplyPlatformNavigation</span><span class="err">(</span><span class="na">additionalClass:</span> <span class="err">"</span><span class="na">iframe-container</span><span class="err">")</span><span class="nt">></span>
        <span class="nt"><iframe> <span class="na">src=</span><span class="s">"/episerver/backoffice/Plugins/hangfire"</span> <span class="na">title=</span><span class="s">"Hangfire Dashboard"</span> <span class="na">frameborder=</span><span class="s">"0"</span><span class="nt">></span>
            <span class="nt"/>Your browser does not support iframes.<span class="nt"/>
        <span class="nt"/></iframe></span>
    <span class="nt"/></div></span>
<span class="nt"/>

<span class="nt"/>
</span></span></code></pre>
</div>
</div>
<p>Epi ナビゲーションを維持するためにすべての問題を乗り越えて、Epi ナビゲーションと統合する必要があるため、メニュー プロバイダーを実装しましょう。</p>
<div class="language-c# highlighter-rouge">
<div class="highlight">
<pre class="highlight"><code><span class="k">using</span> <span class="nn">EPiServer.Authorization</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">Foundation.Features.Hangfire</span><span class="p">;</span>

<span class="p">[</span><span class="n">MenuProvider</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">HangfireMenuProvider</span><span class="p">:</span> <span class="n">IMenuProvider</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">IEnumerable</span><span class="p"><span class="n">MenuItem</span><span class="p">></span>  <span class="nf">GetMenuItems</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">hangFireMenuItem</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">UrlMenuItem</span><span class="p">(</span><span class="s">"Hangfire"</span><span class="p">,</span> <span class="n">MenuPaths</span><span class="p">.</span><span class="n">Global</span> <span class="p">+</span> <span class="s">"/cms"</span> <span class="p">+</span> <span class="s">"/cmsMenuItem"</span><span class="p">,</span>
            <span class="s">"/HangfireCms/index"</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">IsAvailable</span> <span class="p">=</span> <span class="n">request</span> <span class="p">=></span> <span class="n">EPiServer</span><span class="p">.</span><span class="n">Security</span><span class="p">.</span><span class="n">PrincipalInfo</span><span class="p">.</span><span class="n">CurrentPrincipal</span><span class="p">.</span><span class="nf">IsInRole</span><span class="p">(</span><span class="s">"CmsAdmins"</span><span class="p">),</span>
            <span class="n">AuthorizationPolicy</span> <span class="p">=</span> <span class="n">CmsPolicyNames</span><span class="p">.</span><span class="n">CmsAdmin</span><span class="p">,</span>
            <span class="n">SortIndex</span> <span class="p">=</span> <span class="n">SortIndex</span><span class="p">.</span><span class="n">First</span> <span class="p">+</span> <span class="m">25</span>
        <span class="p">};</span>

        <span class="k">return</span> <span class="k">new</span> <span class="n">MenuItem</span><span class="p">[]</span>
        <span class="p">{</span>
            <span class="n">hangFireMenuItem</span>
        <span class="p">};</span>
    <span class="p">}</span>
<span class="p">}</span>
</span></code></pre>
</div>
</div>
<p>この例を終えた後に空のHangfire UIしか見られないのは残念です。簡単な繰り返しジョブを追加して、 <code class="language-plaintext highlighter-rouge">Hangfire.Console</code> 機能。Hangfireジョブの詳細については、ドキュメントを参照してください。 <a target="_blank" href="https://www.hangfire.io/overview.html" rel="noopener">ここ</a>。</p>
<div class="language-c# highlighter-rouge">
<div class="highlight">
<pre class="highlight"><code><span class="k">using</span> <span class="nn">Hangfire.Console</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Hangfire.Server</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">Foundation.Features.Hangfire</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">ExampleRecurringJob</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IContentTypeRepository</span> <span class="n">_contentTypeRepository</span><span class="p">;</span>


    <span class="k">public</span> <span class="nf">ExampleRecurringJob</span><span class="p">(</span><span class="n">IContentTypeRepository</span> <span class="n">contentTypeRepository</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_contentTypeRepository</span> <span class="p">=</span> <span class="n">contentTypeRepository</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Execute</span><span class="p">(</span><span class="n">PerformContext</span> <span class="n">context</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">context</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Hello, world!"</span><span class="p">);</span>
        <span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">1</span><span class="p">));</span>

        <span class="n">context</span><span class="p">.</span><span class="nf">SetTextColor</span><span class="p">(</span><span class="n">ConsoleTextColor</span><span class="p">.</span><span class="n">Red</span><span class="p">);</span>
        <span class="n">context</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Error! Just joking :)"</span><span class="p">);</span>
        <span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">0.2</span><span class="p">));</span>
        <span class="n">context</span><span class="p">.</span><span class="nf">ResetTextColor</span><span class="p">();</span>

        <span class="kt">var</span> <span class="n">bar</span> <span class="p">=</span> <span class="n">context</span><span class="p">.</span><span class="nf">WriteProgressBar</span><span class="p">();</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">contentType</span> <span class="k">in</span> <span class="n">_contentTypeRepository</span><span class="p">.</span><span class="nf">List</span><span class="p">().</span><span class="nf">WithProgress</span><span class="p">(</span><span class="n">bar</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="n">context</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">contentType</span><span class="p">.</span><span class="n">Name</span><span class="p">);</span>
            <span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">0.3</span><span class="p">));</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre>
</div>
</div>
<p>新しいクラスがすべて作成されたので、Hangfireの設定を追加する必要があります。 <code class="language-plaintext highlighter-rouge">Startup.cs</code>。</p>
<div class="language-c# highlighter-rouge">
<div class="highlight">
<pre class="highlight"><code>            <span class="c1">// Add Hangfire services.</span>
            <span class="n">services</span><span class="p">.</span><span class="nf">AddHangfire</span><span class="p">(</span><span class="n">configuration</span> <span class="p">=></span> <span class="n">configuration</span>
                <span class="p">.</span><span class="nf">SetDataCompatibilityLevel</span><span class="p">(</span><span class="n">CompatibilityLevel</span><span class="p">.</span><span class="n">Version_180</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">UseSimpleAssemblyNameTypeSerializer</span><span class="p">()</span>
                <span class="p">.</span><span class="nf">UseRecommendedSerializerSettings</span><span class="p">()</span>
                <span class="p">.</span><span class="nf">UseSqlServerStorage</span><span class="p">(</span><span class="n">_configuration</span><span class="p">.</span><span class="nf">GetConnectionString</span><span class="p">(</span><span class="s">"EcfSqlConnection"</span><span class="p">))</span>
                <span class="p">.</span><span class="nf">UseConsole</span><span class="p">());</span>

            <span class="c1">// Add the processing server as IHostedService</span>
            <span class="n">services</span><span class="p">.</span><span class="nf">AddHangfireServer</span><span class="p">();</span>
</code></pre>
</div>
</div>
<p>この構成も同様です。</p>
<div class="language-c# 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">=></span>
            <span class="p">{</span>
                <span class="n">endpoints</span><span class="p">.</span><span class="nf">MapControllerRoute</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="s">"Default"</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="s">"{controller}/{action}/{id?}"</span><span class="p">);</span>
                <span class="n">endpoints</span><span class="p">.</span><span class="nf">MapControllers</span><span class="p">();</span>
                <span class="n">endpoints</span><span class="p">.</span><span class="nf">MapRazorPages</span><span class="p">();</span>
                <span class="n">endpoints</span><span class="p">.</span><span class="nf">MapContent</span><span class="p">();</span>
                <span class="n">endpoints</span><span class="p">.</span><span class="nf">MapHangfireDashboard</span><span class="p">();</span>
            <span class="p">});</span>

            <span class="kt">var</span> <span class="n">dashboardOptions</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DashboardOptions</span>
            <span class="p">{</span>
                <span class="n">Authorization</span> <span class="p">=</span> <span class="k">new</span><span class="p">[]</span>
                <span class="p">{</span>
                    <span class="k">new</span> <span class="nf">HangfireAuthorizationFilter</span><span class="p">()</span>
                <span class="p">},</span>
                <span class="n">AppPath</span> <span class="p">=</span> <span class="k">null</span>
            <span class="p">};</span>
            <span class="c1">// Order of middlewares is important! Add it after Authentication and Authorization in order to have a user in the context.</span>
            <span class="n">app</span><span class="p">.</span><span class="nf">UseHangfireDashboard</span><span class="p">(</span><span class="s">"/episerver/backoffice/Plugins/hangfire"</span><span class="p">,</span> <span class="n">dashboardOptions</span><span class="p">);</span>

            <span class="n">RecurringJob</span><span class="p">.</span><span class="n">AddOrUpdate</span><span class="p"><span class="n">ExampleRecurringJob</span><span class="p">>(</span><span class="k">nameof</span><span class="p">(</span><span class="n">ExampleRecurringJob</span><span class="p">)</span> <span class="p">+</span> <span class="s">"_Id"</span><span class="p">,</span> <span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="k">null</span><span class="p">),</span> <span class="n">Cron</span><span class="p">.</span><span class="n">Daily</span><span class="p">);</span>
</span></code></pre>
</div>
</div>
<p>これですべて完了したので、アプリケーションをビルドして実行できます。CMS バック オフィスにログインしたら、次のようにします。</p>
<p><img decoding="async" src="https://szolkowski.github.io/assets/img/2024-07-31-hangfire-cms-overview.png" alt="Hangfire CMS の概要"/></p>
<p>作成されたサンプルジョブに移動します。実行の詳細の定期ジョブタブで手動で実行するようにトリガーした後、ライブコンソールエクスペリエンスを示す同様のビューが表示されます。</p>
<p><img decoding="async" src="https://szolkowski.github.io/assets/img/2024-07-31-hangfire-example-job-overview.png" alt="Hangfire - ライブコンソールを使用したサンプルジョブ"/></p>
<p>この投稿からのすべてのコード変更は以下で確認できます。 <a target="_blank" href="https://github.com/szolkowski/Foundation/pull/4" rel="noopener">Github Foundation フォーク プル リクエスト</a>。</p>
<p><noscript>表示するにはJavaScriptを有効にしてください <a target="_blank" href="https://disqus.com/?ref_noscript" rel="noopener">コメントはDisqusによって提供されています。</a></noscript></p></div>
<p>#EpiserverOptimizely #CMS #に #Hangfire #を追加する</p><div class="mykwj6a71882094094" >
<ins class="adsbygoogle"
     style="display:block; text-align:center;"
     data-ad-layout="in-article"
     data-ad-format="fluid"
     data-ad-client="ca-pub-0928966750491324"
     data-ad-slot="4752185579"></ins>
<script>
     (adsbygoogle = window.adsbygoogle || []).push({});
</script></div><style type="text/css">
@media screen and (min-width: 1201px) {
.mykwj6a71882094094 {
display: block;
}
}
@media screen and (min-width: 993px) and (max-width: 1200px) {
.mykwj6a71882094094 {
display: block;
}
}
@media screen and (min-width: 769px) and (max-width: 992px) {
.mykwj6a71882094094 {
display: block;
}
}
@media screen and (min-width: 768px) and (max-width: 768px) {
.mykwj6a71882094094 {
display: block;
}
}
@media screen and (max-width: 767px) {
.mykwj6a71882094094 {
display: block;
}
}
</style>

<div class="sharedaddy sd-sharing-enabled"><div class="robots-nocontent sd-block sd-social sd-social-icon sd-sharing"><h3 class="sd-title">Share this:</h3><div class="sd-content"><ul><li class="share-facebook"><a rel="nofollow noopener noreferrer"
				data-shared="sharing-facebook-3555261"
				class="share-facebook sd-button share-icon no-text"
				href="https://www.nipponese.news/episerver-optimizely-cms-12-%e3%81%ab-hangfire-%e3%82%92%e8%bf%bd%e5%8a%a0%e3%81%99%e3%82%8b/?share=facebook"
				target="_blank"
				aria-labelledby="sharing-facebook-3555261"
				>
				<span id="sharing-facebook-3555261" hidden>Share on Facebook (Opens in new window)</span>
				<span>Facebook</span>
			</a></li><li class="share-x"><a rel="nofollow noopener noreferrer"
				data-shared="sharing-x-3555261"
				class="share-x sd-button share-icon no-text"
				href="https://www.nipponese.news/episerver-optimizely-cms-12-%e3%81%ab-hangfire-%e3%82%92%e8%bf%bd%e5%8a%a0%e3%81%99%e3%82%8b/?share=x"
				target="_blank"
				aria-labelledby="sharing-x-3555261"
				>
				<span id="sharing-x-3555261" hidden>Share on X (Opens in new window)</span>
				<span>X</span>
			</a></li><li class="share-end"></li></ul></div></div></div>
<div id='jp-relatedposts' class='jp-relatedposts' >
	<h3 class="jp-relatedposts-headline"><em>Related</em></h3>
</div>					</div>

					<div class="byo-author-box">
						<strong>執筆者について: nipponese</strong>
						<p>
							Nipponese News編集部は、国内外のニュースを日本語で分かりやすくお届けします。						</p>
					</div>
				</div>

				<aside class="byo-article-side" aria-label="関連記事">
					<h2>関連記事</h2>
											<article>
							<a class="byo-kicker" href="https://www.nipponese.news/category/%e4%bc%81%e6%a5%ad/">企業</a>							<h3><a href="https://www.nipponese.news/%e3%82%b8%e3%83%a7%e3%82%b7%e3%83%a5%e3%83%bb%e3%83%96%e3%83%a9%e3%82%a6%e3%83%b3%e6%b0%8f%e3%80%812026%e5%b9%b4%e3%81%ab%e5%90%91%e3%81%91trv%e3%81%aa%e3%81%a9%e4%bf%9d%e9%99%ba%e5%a4%a7%e6%89%8b3/">ジョシュ・ブラウン氏、2026年に向けTRVなど保険大手3社を推奨</a></h3>
						</article>
											<article>
							<a class="byo-kicker" href="https://www.nipponese.news/category/%e4%bc%81%e6%a5%ad/">企業</a>							<h3><a href="https://www.nipponese.news/hfpa%e3%81%8cpenske-media-corporation%e3%82%92%e7%8b%ac%e5%8d%a0%e7%a6%81%e6%ad%a2%e6%b3%95%e9%81%95%e5%8f%8d%e3%81%a7%e6%8f%90%e8%a8%b4/">HFPAがPenske Media Corporationを独占禁止法違反で提訴</a></h3>
						</article>
											<article>
							<a class="byo-kicker" href="https://www.nipponese.news/category/%e4%bc%81%e6%a5%ad/">企業</a>							<h3><a href="https://www.nipponese.news/%e3%83%86%e3%82%b9%e3%83%a913%e5%8f%b0-%e3%80%8c%e3%83%8a%e3%83%81%e3%82%b9%e3%80%8d%e3%82%84%e3%80%8c%e3%83%95%e3%82%a1%e3%82%b7%e3%82%b9%e3%83%88%e3%80%8d%e3%81%ae%e6%96%87%e5%ad%97%e3%81%a7/">テスラ13台 「ナチス」や「ファシスト」の文字で汚損</a></h3>
						</article>
											<article>
							<a class="byo-kicker" href="https://www.nipponese.news/category/%e4%bc%81%e6%a5%ad/">企業</a>							<h3><a href="https://www.nipponese.news/%e7%b1%b3%e4%b8%bb%e8%a6%81%e6%a0%aa%e4%be%a1%e6%8c%87%e6%95%b0%e3%81%8c%e9%80%b1%e9%96%93%e3%81%a7%e4%b8%8b%e8%90%bd%e3%80%81%e5%8e%9f%e6%b2%b9%e5%ae%89%e3%82%84%e3%83%88%e3%83%a9%e3%83%b3%e3%83%97/">米主要株価指数が週間で下落、原油安やトランプ政権の新関税発動でまちまちの展開</a></h3>
						</article>
											<article>
							<a class="byo-kicker" href="https://www.nipponese.news/category/%e4%bc%81%e6%a5%ad/">企業</a>							<h3><a href="https://www.nipponese.news/trump-increases-tariff-on-generic-drugs-to-200/">Trump Increases Tariff on Generic Drugs to 200%</a></h3>
						</article>
														</aside>
			</div>

										<nav class="byo-article-navigation" aria-label="前後の記事">
					<div class="byo-newsroom-wrap byo-article-navigation__grid">
													<a class="byo-article-navigation__item byo-article-navigation__item--previous" href="https://www.nipponese.news/hibrew-%e3%81%a7%e3%82%b3%e3%83%bc%e3%83%92%e3%83%bc%e3%82%92%e3%81%8a%e5%a5%bd%e3%81%bf%e3%81%ae%e6%96%b9%e6%b3%95%e3%81%a7%e9%a3%b2%e3%82%80-kicktraq/">
								<span class="byo-article-navigation__label">← 前の記事</span>
								<span class="byo-article-navigation__title">HiBREW でコーヒーをお好みの方法で飲む :: Kicktraq</span>
							</a>
						
													<a class="byo-article-navigation__item byo-article-navigation__item--next" href="https://www.nipponese.news/%e3%83%8e%e3%83%bc%e3%82%b9%e3%82%ab%e3%83%ad%e3%83%a9%e3%82%a4%e3%83%8a%e5%b7%9e%e6%9c%80%e5%a4%a7%e3%81%ae%e3%82%af%e3%83%a9%e3%83%95%e3%83%88%e3%83%93%e3%83%bc%e3%83%ab%e9%86%b8%e9%80%a0%e6%89%80/">
								<span class="byo-article-navigation__label">次の記事 →</span>
								<span class="byo-article-navigation__title">ノースカロライナ州最大のクラフトビール醸造所</span>
							</a>
											</div>
				</nav>
					</article>
	</main>


	</div>
</div>

		<section class="byo-newsroom-footer-links" aria-label="Nipponese News">
			<div class="byo-newsroom-wrap">
				<div class="byo-newsroom-footer-links__brand">Nipponese News</div>
									<nav>
													<a href="https://www.nipponese.news/about-us/">About Us</a>
													<a href="https://www.nipponese.news/contact/">Contact</a>
													<a href="https://www.nipponese.news/style-guide/">Style Guide</a>
													<a href="https://www.nipponese.news/privacy-policy/">Privacy Policy</a>
											</nav>
							</div>
		</section>
		
<div class="site-footer">
			<footer class="site-info" aria-label="Site"  itemtype="https://schema.org/WPFooter" itemscope>
			<div class="inside-site-info grid-container">
								<div class="copyright-bar">
					© 2026 Nipponese News. All rights reserved.				<br><span class="byo-footer-contact">お問い合わせ・広告・著作権・不具合に関するご連絡は office@nipponese.news までメールでお願いします。</span></div>
			</div>
		</footer>
		</div>

<a title="Scroll back to top" aria-label="Scroll back to top" rel="nofollow" href="#" class="generate-back-to-top" data-scroll-speed="400" data-start-scroll="300" role="button">
					<span class="gp-icon icon-arrow-up"><svg viewBox="0 0 330 512" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M305.863 314.916c0 2.266-1.133 4.815-2.832 6.514l-14.157 14.163c-1.699 1.7-3.964 2.832-6.513 2.832-2.265 0-4.813-1.133-6.512-2.832L164.572 224.276 53.295 335.593c-1.699 1.7-4.247 2.832-6.512 2.832-2.265 0-4.814-1.133-6.513-2.832L26.113 321.43c-1.699-1.7-2.831-4.248-2.831-6.514s1.132-4.816 2.831-6.515L158.06 176.408c1.699-1.7 4.247-2.833 6.512-2.833 2.265 0 4.814 1.133 6.513 2.833L303.03 308.4c1.7 1.7 2.832 4.249 2.832 6.515z" fill-rule="nonzero" /></svg></span>
				</a><script type="speculationrules">
{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/byo-newsroom/*","/wp-content/themes/generatepress/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]}
</script>
<!-- Default Statcounter code for nipponese
http://www.nipponese.news/ -->
<script type="text/javascript">
var sc_project=12695711; 
var sc_invisible=1; 
var sc_security="6ab6c765"; 
</script>
<script type="text/javascript"
src="https://www.statcounter.com/counter/counter.js"
async></script>
<noscript><div class="statcounter"><a title="Web Analytics"
href="https://statcounter.com/" target="_blank"><img
class="statcounter"
src="https://c.statcounter.com/12695711/0/6ab6c765/1/"
alt="Web Analytics"
referrerPolicy="no-referrer-when-downgrade"></a></div></noscript>
<!-- End of Statcounter Code -->
<!-- Public Byoviral.com Tracking code -->
<script src="https://www.byoviral.com/badge/script?domain=nipponese.news" async defer></script>
<!-- End of Byoviral Code --><script id="generate-a11y">
!function(){"use strict";if("querySelector"in document&&"addEventListener"in window){var e=document.body;e.addEventListener("pointerdown",(function(){e.classList.add("using-mouse")}),{passive:!0}),e.addEventListener("keydown",(function(){e.classList.remove("using-mouse")}),{passive:!0})}}();
</script>
	<div class="gp-modal gp-search-modal" id="gp-search" role="dialog" aria-modal="true" aria-label="Search">
		<div class="gp-modal__overlay" tabindex="-1" data-gpmodal-close>
			<div class="gp-modal__container">
					<form role="search" method="get" class="search-modal-form" action="https://www.nipponese.news/">
		<label for="search-modal-input" class="screen-reader-text">Search for:</label>
		<div class="search-modal-fields">
			<input id="search-modal-input" type="search" class="search-field" placeholder="Search …" value="" name="s" />
			<button aria-label="Search"><span class="gp-icon icon-search"><svg viewBox="0 0 512 512" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"><path fill-rule="evenodd" clip-rule="evenodd" d="M208 48c-88.366 0-160 71.634-160 160s71.634 160 160 160 160-71.634 160-160S296.366 48 208 48zM0 208C0 93.125 93.125 0 208 0s208 93.125 208 208c0 48.741-16.765 93.566-44.843 129.024l133.826 134.018c9.366 9.379 9.355 24.575-.025 33.941-9.379 9.366-24.575 9.355-33.941-.025L337.238 370.987C301.747 399.167 256.839 416 208 416 93.125 416 0 322.875 0 208z" /></svg></span></button>
		</div>
			</form>
				</div>
		</div>
	</div>
	
	<script type="text/javascript">
		window.WPCOM_sharing_counts = {"https://www.nipponese.news/episerver-optimizely-cms-12-%e3%81%ab-hangfire-%e3%82%92%e8%bf%bd%e5%8a%a0%e3%81%99%e3%82%8b/":3555261};
	</script>
				<script id="force-refresh-js-js-extra">
var forceRefreshLocalizedData = {"apiEndpoint":"https://www.nipponese.news/wp-json/force-refresh/v1/current-version","postId":"3555261","isDebugActive":"","refreshInterval":"120","featureFlags":{"scheduledRefresh":true,"troubleshootingTerminal":false,"troubleshootingSubmitDebug":true},"versionFileUrl":null};
//# sourceURL=force-refresh-js-js-extra
</script>
<script id="force-refresh-js-js" src="https://www.nipponese.news/wp-content/plugins/force-refresh/dist/js/force-refresh.js?ver=1785693408"></script>
<script id="generate-menu-js-before">
var generatepressMenu = {"toggleOpenedSubMenus":true,"openSubMenuLabel":"Open Sub-Menu","closeSubMenuLabel":"Close Sub-Menu"};
//# sourceURL=generate-menu-js-before
</script>
<script id="generate-menu-js" src="https://www.nipponese.news/wp-content/themes/generatepress/assets/js/menu.min.js?ver=3.6.1"></script>
<script id="generate-modal-js" src="https://www.nipponese.news/wp-content/themes/generatepress/assets/dist/modal.js?ver=3.6.1"></script>
<script id="generate-navigation-search-js-before">
var generatepressNavSearch = {"open":"Open Search Bar","close":"Close Search Bar"};
//# sourceURL=generate-navigation-search-js-before
</script>
<script id="generate-navigation-search-js" src="https://www.nipponese.news/wp-content/themes/generatepress/assets/js/navigation-search.min.js?ver=3.6.1"></script>
<script id="generate-back-to-top-js-before">
var generatepressBackToTop = {"smooth":true};
//# sourceURL=generate-back-to-top-js-before
</script>
<script id="generate-back-to-top-js" src="https://www.nipponese.news/wp-content/themes/generatepress/assets/js/back-to-top.min.js?ver=3.6.1"></script>
<script async data-wp-strategy="async" fetchpriority="low" id="comment-reply-js" src="https://c0.wp.com/c/7.0.2/wp-includes/js/comment-reply.min.js"></script>
<script id="jetpack-stats-js-before">
_stq = window._stq || [];
_stq.push([ "view", {"v":"ext","blog":"201233194","post":"3555261","tz":"0","srv":"www.nipponese.news","j":"1:16.1-a.1"} ]);
_stq.push([ "clickTrackerInit", "201233194", "3555261" ]);
//# sourceURL=jetpack-stats-js-before
</script>
<script data-wp-strategy="defer" defer fetchpriority="low" id="jetpack-stats-js" src="https://stats.wp.com/e-202632.js"></script>
<script id="sharing-js-js-extra">
var sharing_js_options = {"lang":"en","counts":"1","is_stats_active":"1"};
//# sourceURL=sharing-js-js-extra
</script>
<script id="sharing-js-js" src="https://www.nipponese.news/wp-content/plugins/jetpack/_inc/build/sharedaddy/sharing.min.js?ver=16.1-a.1"></script>
<script id="sharing-js-js-after">
var windowOpen;
			( function () {
				function matches( el, sel ) {
					return !! (
						el.matches && el.matches( sel ) ||
						el.msMatchesSelector && el.msMatchesSelector( sel )
					);
				}

				document.body.addEventListener( 'click', function ( event ) {
					if ( ! event.target ) {
						return;
					}

					var el;
					if ( matches( event.target, 'a.share-facebook' ) ) {
						el = event.target;
					} else if ( event.target.parentNode && matches( event.target.parentNode, 'a.share-facebook' ) ) {
						el = event.target.parentNode;
					}

					if ( el ) {
						event.preventDefault();

						// If there's another sharing window open, close it.
						if ( typeof windowOpen !== 'undefined' ) {
							windowOpen.close();
						}
						windowOpen = window.open( el.getAttribute( 'href' ), 'wpcomfacebook', 'menubar=1,resizable=1,width=600,height=400' );
						return false;
					}
				} );
			} )();
var windowOpen;
			( function () {
				function matches( el, sel ) {
					return !! (
						el.matches && el.matches( sel ) ||
						el.msMatchesSelector && el.msMatchesSelector( sel )
					);
				}

				document.body.addEventListener( 'click', function ( event ) {
					if ( ! event.target ) {
						return;
					}

					var el;
					if ( matches( event.target, 'a.share-x' ) ) {
						el = event.target;
					} else if ( event.target.parentNode && matches( event.target.parentNode, 'a.share-x' ) ) {
						el = event.target.parentNode;
					}

					if ( el ) {
						event.preventDefault();

						// If there's another sharing window open, close it.
						if ( typeof windowOpen !== 'undefined' ) {
							windowOpen.close();
						}
						windowOpen = window.open( el.getAttribute( 'href' ), 'wpcomx', 'menubar=1,resizable=1,width=600,height=350' );
						return false;
					}
				} );
			} )();
//# sourceURL=sharing-js-js-after
</script>
<script id="wp-emoji-settings" type="application/json">
{"baseUrl":"https://s.w.org/images/core/emoji/17.0.2/72x72/","ext":".png","svgUrl":"https://s.w.org/images/core/emoji/17.0.2/svg/","svgExt":".svg","source":{"concatemoji":"https://www.nipponese.news/wp-includes/js/wp-emoji-release.min.js?ver=7.0.2"}}
</script>
<script type="module">
/*! This file is auto-generated */
const a=JSON.parse(document.getElementById("wp-emoji-settings").textContent),o=(window._wpemojiSettings=a,"wpEmojiSettingsSupports"),s=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const a=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===a[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e<n.data.length;e++)if(0!==n.data[e])return!1;return!0}function u(e,t,n,a){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!a(e,"\ud83e\u1fac8")}return!1}function f(e,t,n,a){let r;const o=(r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),s=(o.textBaseline="top",o.font="600 32px Arial",{});return e.forEach(e=>{s[e]=t(o,e,n,a)}),s}function r(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}a.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),c.toString(),p.toString()].join(",")+"));",a=new Blob([e],{type:"text/javascript"});const r=new Worker(URL.createObjectURL(a),{name:"wpTestEmojiSupports"});return void(r.onmessage=e=>{i(n=e.data),r.terminate(),t(n)})}catch(e){}i(n=f(s,u,c,p))}t(n)}).then(e=>{for(const n in e)a.supports[n]=e[n],a.supports.everything=a.supports.everything&&a.supports[n],"flag"!==n&&(a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&a.supports[n]);var t;a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&!a.supports.flag,a.supports.everything||((t=a.source||{}).concatemoji?r(t.concatemoji):t.wpemoji&&t.twemoji&&(r(t.twemoji),r(t.wpemoji)))});
//# sourceURL=https://www.nipponese.news/wp-includes/js/wp-emoji-loader.min.js
</script>

</body>
</html>
<!--
Performance optimized by Redis Object Cache. Learn more: https://wprediscache.com

Retrieved 3134 objects (1 MB) from Redis using PhpRedis (v6.3.0).
-->


<!-- Page cached by LiteSpeed Cache 7.8.1 on 2026-08-04 06:35:12 -->