WebSurfer's Home

Filter by APML

ASP.NET Web アプリ開発用サーバー証明書の更新

by WebSurfer 4. April 2025 13:00

Visual Studio Community 2022 Version 17.13.5 で ASP.NET Web アプリを IIS Express 上で動かそうとしたら、開発用のサーバー証明書が期限切れとなっていたので更新したのですが、一筋縄ではいかなかったので、その時の顛末を書いておきます。

開発用のサーバー証明書

上の画像の通り、開発用のサーバー証明書には 2 種類あって、フレンドリ名 IIS Express Development Certificate が IIS Express 用、ASP.NET Core HTTPS development certificate が Kestrel 用です。いずれも自己署名証明書で、Visual Studio で作業を行う際に生成されます。

ちなみに上の画像の証明書は今日 2025/4/4 に新規作成したもので、有効期限は作成日から IIS Express Development Certificate が 5 年、ASP.NET Core HTTPS development certificate が 1 年となっています。

有効期限が切れたら Visual Studio が自動的に更新してくれると思っていたのですが、少なくとも Visual Studio Community 2022 Version 17.13.5 ではそうではなかったということで、どのように対応したかをこのブログに備忘録として書いておくことにした次第です。

古い証明書が残ったままではダメなのかと思って、上の画像の管理コンソールの「信頼されたルート証明書」の「証明書」にある期限切れの IIS Express Development Certificate を削除してから Visual Studio 2022 で ASP.NET Web アプリを起動して IIS Express で動かしてみました。しかしながら、新しい証明書が発行されることはなかったです。さらに、古い証明書は削除済みにもかかわらず、ブラウザ上にはそれを使っているという警告が出ます。Visual Studio 2022 を再起動してから試してみたのですが同じでした。

2025/4/10 追記: 今頃気づいたのですが、期限切れの証明書は削除したのにブラウザ上にはそれを使っているという警告が出るのは、どこかで証明書情報がキャッシュされているからという可能性が高そうです。管理コンソールで期限切れの証明書を削除した後、まずはブラウザのキャッシュの削除と「SSL 状態のクリア(S)」(下の画像参照) を行うべきだったかもしれません。

SSL 状態のクリア

Visual Studio 2022 の不具合かと思って、Visual Studio Community 2019 Version 16.11.45 を起動して試してみました。すると、Microsoft のチュートリアルの「アプリを実行する」のセクションにあるダイアログ(下の画像参照)が出てきたので[Yes]ボタンをクリックして証明書を作成、

確認ダイアログ

Security Warning

・・・できたと思ったのですが、状況は変わらなかったです。

Visual Studio 2022 で ASP.NET Web アプリを起動して IIS Eaxpress で動かしてみると、依然として、ブラウザ上には管理コンソールで削除した期限切れの証明書を使っているという警告が出ます。Visual Studio 2019 と 2015 でも試してみましたが状況は同じでした。管理コンソールで「信頼されたルート証明書」の「証明書」の中身を見ると、削除した期限切れの証明書は存在しませんし、上の Visual Studio 2019 の操作で発行されたと思った新しい証明書もありませんでした。

ググってヒットしたネットの記事を見ると C:\Program Files (x86)\IIS Express フォルダにある IisExpressAdminCmd.exe コマンドを使って対応するという記事を目にしました。調べてみましたがそのコマンドの Microsoft のドキュメントは見つかりませんでしたので、実行結果がどうなるのか詳しいことが分かりません。

そういう訳の分からないコマンドを実行する勇気は自分にはありませんでしたので、さらにググって調べてみると、stackoverflow の記事 How do I restore a missing IIS Express SSL Certificate? の回答に IIS Express を修復して解決したとありましたので試してみました。(回答に "I typically run appwiz.cpl to launch the old control panel applet and run repair from there." とありますが、appwiz.cpl を起動するのは自分の Windows 10 では「コントロールパネル」から「プログラムと機能」を立ち上げたのと同じです)

IIS Express の修復

その後で ASP.NET Web アプリを Visual Studio 2022 から IIS Express 上で実行してみました。やはり、ブラウザ上には証明書が期限切れという警告が出ました。しかしながら、ブラウザに表示された証明書の有効期限は、当初のホントに期限切れとなっていた証明書の日付とは異なり、たぶん Visual Studio 2019 で作業した時に再生されたと思われる新証明書の有効期限 2030/04/04 となっていました。なぜ、ブラウザは有効期限が 2030/04/04 と 5 年も先と認識しているのに、期限切れという警告を出したのか謎です。

Visual Studio 2022 を再起動して試してみると、今度はブラウザ上では警告なしで目的のページが表示されました。管理コンソールで「信頼されたルート証明書」の「証明書」の中身を見ると、この記事の一番上の画像の通り有効期限が 2030/04/04 の IIS Express Development Certificate が含まれていました。

IIS Express Development Certificate は IIS Express が発行するとのことですが、IIS Express に証明書を発行させて所定の証明書ストアに配置し警告を出さずに動くようにするのは Visual Studio の仕事のようです。何故かその連携がうまく行ってなかったのが、IIS Express の修復で解決できたのでしょうか?

一体どうなっているのかは自分には分かりません。これ以上深く追求する気力はありませんので、次回同様な IIS Express Development Certificate 期限切れ問題が出たら、上に書いたことを参考に解決に当たりたいと思っています。


ちなみにですが、Visual Studio 2022 のテンプレートを使って ASP.NET Core アプリのプロジェクトを作成すると、現在のところデフォルトでは Kestrel 上で動くように設定されています。

アプリを IIS Express 上で動かすには下の画像のように Visual Studio のドロップダウンメニューで IIS Express を選びます。

Web サーバーの選択

それにより、IIS Express を使ったインプロセスホスティングモデルで動くようになります。

IIS Express の設定


もう一つ、Kestrel 用の証明書 ASP.NET Core HTTPS development certificate の更新については、たぶん Visual Studio が自動的にやってくれると思いますが、もし自動更新がされない場合は、先の記事「dotnet dev-certs https コマンドについて」に書いたとおり、dev-certs https コマンドを実行して更新できます。

先の記事の手順、(2) dotnet dev-certs https --clean 実行、(4) dotnet dev-certs https 実行、(6) dotnet dev-certs https --trust 実行で新規作成したのがこの記事に一番上の画像にある ASP.NET Core HTTPS development certificate です。

Tags: , , ,

DevelopmentTools

.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

Web API に Blob をアップロード

by WebSurfer 4. February 2025 12:28

ASP.NET Core Web API で、ブラウザから fetch や axios を使って multipart/form-data 形式でアップロードされてきた Blob データをどのように取得できるかということを書きます。

axios のドキュメント Multipart Bodies のサンプルコード(抜粋下記)を見て、そこに書いてある Blob データを Web API でどのように取得できるかを考えたのがきっかけです。

const form = new FormData();
form.append('my_field', 'my value');
form.append('my_buffer', new Blob([1,2,3]));
form.append('my_file', fileInput.files[0]);

上のコードの FormData を、ブラウザから JavaScript の fetch や axios を使って multipart/form-data 形式で送信すると、ボディ部分は下のようになります (Fiddler によるキャプチャ画像)。2 つ目の name が my_buffer となっているパートが Blob データです。

Fiddler によるキャプチャ画像

上のように送信されてきた my_buffer の Blob データは、ASP.NET Core Web API のアクションメソッドの引数の型を IFormFile 型とすれば取得できました。

具体的には、Web API プロジェクトで以下のクラスを定義し、

public class Blob
{
    public string? My_field { get; set; }
    public IFormFile? My_buffer { get; set; }
    public IFormFile? My_file { get; set; }
}

それをアクションメソッドの引数に設定して、JavaScript の fetch や axios を使って FormData をアクションメソッドに POST 送信すれば、下の画像の通り My_buffer プロパティに Blob を取得できます。

My_buffer プロパティに Blob を取得

MDN のドキュメント Blob に、

"File インターフェイスは Blob をベースにしており、 Blob の機能を継承してユーザーのシステム上のファイルをサポートするように拡張しています"

・・・と書いてあるとおり、input type="file" を使ってのファイルと同様の扱いになるということのようです。

最初、バイト配列として取得できるのではと思って、上の Blob クラスの My_buffer プロパティの型を byte[]? として試してみたのですが、バインドできないようで null になってしまいます。

My_buffer プロパティが byte[]? 型の場合

以下に検証に使ったコードを載せておきます。Visual Studio 2022 のテンプレートを使ってターゲットフレームワーク .NET 9.0 で作成した ASP.NET Core Web API アプリです。

Controller

using Microsoft.AspNetCore.Mvc;
using WebApi.Models;

namespace WebApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UploadController : ControllerBase
    {
        [HttpPost("blob")]
        public async Task<IActionResult> ReceiveBlob([FromForm] Blob model)
        {
            string result = $"My_field: {model.My_field}";

            if (model.My_file != null && model.My_file.Length > 0)
            {
                string filename = Path.GetFileName(model.My_file.FileName);
                result += $", My_file: {filename}";
            }

            if (model.My_buffer != null && model.My_buffer.Length > 0)
            {
                string array = string.Empty;
                using (var stream = new MemoryStream())
                {
                    await model.My_buffer.CopyToAsync(stream);
                    byte[] bytes = stream.ToArray();
                    foreach (byte b in bytes)
                    {
                        array += $"[{b:x2}]";
                    }
                }
                result += $", My_buffer: {array}";
            }

            return Content(result);
        }
    }
}

View

@{
    ViewData["Title"] = "SendBlob";
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewData["Title"] - WebApi</title>
    <script type="text/javascript">
        const url = "/api/upload/blob";

        const upload = async () => {
            const fileInput = document.getElementById("fileupload");
            const resultDiv = document.getElementById("result");
            const form = new FormData();
            form.append('my_field', 'my value');
            form.append('my_buffer', new Blob([1, 2, 3]));
            form.append('my_file', fileInput.files[0]);

            const param = {
                method: "POST",
                body: form
            }
            const response = await fetch(url, param);
            if (response.ok) {
                const message = await response.text();
                resultDiv.innerText = message;
            } else {
                resultDiv.innerText = "アップロード失敗";
            }
        };

        window.addEventListener('DOMContentLoaded', () => {
            const btn = document.getElementById("button1");
            btn.addEventListener("click", upload);
        });
    </script>
</head>
<body>
    <input type="file" name="fileupload" id="fileupload" multiple="multiple" />
    <br />
    <button type="button" id="button1">Upload</button>
    <br />
    <div id="result"></div>
</body>
</html>

上の View のコードの実行結果は以下のようになります。

View のコードの実行結果

(メモ: プロジェクトは VS2022 AspNet9 WebApi)

Tags: , , ,

Upload Download

About this blog

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

Calendar

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

View posts in large calendar