WebSurfer's Home

Filter by APML

.NET アプリで Shift_JIS を使うには?

by WebSurfer 14. March 2025 13:57

.NET アプリにおいて Encoding.GetEncoding メソッドで Shift_JIS のエンコーディングを取得しようとすると、以下の画像のように ArgumentException 例外がスローされます。

Shift_JIS エンコーディングの取得

これは、.NET では特定の OS に依存する機能はサポートされてないので、Shift_JIS に限らず Windows OS のコードページの中の特定の locale のエンコーディングはデフォルトでは使えないということのようです。(注: .NET Framework ではデフォルトで Windows コードページの大多数のエンコーディングが使えます)

サポートされているエンコーディングは Encoding.GetEncodings メソッドで取得できます。.NET 8.0 の場合、自分の環境で試すと以下の 7 種類になっていました。

  • ASCII (code page 20127)
  • UTF-8 (code page 65001)
  • UTF-16LE (code page 1200)
  • UTF-16BE (code page 1201)
  • UTF-32LE (code page 12000)
  • UTF-32BE (code page 12001)
  • ISO-8859-1 (code page 28591)

UTF-7 も含まれるというMicrosoft のドキュメントを目にしましたが "The UTF-7 encoding is insecure and should not be used." ということで除外されたようです。

では、.NET アプリで上の一覧にないエンコーディングを Encoding.GetEncoding メソッドで取得して使うにはどうするかですが、それについては Microsoft のドキュメント「EncodingProvider クラス」に説明があります。それを参考に以下に Shift_JIS を使う場合の例を書きます。

(1) プロバイダクラスを定義

抽象クラス EncodingProvider を継承したプロバイダクラスを定義します。この例では、プロバイダクラスの名前を ShiftJisEncodingProvider とします。

ShiftJisEncodingProvider クラスで、 継承した抽象クラス EncodingProvider の抽象メソッド GetEncoding(Int32) と GetEncoding(String) を override し、Shift_JIS エンコーディングを返すようにします。

Shift_JIS エンコーディングは CodePagesEncodingProvider クラスのインスタンスから取得します。

ShiftJisEncodingProvider クラスのコード例は以下の通りです。

using System.Text;

public class ShiftJisEncodingProvider : EncodingProvider
{
    public override Encoding? GetEncoding(int codepage)
    {
        var provider = CodePagesEncodingProvider.Instance;
        if (codepage == 932)
        {            
            return provider.GetEncoding(932);
        }

        return null;
    }

    public override Encoding? GetEncoding(string name)
    {
        var provider = CodePagesEncodingProvider.Instance;
        if (name.Equals("shift_jis", StringComparison.CurrentCultureIgnoreCase))
        {                
            return provider.GetEncoding("shift_jis");
        }
        
        return null;
    }
}

(2) ShiftJisEncodingProvider の登録

ShiftJisEncodingProvider のインスタンスを Encoding.RegisterProvider メソッド に渡して、.NET アプリで Shift_JIS エンコーディングを取得できるようにします。

(3) Shift_JIS エンコーディングの取得

.NET アプリでは Encoding.GetEncoding メソッドを使って Shift_JIS エンコーディングを取得します。ステップ (2) と (3) のコード例は以下の通りです。コメントの結果に示すように期待通り Shift_JIS エンコーディングが取得できています。

using System.Text;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var provider = new ShiftJisEncodingProvider();
            Encoding.RegisterProvider(provider);

            var sjis = Encoding.GetEncoding("shift_jis");
            Console.WriteLine($"{sjis.EncodingName}\t{sjis.CodePage}\t{sjis.WebName}");

            var str = "123abcあいう";
            Console.Write($"{str}: ");

            byte[] bytes = sjis.GetBytes(str);
            foreach (byte b in bytes)
            {
                Console.Write($"[{b:x2}]");
            }            
        }
    }
}

// 結果:
// Japanese (Shift-JIS)    932     shift_jis
// 123abcあいう: [31][32][33][61][62][63][82][a0][82][a2][82][a4]

上で述べたようなカスタムプロバイダクラスを定義しなくても Windows コードページのエンコーディングを簡単に全部使えるようにする方法もあります。

その方法は、Microsoft のドキュメント「CodePagesEncodingProvider クラス」に説明があります。

  1. CodePagesEncodingProvider.Instance 静的プロパティから CodePagesEncodingProvider オブジェクトを取得します。
  2. CodePagesEncodingProvider オブジェクトを Encoding.RegisterProvider メソッドに渡します。

コード例は以下の通りで一行で済みます。必要ないエンコーディングまで使えるようにすることで何か予期せぬ副作用が出るかもしれないという可能性は否定しきれませんが、こちらのが本筋かもしれません。

using System.Text;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 以下の一行で OK
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

            var sjis2 = Encoding.GetEncoding("shift_jis");
            Console.WriteLine($"{sjis2.EncodingName}\t{sjis2.CodePage}\t{sjis2.WebName}");

            Console.Write($"{str}: ");

            byte[] bytes2 = sjis2.GetBytes(str);
            foreach (byte b in bytes2)
            {
                Console.Write($"[{b:x2}]");
            }

            Console.WriteLine("\n\nサポートしているすべてのエンコーディングを取得する");
            EncodingInfo[] eis = Encoding.GetEncodings();
            foreach (EncodingInfo ei in eis)
            {
                Console.WriteLine($"{ei.DisplayName}\t{ei.CodePage}\t{ei.Name}");
            }
        }
    }
}

/* 結果
Japanese (Shift-JIS)    932     shift_jis
123abcあいう: [31][32][33][61][62][63][82][a0][82][a2][82][a4]

サポートしているすべてのエンコーディングを取得する
Japanese (Shift-JIS)    932     shift_jis
Portuguese (DOS)        860     IBM860
Icelandic (DOS) 861     ibm861
IBM EBCDIC (Cyrillic Russian)   20880   IBM880
Hebrew (DOS)    862     DOS-862
French Canadian (DOS)   863     IBM863
Chinese Simplified (GB2312)     936     gb2312
Arabic (864)    864     IBM864
Nordic (DOS)    865     IBM865
Cyrillic (DOS)  866     cp866
Cyrillic (KOI8-U)       21866   koi8-u
IBM EBCDIC (US-Canada)  37      IBM037
Greek, Modern (DOS)     869     ibm869
IBM EBCDIC (International)      500     IBM500
Icelandic (Mac) 10079   x-mac-icelandic
IBM EBCDIC (US-Canada-Euro)     1140    IBM01140
IBM EBCDIC (Germany-Euro)       1141    IBM01141
IBM EBCDIC (Denmark-Norway-Euro)        1142    IBM01142
IBM EBCDIC (Germany)    20273   IBM273
IBM EBCDIC (Finland-Sweden-Euro)        1143    IBM01143
IBM EBCDIC (Italy-Euro) 1144    IBM01144
IBM EBCDIC (Spain-Euro) 1145    IBM01145
Central European (Windows)      1250    windows-1250
IBM EBCDIC (UK-Euro)    1146    IBM01146
Cyrillic (Windows)      1251    windows-1251
IBM EBCDIC (France-Euro)        1147    IBM01147
Western European (Mac)  10000   macintosh
Western European (Windows)      1252    windows-1252
Arabic (DOS)    720     DOS-720
IBM EBCDIC (Denmark-Norway)     20277   IBM277
IBM EBCDIC (International-Euro) 1148    IBM01148
Japanese (Mac)  10001   x-mac-japanese
Greek (Windows) 1253    windows-1253
OEM United States       437     IBM437
IBM EBCDIC (Finland-Sweden)     20278   IBM278
IBM EBCDIC (Icelandic-Euro)     1149    IBM01149
Chinese Traditional (Mac)       10002   x-mac-chinesetrad
Turkish (Windows)       1254    windows-1254
Hebrew (Windows)        1255    windows-1255
Korean (Johab)  1361    Johab
Arabic (Windows)        1256    windows-1256
Arabic (Mac)    10004   x-mac-arabic
Baltic (Windows)        1257    windows-1257
Hebrew (Mac)    10005   x-mac-hebrew
Vietnamese (Windows)    1258    windows-1258
Greek (Mac)     10006   x-mac-greek
Cyrillic (Mac)  10007   x-mac-cyrillic
IBM Latin-1     20924   IBM00924
Central European (ISO)  28592   iso-8859-2
Latin 3 (ISO)   28593   iso-8859-3
Baltic (ISO)    28594   iso-8859-4
Cyrillic (ISO)  28595   iso-8859-5
Arabic (ISO)    28596   iso-8859-6
IBM EBCDIC (Multilingual Latin-2)       870     IBM870
Greek (ISO)     28597   iso-8859-7
Hebrew (ISO-Visual)     28598   iso-8859-8
Turkish (ISO)   28599   iso-8859-9
Turkish (Mac)   10081   x-mac-turkish
Croatian (Mac)  10082   x-mac-croatian
Thai (Windows)  874     windows-874
IBM EBCDIC (Greek Modern)       875     cp875
IBM EBCDIC (Arabic)     20420   IBM420
Korean  949     ks_c_5601-1987
IBM EBCDIC (Greek)      20423   IBM423
IBM EBCDIC (Hebrew)     20424   IBM424
IBM EBCDIC (Italy)      20280   IBM280
IBM Latin-1     1047    IBM01047
IBM EBCDIC (Spain)      20284   IBM284
IBM EBCDIC (UK) 20285   IBM285
Romanian (Mac)  10010   x-mac-romanian
Japanese (JIS 0208-1990 and 0212-1990)  20932   EUC-JP
Ukrainian (Mac) 10017   x-mac-ukrainian
Europa  29001   x-Europa
Greek (DOS)     737     ibm737
Western European (IA5)  20105   x-IA5
Chinese Traditional (Big5)      950     big5
Chinese Simplified (GB2312-80)  20936   x-cp20936
German (IA5)    20106   x-IA5-German
Swedish (IA5)   20107   x-IA5-Swedish
Norwegian (IA5) 20108   x-IA5-Norwegian
Cyrillic (KOI8-R)       20866   koi8-r
Baltic (DOS)    775     ibm775
Estonian (ISO)  28603   iso-8859-13
IBM EBCDIC (Japanese katakana)  20290   IBM290
Latin 9 (ISO)   28605   iso-8859-15
Chinese Traditional (CNS)       20000   x-Chinese-CNS
Arabic (ASMO 708)       708     ASMO-708
IBM EBCDIC (France)     20297   IBM297
Thai (Mac)      10021   x-mac-thai
TCA Taiwan      20001   x-cp20001
IBM EBCDIC (Turkish)    20905   IBM905
Chinese Traditional (Eten)      20002   x-Chinese-Eten
IBM EBCDIC (Korean Extended)    20833   x-ebcdic-koreanextended
IBM5550 Taiwan  20003   x-cp20003
TeleText Taiwan 20004   x-cp20004
Wang Taiwan     20005   x-cp20005
Western European (DOS)  850     ibm850
IBM EBCDIC (Thai)       20838   IBM-Thai
Central European (DOS)  852     ibm852
IBM EBCDIC (Icelandic)  20871   IBM871
Central European (Mac)  10029   x-mac-ce
OEM Cyrillic    855     IBM855
IBM EBCDIC (Cyrillic Serbian-Bulgarian) 21025   cp1025
Korean Wansung  20949   x-cp20949
Turkish (DOS)   857     ibm857
OEM Multilingual Latin I        858     IBM00858
T.61    20261   x-cp20261
IBM EBCDIC (Turkish Latin-5)    1026    IBM1026
ISO-6937        20269   x-cp20269
Unicode 1200    utf-16
Unicode (Big-Endian)    1201    utf-16BE
Unicode (UTF-32)        12000   utf-32
Unicode (UTF-32 Big-Endian)     12001   utf-32BE
US-ASCII        20127   us-ascii
Western European (ISO)  28591   iso-8859-1
Unicode (UTF-8) 65001   utf-8
*/

Tags: , , ,

CORE

HttpClient の同時接続数

by WebSurfer 21. December 2024 14:02

HTTP 1.1 仕様では同時接続数は 2 つまでとなっているそうで (過去の話らしい)、それに準じて .NET Framework 4.8 の HttpWebRequest を使ったアプリでも同時接続数は 2 つまでの制限がかかります。(要求先が localhost では無制限となります。詳しい話は先の記事「HttpWebRequest の localhost への同時接続数」を見てください)

その同時接続数の制限が、この記事を書いた時点での最新ターゲットフレームワーク .NET 9.0 の HttpClient を使ったアプリではどうなるかを調べたというのがこの記事の話です。

実は、後になって気が付いたのですが、Microsoft のドキュメント「System.Net.Http.HttpClient クラス」の「接続のプール」のセクションの「注意」に、

"同時接続の数を制限するには MaxConnectionsPerServer プロパティを設定します。既定では同時 HTTP/1.1 接続の数は無制限です"

・・・と書いてあって、わざわざコードを書いたりして調べるまでもなかったです。でも、せっかく調べたので、調べたことを以下に書いておきます。

surferonwww.info の場合

上の画像は、自分が使っているホスティングサービスの ASP.NET Web Forms アプリに、要求を受けて 10 秒後に Hello World という文字列を返す HTTP ジェネリックハンドラを作り、それに対して HttpClient を使ったマルチスレッドアプリから同時に 8 つの要求を出し、その応答を表示したものです。(同時要求 8 は検証に使った PC のコア数です。その数まではスレッドプールから一気にスレッドを取得できるそうです)

異なるスレッドで 8 つの要求が同時にサーバーに送信され、10 秒後に 8 つの応答が同時にクライアントに返されています。すなわち同時接続数の制限はされてないという結果でした。

ちなみに、HttpWebRequest のように同時接続数が 2 つまでという制限がかかると、2 を超えた分は前回の応答が返ってきてからでないとクライアントから要求が出ず、2 要求毎にサーバーでの処理時間の 10 秒ずつ待たされ、8 つ要求出した場合は全部終わるのに 40 秒かかります。

以下に検証に使った .NET 9.0 の HttpClient のコンソールアプリのコードを載せておきます。Visual Studio 2022 のテンプレートで作ったものです。

namespace ConsoleAppLocalhost
{
    internal class Program
    {
        static readonly HttpClient client = new();

        static async Task Main(string[] args)
        {
            // ホスティングサービスの IIS で動く ASP.NET Web Forms
            // アプリの HTTP ジェネリックハンドラ。
            // 要求を受けて 10 秒後に Hello World という文字列を返す
            var uri = "https://example.com/xxxxxxxx";

            var tasks = new List<Task>();

            // 同じ URL を 5 回同時に要求する
            foreach (var i in Enumerable.Range(0, 5))
            {
                var task = Task.Run(async () =>
                {
                    // ThreadId と開始時刻
                    int id = Thread.CurrentThread.ManagedThreadId;
                    string start = $" / ThreadID = {id}, " +
                                   $"start: {DateTime.Now:ss.fff}, ";

                    using HttpResponseMessage response = await client.GetAsync(uri);
                    response.EnsureSuccessStatusCode();
                    string responseBody = await response.Content.ReadAsStringAsync();
                    

                    // 終了時刻
                    string end = $"end: {DateTime.Now:ss.fff}";
                    responseBody += start + end;

                    Console.WriteLine(responseBody);
                });

                tasks.Add(task);
            }

            await Task.WhenAll(tasks);

            Console.WriteLine("Finish");
            Console.ReadLine();
        }
    }
}

Tags: ,

CORE

Blazor Web App の WebAssembly モード

by WebSurfer 17. November 2024 18:15

Visual Studio 2022 を使って下の画像のように Blazor Web App をテンプレートに選んで作成した Blazor アプリは、予想に反して WebAssembly モードでも Server-Side Rendering (SSR) になることがありました。以下にそのことを書きます。

Blazor Web App を選択

作成時に [Interactive render mode] を [WebAssembly] に設定しても、[Interactive location] に [Per page/component] を選んだ場合(多くの人はこちらを選ぶのではなかろうかと思います)、

[WebAssembly], [Per page/component] に設定

アプリを起動して [Home], [Counter], [Weather] ボタンをクリックして画面を切り替えると、

アプリを起動

下の Fiddler によるキャプチャ画像の通り、

Fiddler によるキャプチャ画像

毎回サーバーに要求を出し、完全な html ソースをサーバーから応答として受け取り、それをブラウザに表示するという SSR になります。WebAssembly だからサーバーとのやり取りはしない、即ち CSR になることを期待していましたがそうはなりません。

Microsoft のドキュメント ASP.NET Core Blazor render modes(日本語版もありますが翻訳がアレなので英語版がお勧め)の Render modes のセクションに説明があります。

そこにはコンポーネントで @rendermode を InteractiveWebAssembly に設定すると Client-side rendering (CSR) using Blazor WebAssembly となって Render location が Client になると書いてあります。

なので、試しに Home, Counter, Weather 全てのコンポーネントで @rendermode InteractiveWebAssembly を設定してみました。しかし、相変わらずサーバとのやり取りを行う SSR になります。

プロジェクトを作成する際に [Interactive location] を [Global] に設定すればサーバーとのやり取りはなくなり CSR になります。コンポーネントに @rendermode InteractiveWebAssembly を設定する必要はないようで、自動生成される各コンポーネントには @rendermode の設定は含まれていません。

知ってました? 自分は知らなかったです。 [Interactive render mode] を [WebAssembly] に設定するだけで無条件で CSR になると思ってました。WebAssembly のメリットは CSR のはずなので、勝手に知らないうちに SSR になっては困ると思うのですが・・・

ちなみに、テンプレートに Blazor WebAssembly を使って作成した Blazor アプリの場合は [Home], [Counter], [Weather] ボタンをクリックして画面を切り替えてもサーバーとの通信は発生せず CSR になります。


そのあたりのことを書いた Microsoft のドキュメントは見つからなかったので Copilot に聞いてみました。回答を載せておきます。(ホントかどうかの裏は取ってませんのでご参考まで)

(1) Blazor Web App と Blazor WebAssembly の違いに関して

The difference you're seeing between the two cases is indeed a bit subtle but has to do with how the Blazor Web App and Blazor WebAssembly templates are configured and what each is optimized for.

Blazor Web App

Interactive Render Mode: When you set the interactive render mode to WebAssembly but also have it set to Per page/component, Blazor is likely configured to request the server to render the pages/components interactively. This is why you're seeing GET requests being made to the server.

Behavior: Even though WebAssembly is being used, each time you navigate, a new GET request is made to the server. This means the server is still involved in delivering the HTML content, and the WebAssembly part is handling the interactive functionalities of the individual components on the page. This can be useful if you want the server to control the navigation and then WebAssembly to handle the client-side interactivity.

Blazor WebAssembly

Pure Client-Side Rendering: This template is optimized for a purely client-side experience. Once the initial application is loaded, all subsequent navigation and component rendering are handled entirely by the client without additional requests to the server.

Behavior: This makes it behave like a traditional Single Page Application (SPA) where the entire application, including navigation between pages, is handled within the client browser using WebAssembly. This is why you're not seeing additional requests to the server when navigating between Home, Counter, and Weather.

To summarize, the main difference is in how much the server is involved in rendering pages. In the Blazor Web App with interactive mode set to WebAssembly and per page/component, the server still plays a role in serving up pages, whereas, in Blazor WebAssembly, once the application is loaded, the server is not involved in further page rendering. This setup can affect performance and user experience depending on your application's needs.


(2) [Interactive location] を [Global] に設定すると何が変わるか

You're right, changing the Interactive location to Global in a Blazor Web App with the WebAssembly interactive render mode would make it behave similarly to the pure Blazor WebAssembly app, where no additional server requests are made during navigation. This setup alters the application's structure in a way that makes the WebAssembly component responsible for rendering more globally, not just per page/component.

Configuration Differences

Project Structure:

Global Interactive Location: This mode places components like Home.razor and Weather.razor under the .Client project, indicating that the client-side WebAssembly is handling the rendering globally for all these components.

Per Page/Component Interactive Location: In this mode, each page or component might still be interacting with the server for rendering purposes, leading to the behavior you observed with additional server requests.

Component Loading:

Global Interactive Location: The client-side WebAssembly loads the components globally, so once the application is loaded, navigation between different pages like Home, Counter, and Weather is handled entirely on the client side.

Per Page/Component Interactive Location: Each navigation may involve a server request because the rendering is done on a per-component basis, making the server more involved in the process.

Potential Reasons for Similar Program.cs

Shared Configuration: The Program.cs file might not differ significantly because the core configuration and service registrations for Blazor WebAssembly applications are quite similar. The main difference lies in how the application components are managed and rendered within the project structure and specific component files.

Conclusion

By setting the interactive location to Global, you've essentially optimized the application to function more like a typical SPA where the client-side WebAssembly handles most of the rendering and navigation logic, reducing the need for server interaction during page changes. This change primarily affects how components are structured and rendered rather than altering the fundamental configuration in Program.cs.

Tags: , , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  April 2025  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar