🐥note.

小鳥とMicrosoft <3 なエンジニアの技術Blog📚

ASP.NET Core 3.1の変更点について

ついに.NET Core 3.1 LTSがリリースされましたね!

あわせてASP.NET Core 3.1もリリースされました。

devblogs.microsoft.com

Microsoft Docsの特設ページはこちらです。

docs.microsoft.com

変更点は以下の通りです。

Here’s what’s new in this release for ASP.NET Core:
- Partial class support for Razor components
- Pass parameters to top-level components
- New component tag helper
- Prevent default actions for events in Blazor apps
- Stop event propagation in Blazor apps
- Detailed errors during Blazor app development
- Support for shared queues in HttpSysServer
- Breaking changes for SameSite cookies

気になる点だけ細かく見ていきましょう。

Partial class support for Razor components

Razor ComponentでPartial Classがサポートされたそうです。

今まではPartialっぽい動きを実現するには以下の手順が必要でした。

  • ComponentBaseを継承したCode Behind Classを用意
  • .razor側で上記Classを@inheritsで継承

今回のUpdateからは、.razorファイルと同じ名前のClassにpartialを指定するだけになりました。

具体的には以下のようになります。

Counter.razor

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

Counter.cs

namespace BlazorApp.Pages
{
    public partial class Counter
    {
        int currentCount = 0;

        void IncrementCount()
        {
            currentCount++;
        }
    }
}

Pass parameters to top-level components

<component />を使ってRazor Componentの描画ができるようになりました。
これはPreview2で追加されたやつですね。

そして<component />を使えばpara-***でComponentの[Parameter]属性を持つプロパティにパラメータ渡しができます。
最初の頃はパラーメータ渡しは出来なかったんですよね。

以前の書き方

// .NET Core 3.0 Preview8 までの書き方
@(await Html.RenderComponentAsync<BlazorComponent>())

// .NET Core 3.0 Preview9 から.NET Core 3.1 Preview1 までの書き方
@(await Html.RenderComponentAsync<BlazorComponent>(RenderMode.ServerPrerendered))

バージョンアップ後の書き方

// .NET Core 3.1 Preview2 以降の書き方
<component type="typeof(BlazorComponent)" render-mode="ServerPrerendered" param-IncrementAmount="10" />

New component tag helper

上記<component />のことなので省略します。

Prevent default actions for events in Blazor apps

@on{EventName}:preventDefaultでイベントの既定の動作を禁止することができます。

過去にこの記事で触れたやつですね。

blog.piyosi.com

こんな感じに書けるようです。
※下記コードを実行すると、Textboxでキーを押下してもKeyHanderが発火されません。

<input value="@_count" @onkeypress="KeyHandler" @onkeypress:preventDefault />

PreventDefaultって何?って人はMDNとかが参考になるかと思います。

developer.mozilla.org

Stop event propagation in Blazor apps

@on{EVENT}:stopPropagationでイベントの伝播を停止させることができます。

私の過去の記事から引用します。

@oneventname:stopPropagationディレクティブ属性を使用して、Blazorアプリでイベントの伝播を停止します。
次の例では、チェックボックスをオンにすると、子divからのクリックイベントが親に伝播しなくなります。

<input @bind="stopPropagation" type="checkbox" />
<div @onclick="OnClickParentDiv">
    Parent div
    <div @onclick="OnClickChildDiv" @onclick:stopPropagation="stopPropagation">
        Child div
    </div>
</div>

<button @onclick="OnClick">Click me!</button>

@code {
    bool stopPropagation;

    void OnClickParentDiv() => Console.WriteLine("Parent div clicked.");
    void OnClickChildDiv() => Console.WriteLine("Child div clicked.");
}

Detailed errors during Blazor app development

Blazorがより詳細なエラー情報を出力するようになりました。
これはデバッグ実行時にブラウザのConsoleへ例外を吐くのと、画面下部に黄色くバーが表示されるようになった件ですね。

f:id:piyo_esq:20191204065446p:plain
Exception発生時

バーはエラー発生時に<div id="blazor-error-ui"></div>のHtmlを描画するようになっています。
テンプレートに入っているので、エラー発生時の画面のカスタマイズが楽になりました。
※今までは自分で追加する必要がありました。。

以下がそのHtmlです。

<div id="blazor-error-ui">
    <environment include="Staging,Production">
        An error has occurred. This application may no longer respond until reloaded.
    </environment>
    <environment include="Development">
        An unhandled exception has occurred. See browser dev tools for details.
    </environment>
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

Support for shared queues in HttpSysServer

Microsoft Docsから引用します。

HTTP.sysは、匿名要求キューの作成をサポートしています。ASP.NET Core 3.1では、既存の名前付きHTTP.sys要求キューを作成または添付する機能が追加されました。既存の名前付きHTTP.sys要求キューを作成または添付すると、キューを所有するHTTP.Sysコントローラープロセスがリスナープロセスから独立したシナリオが可能になります。この独立性により、リスナープロセスの再起動間で既存の接続とエンキューされた要求を保持できます。

cs public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { // ... webBuilder.UseHttpSys(options => { options.RequestQueueName = "MyExistingQueue"; options.RequestQueueMode = RequestQueueMode.CreateOrAttach; }); });

docs.microsoft.com

Breaking changes for SameSite cookies

Microsoft Docsから引用します。

SameSite Cookieの動作は、今後のブラウザーの変更を反映するように変更されました。これは、AzureAd、OpenIdConnect、WsFederationなどの認証シナリオに影響する場合があります。詳細については、「ASP.NET CoreでSameSite Cookieを使用する」を参照してください。

docs.microsoft.com

おわり

Partial嬉しいです

ASP.NET Core MVCにServer side Blazorのコンポーネントを共存させる話

本エントリはBlazor Advent Calender 2019の4日目の記事です

はじめに

.NET Core 3.0がリリースされてから3か月近く経過しました。
※追記(12/04):本日.NET Core 3.1 LTSがリリースされました!

皆さんはお仕事でBlazor書いてますでしょうか?勿論私は書けていません。
産まれて間もないですし、中々難しいところではあります。
※海外ではBlazorチョットデキル人への求人がちょこちょこ出てきているようですが...。

どうにかBlazorを合法的に使用できないものか?

弊社の場合フルスクラッチでBlazorのSPAを作成するのは政治的な理由で難しく、既存のASP.NET Core MVCにComponent単位で組込む程度が現実的でしょう。
※BlazorのComponent(Razor Componenet)は普通のASP.NET Core MVCでも表示可能なので、SignalRとの通信さえできればMVC上でServer side Blazorできるはず。

というわけで「既存のASP.NET Core MVCをComponent単位でBlazorに置き換えてく作戦」で行きたいと思います。

留意点

とはいえ、手放しにServer side Blazorを組込むことは出来ません。
何個か留意点があります。

  1. SignalRと常時通信が発生するためクライアント側に安定した接続環境が必要となる。

  2. SignalRとの通信タイムアウトやBlazorのエラーハンドリングが必要 ※最新のBlaozrなら<div id="blazor-error-ui"></div>とかが用意されてるので苦ではないかも。

  3. サーバーのスペックとかスケーリングの見積

ちなみに、3番についてはMSの人が多少検証したっぽいです。

devblogs.microsoft.com

上記Blogから引用します。

In our tests, a single Standard_D1_v2 instance on Azure (1 vCPU, 3.5 GB memory) could handle over 5,000 concurrent users without any degradation in latency. A Standard_D3_V2 instance (4 vCPU, 14GB memory) handled well over 20,000 concurrent clients. The main bottleneck for handling further load was available memory.

メモリ量が大事っぽいですね。

では、まずはRazor Componentの表示からやってみましょう。

ASP.NET Core MVC Projectの作成

ベースとなるASP.NET Core MVCプロジェクトを作成します。
※筆者の開発環境は.NET Core 3.1 Preview3です。 ※追記(12/04):本日リリースされた.NET Core 3.1 LTSでOKです!

dotnet new mvc -o BlazorOnMVC
cd BlazorOnMVC

Views/Home/BlazorComponent.razorの作成

Blazorのコンポーネントを作成します。

<h1>Hello @Name!</h1>

<p>This is Blazor Components.</p>

@code {
    [Parameter]
    public string Name {get; set;}
}

Views/Home/Index.cshtmlの編集

作成したRazor Componentを<component />で表示させます。
この辺りは.NET Core 3.1 Preview2で変更が入った書き方ですね。

param-Nameで文字列を引数として渡していますが
param-Name="Blazor"だとerror CS0103: 現在のコンテキストに Blazor という名前は存在しません。と怒られます。
しゃーないのでStringを@("文字列")で括って与えています。

+ @using BlazorOnMVC.Views.Home

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

+ <component type="typeof(BlazorComponent)" render-mode="ServerPrerendered" param-Name="@("Blazor")"/>

ちなみに過去のバージョンでのRazor Componentの表示方法は以下の通りです。

// .NET Core 3.0 Preview8 までの書き方
@(await Html.RenderComponentAsync<BlazorComponent>())

// .NET Core 3.0 Preview9 から.NET Core 3.1 Preview1 までの書き方
@(await Html.RenderComponentAsync<BlazorComponent>(RenderMode.ServerPrerendered))

// .NET Core 3.1 Preview2 からの書き方
<component type="typeof(BlazorComponent)" render-mode="ServerPrerendered" /> 

とりあえず以上です。

Razor Componentが描画されるかどうか、実行してみます。

実行結果

ASP.NET Core MVCでRazor Componentの表示に成功しました。

f:id:piyo_esq:20191202215954p:plain
Razor Component on ASP.NET Core MVC

続いてSignalRと通信するための部分を追加します。

Startup.csの編集

Startup.csを編集します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
+   services.AddServerSideBlazor();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{

    // 途中省略
    
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
+       endpoints.MapBlazorHub();
    });
}

Views/Home/Index.cshtmlの編集

blazor.server.jsを埋め込みます。

_Layout.cshtmlに埋め込んでも良いですが、Razor Componentを使用していないページでもSignalRとの通信が発生してしまいます。 よってRazor Componentを使用するページであるIndex.cshtmlにblazor.server.jsを埋め込みます。

@using BlazorOnMVC.Views.Home
+ <script src="_framework/blazor.server.js" autostart="false"></script>

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<component type="typeof(BlazorComponent)" render-mode="ServerPrerendered" param-Name="@("Blazor")" />

+ <script>
+     window.onload = function() {
+         Blazor.start({
+             configureSignalR: function (builder) {
+                 builder.configureLogging(2);  // 2はLogLevel.Information
+             },
+             logLevel: 2,
+             reconnectionOptions: {
+                 maxRetries: 3,
+                 retryIntervalMilliseconds: 2000,
+             }
+         });
+     }
+ </script>

_Layout.cshtmlならautostart="true"で正常に動作しますが、Index.cshtmlだとうまく動作しませんでした。
仕方ないのでautostart="false"を指定しwindow.onloadのタイミングでSignalRとの通信を開始しています。ライフサイクルの関係でしょうか...。

なおautostartは省略可能です。省略した場合autostart="true"で動作します。
ソース的にはこことかここら辺っぽいですね

LogLevelの指定はこちらをご参照ください。

docs.microsoft.com

BlazorComponent.razorの編集

Razor Componentに手を加えます。
ボタンを押下した回数を画面に表示する処理を追加しました。

注意点としてはMicrosoft.AspNetCore.Components.Webを参照に追加するのを絶対に忘れないでください。
上記参照がない状態だと、ボタンを押下しても@onclickにbindしたメソッドが実行されません。しかもエラーも何も表示されません...😖

+ @using Microsoft.AspNetCore.Components.Web 
<h1>Hello @Name!</h1>

<p>This is Blazor Components.</p>

+ <p>Counter: @Count</p>
+ <button @onclick="IncrementCount">Count++</button>

@code {
    [Parameter]
    public string Name {get; set;}

+   int Count = 0;
+   void IncrementCount()
+   {
+       Count++;
+   }
}

実行結果

dotnet runで起動します。
WebSocket connectedって書いてありますね。

f:id:piyo_esq:20191202215004p:plain
dotnet run

念のためNetworkタブを見たところ、ちゃんとSignalRと通信してるっぽいです。
数秒おきにSignalRとHeartBeatしてるログが表示されています。

f:id:piyo_esq:20191202215027p:plain
HeartBeat Log

Count++ボタンを押下すると画面上のCountの数値がインクリメントされていきます。
Server side Blazorが動きました!

おわり

というわけで、無事ASP.NET Core MVCにServer side Blazor(Razor Component)を乗せることに成功しました。「既存のASP.NET Core MVCをComponent単位でBlazorに置き換えてく作戦」は実現可能っぽいですね。

今回はRazor Componentを組込むだけですが、MapFallbackToPageを使えばMapControllerRouteによるRoutingを保ちつつ一部のページをRazor PageでSPA化もできるのかな?いずれチャレンジしてみたいと思います。。

以上です。

Client side Blazor on WPF WebViewを実験的に作って挫折した話

WPF(.NET Core)のWebView上に、publishしたClient side Blazorを表示するDesktop Appを実験的に作って挫折したのでその備忘録です。
※挫折した、という点がポイントです。

Demo

最低限動くところまでは作ったので、その様子だけ載せておきます。

f:id:piyo_esq:20191123113254g:plain
demo

きっかけ

先日MicrosoftのSteve Sanderson氏が氏のBlogにWebWindow.Blazorなるlibraryの記事を執筆されました。

blog.stevensanderson.com

これはElectronやPWAを使わずにWebアプリをHybrid Desktop App化する話で

  • Windows, Mac, Linuxをサポート

  • サイズがめっちゃ小さく済む

  • メモリめっちゃ少なく済む

って感じの実験的Libraryです。
中身はC++とか使ってるっぽいですね ※詳細は氏のBlogをご参照ください。

この記事を見て、「Windowsだけを対象とすればWPFのWebViewでできるかな~」と何も考えず軽いノリで始めたのがきっかけです。

localのClient side Blazorをブラウザで開く

そもそもClient side Blaozrってlocalのブラウザで開けるのでしょうか。

Client side Blazorをpublishしてindex.htmlをGoogle Chromeで開いてみました。

f:id:piyo_esq:20191123110028p:plain

ファイルのパスがダメっぽいですね。
index.htmlの<base href="/" />を一時的に削除してブラウザを更新します。

f:id:piyo_esq:20191123110048p:plain

やっぱダメですね。

方針

Client side Blazorをlocalのブラウザで開くにはちゃんとサーバ建てないとダメっぽいですね。
仕方がないのでWPF上でASP.NET Coreをホストするやけくそ構成で行きましょう。
※これ、結局ホストするならClient side Blazorである必要がないですよね。しかもWPF上でホストするって。

プロジェクトの作成

とりあえず作っていきます。
※環境は.NET Core 3.1 Preview3です

dotnet new sln -o BlazorOnWebView
cd BlazorOnWebView
dotnet new blazorwasm -o BlazorOnWebView.Blazor
dotnet new wpf -o BlazorOnWebView.WPF
dotnet sln BlazorOnWebView.sln add BlazorOnWebView.Blazor
dotnet sln BlazorOnWebView.sln add BlazorOnWebView.WPF

Client side BlaozrのPublish

先にClient side BlazorをPublishしておきます。

dotnet publish -c Release

これでBlazorOnWebView.Blazor\bin\Release\netstandard2.0\publish\BlazorOnWebView.Blazor\dist以下にClient side BlazorがPublishされました。

上記パスのファイルをBlazorOnWebView.WPF\wwwroot\にコピーしておきます。
※イケてない感が満載ですが、.csprojに以下を追加してpublishしても良いです。

  <Target Name="PostPublish" AfterTargets="AfterPublish">
    <Exec Command="xcopy $(TargetDir)publish\BlazorOnWebView.Blazor\dist\* $(ProjectDir)..\BlazorOnWebView.WPF\wwwroot\ /E /I /Y" />
  </Target>

BlazorOnWebView.WPFへLibraryの追加

BlazorOnWebView.WPFに必要なLibraryを追加します。
WebViewはWPF上でEdgeの表示ができるMicrosoft.Toolkit.Wpf.UI.Controls.WebViewを使用します。
※WebViewはWebView2に置き換えられるのでビルド時に警告が発せられますがひとまず無視します。

cd BlazorOnWebView.WPF
dotnet add package Microsoft.AspNetCore
dotnet add package Microsoft.AspNetCore.Hosting
dotnet add package Microsoft.AspNetCore.SpaServices.Extensions
dotnet add package Microsoft.AspNetCore.StaticFiles
dotnet add package Microsoft.Toolkit.Wpf.UI.Controls.WebView

コーディング

準備完了です。
WPF側コードを編集します。

BlazorOnWebView.WPF/BlazorOnWebView.WPF.csprojの編集

ビルド時にClient side BlazorでpublishしたファイルをBlazorOnWebView.WPF/wwwroot/フォルダにコピーするよう.csprojを編集します。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.0-preview3.19555.2" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
    <PackageReference Include="Microsoft.Toolkit.Wpf.UI.Controls.WebView" Version="6.0.0" />
  </ItemGroup>

+ <ItemGroup>
+   <Content Include="wwwroot\**\*.*">
+     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+   </Content>
+ </ItemGroup>

</Project>

BlazorOnWebView.WPF/MainWindow.xamlの編集

xamlにWebViewを追加します。
IsPrivateNetworkClientServerCapabilityEnabledを指定することでLocalHostのURLにもアクセスできるようになるそうです。

<Window x:Class="BlazorOnWebView.WPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BlazorOnWebView.WPF"
+        xmlns:Controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls.WebView"
        mc:Ignorable="d"
+       Closing="Window_Closing"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
+       <Controls:WebView x:Name="WebView" Source="https://localhost:5001/" IsPrivateNetworkClientServerCapabilityEnabled="True" />
    </Grid>
</Window>

BlazorOnWebView.WPF/MainWindow.xaml.cs

コードビハインドにASP.NET CoreでWebサーバをホストする処理を追加します。

本当はちゃんとサーバが起動してからWebViewの遷移を開始するべきですが、割愛します。

using System.IO;
using System.Windows;
+ using Microsoft.AspNetCore;
+ using Microsoft.AspNetCore.Hosting;

namespace BlazorOnWebView.WPF
{
    public partial class MainWindow : Window
    {
+       CancellationTokenSource cts = new CancellationTokenSource();
        public MainWindow()
        {
            InitializeComponent();

+           WebHost.CreateDefaultBuilder()
+                   .UseStartup<Startup>()
+                   .Build().RunAsync(cts.Token);
        }

+       private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+       {
+           cts.Cancel();
+       }
    }
}

BlazorOnWebView.WPF/Startup.cs

Startupクラスを新規作成します。
Webサーバの設定ですね。

Client side Blazorが使用する_framework/_bin/**.dllはデフォルトだとMIMEがtext/htmlか何かになってた気がするので application/x-msdownloadに変更します。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using System.IO;

namespace BlazorOnWebView.WPF
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSpaStaticFiles(opt => opt.RootPath = "wwwroot");
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            var provider = new FileExtensionContentTypeProvider();
            provider.Mappings[".dll"] = @"application/x-msdownload";
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "_framework", "_bin")),
                RequestPath = "/_framework/_bin",
                ContentTypeProvider = provider
            });

            app.UseSpaStaticFiles();
            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "wwwroot";
            });
        }
    }
}

checknetisolationの設定

ここでdotnet runしたものの、画面が真っ白で何も表示されませんでした。
ぐぐってみると、以下のissueを発見しました。

github.com

上記issueに記載されたコマンドを管理者権限で実行します。

checknetisolation LoopbackExempt -a -n="Microsoft.Win32WebViewHost_cw5n1h2txyewy"

f:id:piyo_esq:20191123110133p:plain
checknetisolation

実行

BlazorOnWebView.WPFでdotnet runします。

f:id:piyo_esq:20191123113254g:plain
demo

おわり

WebViewはchecknetisolationの件があるので配布は厳しいですね。
詳細は調べていませんが、CefSharpを使えばなんとかなるのかな?
それかChromium版Edgeが動くWebView2を使うとか(こっちはC++ですが。)

WebViewのキャッシュが残るようで、wwwrootの中身を更新してもなかなかWPF上に反映されない点が辛かったです。

というわけで、実用性のないClient side Blaozr on WPF(WebView)ができました。
やっぱりSteve Sanderson氏のようなスーパーエンジニアはすごいなぁ...。

.NET CoreのWPFでWindowを表示するClass Libraryを作成した

実行するとWPFのWindowを表示するClass Libraryを作成しました。

これはClient side Blazorをいい感じにWPFのWebViewで表示できないか試行錯誤してた最中の副産物です。結局ボツ案で使うことは無いとは思いますが、せっかく作ったのでここで供養させてもらいます...
※そもそもアプローチの方向が全然違うでしょうに

プロジェクトの作成

ClassLibraryはWindowLib
呼び出し側はConsoleAppという名前のConsole Applicationを作成します。

ちなみに開発環境は.NET Core 3.1 Preview3です。

mkdir WindowLib\src
cd WindowLib\src
dotnet new sln -n WindowLib
dotnet new console -o ConsoleApp
dotnet new classlib -o WindowLib
dotnet sln add WindowLib.sln ConsoleApp WindowLib
dotnet add .\ConsoleApp\ reference .\WindowLib\

Library側の作成

Library側を作っていきます。

WindowLib.csproj

WPFを扱うのでSDKを変更しUseWPFを指定します。

WPFは.NET Core3.0(netstandard2.1)以上から対応しているので、TargetFrameworkもそこら辺の値に変更します。

ビルド時にMainWindow.xamlをコピーするようContentで指定します。

- <Project Sdk="Microsoft.NET.Sdk">
+ <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
-   <TargetFramework>netstandard2.0</TargetFramework>
+  <TargetFramework>netcoreapp3.1</TargetFramework>
+   <UseWPF>true</UseWPF>
  </PropertyGroup>

+ <ItemGroup>
+   <Content Include="MainWindow.xaml">
+     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+   </Content>
+ </ItemGroup>
</Project>

MainWindow.xaml

とりあえずのWindowを作成します。

<Window
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
       xmlns:local="clr-namespace:WindowLib"
       mc:Ignorable="d"
       Title="MainWindow" Height="450" Width="800">
    <TextBlock Text="Hello WPF World."></TextBlock>
</Window>

Class1.cs

STAThreadで動作させます。
MainWindow.xamlを読み込んでWindowクラスにキャストして起動するだけです。 ※ファイル名・クラス名は適宜変えてください...

using System;
using System.IO;
using System.Threading;
using System.Windows;
using System.Xml;
using System.Windows.Markup;
using System.Reflection;

namespace WindowLib
{
    public class Class1
    {
        [STAThread]
        public static void Run()
        {
            var th = new Thread(new ThreadStart(runApp));
            th.SetApartmentState(ApartmentState.STA);
            th.Start();
        }

        private static void runApp()
        {
            Application app = new Application();
            Window window = new Window();

            var filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "MainWindow.xaml");
            using (FileStream infs = new FileStream(filePath, FileMode.Open))
            {
                XmlReader xmlReader = XmlReader.Create(infs);
                window = (Window)XamlReader.Load(xmlReader);
            }

            app.Run(window);
        }
    }
}

呼び出し側の作成

WindowLibを使う側のアプリであるConsoleAppのコードです。

Program.cs

呼び出すだけです

using System;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
-           Console.WriteLine("Hello World!");
+           WindowLib.Class1.Run();
        }
    }
}

以上で完成です。

実行

ファイル構成はこんな感じです。

f:id:piyo_esq:20191121211253p:plain
ファイル構成

ConsoleAppのディレクトリに移動してdotnet runで実行します。

f:id:piyo_esq:20191121211311p:plain
実行結果

;csprojにContentを記載したおかげでdotnet publish -c Releaseで発行すると bin\Release\netcoreapp3.1\publishにMainWindow.xamlがしっかり出力されていますね。

f:id:piyo_esq:20191121211327p:plain
publishフォルダ

おわり

供養完了(なむなむ

.NET Core 3.1 Preview3がリリースされました

.NET Core 3.1 Preview 3が公開されました。

devblogs.microsoft.com

記事内で12月上旬に.NET Core 3.1 LTSリリース予定って言ってますね!
※もともとは11月だったのにサラッと伸びてる

Visual Studio 16.4 Preview 5およびVisual Studio for Mac 8.4 Preview 5も本日リリースされるとのこと。

以下記事から引用

.NET Core 3.0の初期ダウンロード数は、予想をはるかに超えています。.NET Coreエコシステムの80〜90%(またはそれ以上)は、リリースの最初の6か月以内に.NET Core 3.1に移行すると推測されます。多くの改善(主に3.0による)があり、最新のLTSリリースであるため、できるだけ早く3.1リリースに移行することをお勧めします。

だそうです(機械翻訳)

ASP.NET Coreの変更点

devblogs.microsoft.com

今回はBug Fixが主だそうです。

Release Noteはこちら。

ASP.NET Core bugs

github.com

ASP.NET Core new features

github.com

  • SPAのTemplateが更新(Angular, React, ReactRedux)

  • Blazorのpartial classがVisual Studio Codeでちゃんと動くようにした(?)

Blazorの変更がよくわからないですね。
O#(OmniSharpのこと?)とRazor言語サーバに手を入れたって書いてあるので、VSCodeのインテリセンスがいい感じに動くようになったってことなのかな?

おわり

LTSまであと2週間ちょい?楽しみですね。