Open32

メモ

ak358ak358

import requests

ボードIDを設定

board_id = 'abcd1234' # ここにボードIDを入力

APIキーとトークンを設定

api_key = 'yourAPIKey' # ここにAPIキーを入力
api_token = 'yourAPIToken' # ここにAPIトークンを入力

Trello APIのURL

url = f"https://api.trello.com/1/boards/{board_id}/cards"
params = {
'key': api_key,
'token': api_token
}

ak358ak358

APIリクエストの実行

response = requests.get(url, params=params)

レスポンスの処理

if response.status_code == 200:
cards_info = response.json()
for card in cards_info:
print(card)
else:
print(f"Failed to fetch cards: {response.status_code}")

ak358ak358

すべての項目を含めてカード情報をエクセルに保存する方法を示します。カード情報をエクセルに保存する際に、すべてのフィールドを含めるように変更します。

以下のコードは、Trello APIから取得したカードデータのすべてのフィールドをエクセルファイルに保存する方法です。

import requests
import pandas as pd

url = "https://api.trello.com/1/boards/{id}/cards"

query = {
  'key': 'APIKey',
  'token': 'APIToken'
}

response = requests.get(url, params=query)

if response.status_code == 200:
    cards = response.json()

    # カード情報をそのままデータフレームに変換
    df = pd.json_normalize(cards)

    # エクセルファイルに保存
    df.to_excel('trello_cards_all_fields.xlsx', index=False)
    print("データがtrello_cards_all_fields.xlsxに保存されました。")
else:
    print(f"エラーが発生しました。ステータスコード: {response.status_code}")

このコードでは、pd.json_normalize関数を使用して、APIから取得したJSONデータを直接データフレームに変換します。これにより、JSONオブジェクト内のすべてのフィールドがエクセルファイルに含まれます。エクセルファイルの名前はtrello_cards_all_fields.xlsxです。

また、必要に応じてエクセルファイルの名前を変更することができます。APIキー、トークン、およびボードIDを適切に設定してから実行してください。

ak358ak358

理解しました。指定されたJSON構造に名前を付けることで、それがメッセージを表すことを明確にします。以下に、JSON構造に message という名前を付けた例を示します。

{
    "message": {
        "A": ["あなたは", "@value1", "を選びました。"],
        "B": ["あなたは", "@value2", "を選びました。"],
        "C": ["あなたは", "@value3", "を選びました。"],
        "Invalid": ["無効な入力です。", "A, B, C のいずれかを入力してください。"]
    }
}

このようにすることで、message という名前の下にメッセージの定義がまとめられており、他のデータと区別しやすくなります。これにより、プログラムでJSONを扱う際に特定のコンテキストを示すことができます。

ak358ak358

理解しました。value1 の値をリフレクションを使用して取得し、それをメッセージ生成に使う方法を示します。

JSONファイルの修正

まず、JSONファイル(messages.json)を修正して、value1 の代わりに @value1 というプレースホルダーを使用します。

{
    "A": ["あなたは", "@value1", "を選びました。"],
    "B": ["あなたは", "@value2", "を選びました。"],
    "C": ["あなたは", "@value3", "を選びました。"],
    "Invalid": ["無効な入力です。", "A, B, C のいずれかを入力してください。"]
}

C#プログラムの修正

次に、C#プログラムを修正して、リフレクションを使用して value1 の値を取得し、JSONファイルからメッセージを生成します。

プログラム (Program.cs)

using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Reflection;

class Program
{
    // サンプルとして value1 を定義する(実際のアプリケーションでは適切に設定してください)
    static string value1 = "A";

    static void Main()
    {
        // JSONファイルのパス
        string jsonFilePath = "messages.json";
        
        // JSONファイルを読み込む
        var messages = LoadMessages(jsonFilePath);

        // コンソールに入力を促すメッセージを表示
        Console.WriteLine("A, B, C のいずれかを入力してください:");
        
        // ユーザーからの入力を取得
        string input = Console.ReadLine().ToUpper();

        // 入力に基づいて出力するメッセージを取得
        string[] outputMessage;

        if (messages.ContainsKey(input))
        {
            outputMessage = messages[input];
            
            // メッセージのパーツをリフレクションで置き換える
            for (int i = 0; i < outputMessage.Length; i++)
            {
                if (outputMessage[i].StartsWith("@"))
                {
                    string propertyName = outputMessage[i].Substring(1); // @を除いたプロパティ名を取得
                    string propertyValue = GetPropertyValue(propertyName); // リフレクションでプロパティの値を取得
                    outputMessage[i] = propertyValue; // 値を置き換える
                }
            }
        }
        else
        {
            outputMessage = messages["Invalid"];
        }

        // 配列の要素を順に出力
        foreach (var part in outputMessage)
        {
            Console.Write(part + " ");
        }
        Console.WriteLine(); // 改行
    }

    static Dictionary<string, string[]> LoadMessages(string filePath)
    {
        try
        {
            // ファイル内容を読み込み
            var jsonData = File.ReadAllText(filePath);
            
            // JSONをDictionaryに変換
            var messages = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(jsonData);
            
            return messages;
        }
        catch (Exception ex)
        {
            Console.WriteLine("JSONファイルの読み込みに失敗しました: " + ex.Message);
            return new Dictionary<string, string[]>();
        }
    }

    static string GetPropertyValue(string propertyName)
    {
        // リフレクションで定義された変数の値を取得する
        PropertyInfo propertyInfo = typeof(Program).GetProperty(propertyName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
        if (propertyInfo != null)
        {
            object value = propertyInfo.GetValue(null); // staticなプロパティの場合はnullを渡す
            if (value != null)
            {
                return value.ToString(); // 値を文字列として返す
            }
        }
        return ""; // プロパティが見つからない場合は空文字を返す
    }
}

実行方法と結果

  1. 上記のJSONファイル(messages.json)をプロジェクトの実行ディレクトリに保存します。
  2. 上記の修正されたC#プログラム(Program.cs)をIDEに追加します。
  3. 必要なNuGetパッケージ(Newtonsoft.Json)をインストールします。
  4. プロジェクトをビルドして実行します。

結果

プログラムを実行すると、以下のようにメッセージが表示されます。

A, B, C のいずれかを入力してください:

ユーザーが A と入力した場合(value1"A" に置き換わる):

あなたは A を選びました。 

このように、リフレクションを使用してプログラム内で定義された変数の値を取得し、JSONファイルのメッセージ生成に活用することができます。

ak358ak358

理解しました。プログラムを修正して、JSON構造に message という名前を付ける形に変更します。

JSONファイルの修正

{
    "message": {
        "A": ["あなたは", "@value1", "を選びました。"],
        "B": ["あなたは", "@value2", "を選びました。"],
        "C": ["あなたは", "@value3", "を選びました。"],
        "Invalid": ["無効な入力です。", "A, B, C のいずれかを入力してください。"]
    }
}

C#プログラムの修正

プログラム (Program.cs)

using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Reflection;

class Program
{
    // サンプルとして value1 を定義する(実際のアプリケーションでは適切に設定してください)
    static string value1 = "A";

    static void Main()
    {
        // JSONファイルのパス
        string jsonFilePath = "messages.json";
        
        // JSONファイルを読み込む
        var messages = LoadMessages(jsonFilePath);

        // コンソールに入力を促すメッセージを表示
        Console.WriteLine("A, B, C のいずれかを入力してください:");
        
        // ユーザーからの入力を取得
        string input = Console.ReadLine().ToUpper();

        // 入力に基づいて出力するメッセージを取得
        string[] outputMessage;

        if (messages.ContainsKey("message") && messages["message"].ContainsKey(input))
        {
            outputMessage = messages["message"][input];
            
            // メッセージのパーツをリフレクションで置き換える
            for (int i = 0; i < outputMessage.Length; i++)
            {
                if (outputMessage[i].StartsWith("@"))
                {
                    string propertyName = outputMessage[i].Substring(1); // @を除いたプロパティ名を取得
                    string propertyValue = GetPropertyValue(propertyName); // リフレクションでプロパティの値を取得
                    outputMessage[i] = propertyValue; // 値を置き換える
                }
            }
        }
        else
        {
            outputMessage = messages["message"]["Invalid"];
        }

        // 配列の要素を順に出力
        foreach (var part in outputMessage)
        {
            Console.Write(part + " ");
        }
        Console.WriteLine(); // 改行
    }

    static Dictionary<string, Dictionary<string, string[]>> LoadMessages(string filePath)
    {
        try
        {
            // ファイル内容を読み込み
            var jsonData = File.ReadAllText(filePath);
            
            // JSONをDictionaryに変換
            var messages = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string[]>>>(jsonData);
            
            return messages;
        }
        catch (Exception ex)
        {
            Console.WriteLine("JSONファイルの読み込みに失敗しました: " + ex.Message);
            return new Dictionary<string, Dictionary<string, string[]>>();
        }
    }

    static string GetPropertyValue(string propertyName)
    {
        // リフレクションで定義された変数の値を取得する
        PropertyInfo propertyInfo = typeof(Program).GetProperty(propertyName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
        if (propertyInfo != null)
        {
            object value = propertyInfo.GetValue(null); // staticなプロパティの場合はnullを渡す
            if (value != null)
            {
                return value.ToString(); // 値を文字列として返す
            }
        }
        return ""; // プロパティが見つからない場合は空文字を返す
    }
}

修正内容の説明

  • JSONファイル内の構造を message という名前のオブジェクトに変更しました。
  • C#プログラムでは、LoadMessages メソッドで message オブジェクト内のデータを読み込むように修正しました。
  • メインのロジック部分では、messages["message"] として message オブジェクトにアクセスし、そこから適切なメッセージを取得します。

これにより、JSONファイル内でメッセージに対して明確な名前を付け、プログラム内でその名前を使用してメッセージを処理することができるようになります。

ak358ak358

はい、修正したコードではメッセージを変数に応じて動的に生成することができます。以下のポイントで説明します。

  1. JSONファイルの修正:
    JSONファイル (messages.json) では、message という名前のオブジェクトがあり、その中に A, B, C, Invalid というキーがあります。各キーには、それぞれの入力値に対するメッセージが配列として定義されています。メッセージの一部として @value1, @value2, @value3 というプレースホルダーがあり、実行時にこれらが対応する変数の値に置き換えられます。

  2. C#プログラムの修正:

    • LoadMessages メソッドでJSONファイルを読み込み、message オブジェクトを Dictionary<string, Dictionary<string, string[]>> の形式で取得します。
    • Main メソッドでは、ユーザーからの入力に応じて適切なメッセージを取得し、その中の @value1, @value2, @value3 をリフレクションを使用して実際の変数の値で置き換えます。
  3. リフレクションを使用した動的なメッセージ生成:

    • メッセージの配列の要素に @value1, @value2, @value3 が含まれており、これらは実際のプログラム内の変数(ここでは value1, value2, value3)に対応しています。
    • プログラムでは、これらのプレースホルダーが含まれる場合、それをリフレクションで対応する変数の値に置き換えます。これにより、動的に生成されたメッセージを生成し、コンソールに表示することができます。

修正したプログラムは、指定された変数の値に応じてメッセージを動的に生成し、出力する機能を持っています。この方法により、メッセージの内容を柔軟に変更できるため、アプリケーションの柔軟性を高めることができます。

ak358ak358

申し訳ありませんが、前回の説明が不十分でした。実際のプログラムでは、Main メソッド内でユーザーからの入力とJSONファイルの定義を比較する部分がありませんでした。ここで、入力された値とJSONファイル内のキー(例えば "A", "B", "C")を比較しています。これにより、入力された値に対応するメッセージを取得し、それをリフレクションを使って動的に生成しています。

以下に、入力とJSONファイルの比較およびそれに基づいたメッセージの取得を正しく組み込んだ修正例を示します。

修正したC#プログラム

using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Reflection;

class Program
{
    // サンプルとして value1 を定義する(実際のアプリケーションでは適切に設定してください)
    static string value1 = "A";
    static string value2 = "B";
    static string value3 = "C";

    static void Main()
    {
        // JSONファイルのパス
        string jsonFilePath = "messages.json";
        
        // JSONファイルを読み込む
        var messages = LoadMessages(jsonFilePath);

        // コンソールに入力を促すメッセージを表示
        Console.WriteLine("A, B, C のいずれかを入力してください:");
        
        // ユーザーからの入力を取得
        string input = Console.ReadLine().ToUpper();

        // 入力に基づいて出力するメッセージを取得
        string[] outputMessage;

        if (messages.ContainsKey("message") && messages["message"].ContainsKey(input))
        {
            outputMessage = messages["message"][input];
            
            // メッセージのパーツをリフレクションで置き換える
            for (int i = 0; i < outputMessage.Length; i++)
            {
                if (outputMessage[i].StartsWith("@"))
                {
                    string propertyName = outputMessage[i].Substring(1); // @を除いたプロパティ名を取得
                    string propertyValue = GetPropertyValue(propertyName); // リフレクションでプロパティの値を取得
                    outputMessage[i] = propertyValue; // 値を置き換える
                }
            }
        }
        else
        {
            outputMessage = messages["message"]["Invalid"];
        }

        // 配列の要素を順に出力
        foreach (var part in outputMessage)
        {
            Console.Write(part + " ");
        }
        Console.WriteLine(); // 改行
    }

    static Dictionary<string, Dictionary<string, string[]>> LoadMessages(string filePath)
    {
        try
        {
            // ファイル内容を読み込み
            var jsonData = File.ReadAllText(filePath);
            
            // JSONをDictionaryに変換
            var messages = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string[]>>>(jsonData);
            
            return messages;
        }
        catch (Exception ex)
        {
            Console.WriteLine("JSONファイルの読み込みに失敗しました: " + ex.Message);
            return new Dictionary<string, Dictionary<string, string[]>>();
        }
    }

    static string GetPropertyValue(string propertyName)
    {
        // リフレクションで定義された変数の値を取得する
        PropertyInfo propertyInfo = typeof(Program).GetProperty(propertyName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
        if (propertyInfo != null)
        {
            object value = propertyInfo.GetValue(null); // staticなプロパティの場合はnullを渡す
            if (value != null)
            {
                return value.ToString(); // 値を文字列として返す
            }
        }
        return ""; // プロパティが見つからない場合は空文字を返す
    }
}

解説と修正内容

  1. 入力とJSONファイルの比較:

    • Main メソッド内の条件式 messages.ContainsKey("message") && messages["message"].ContainsKey(input) で、message オブジェクトが含まれているか、そして入力された値 input がその中に含まれるかを確認しています。
    • 入力された値 inputmessages["message"] のキーに存在する場合、対応するメッセージを取得しています。
  2. メッセージの動的な生成:

    • 入力された値に対応するメッセージが見つかった場合、そのメッセージの配列を outputMessage に代入します。
    • outputMessage 内の各要素が @value1, @value2, @value3 のいずれかで始まる場合、それをリフレクションを使用して対応する変数の値で置き換えます(GetPropertyValue メソッドを使用)。
  3. エラー処理:

    • 入力された値が messages["message"] のキーに存在しない場合、messages["message"]["Invalid"] からエラーメッセージを取得して表示します。

この修正により、プログラムは正しくユーザーからの入力を処理し、対応するメッセージを動的に生成することができるようになります。

ak358ak358

理解しました。JSONファイルを修正して、"Delimiter" というキーを持つオブジェクトを追加し、それをメッセージの配列内で区切り文字として使用する形にします。以下に修正したJSONファイルの例を示します。

{
    "message": {
        "A": ["あなたは", "Delimiter", "@value1", "を選びました。"],
        "B": ["あなたは", "Delimiter", "@value2", "を選びました。"],
        "C": ["あなたは", "Delimiter", "@value3", "を選びました。"],
        "Invalid": ["無効な入力です。", "A, B, C のいずれかを入力してください。"]
    },
    "Delimiter": ","
}

この修正により、各メッセージ配列内の "Delimiter" を区切り文字として認識し、それに対応する値(ここでは ",")を実際の区切り文字として使用します。

このJSONファイルをプログラムに読み込ませ、メッセージ生成の際に "Delimiter" を適切に処理することで、目的の動作を実現できます。

ak358ak358

すみません、前回のメッセージが意味不明な文字列で終わってしまいました。もう一度修正したJSONファイルを見直しましょう。直感的に理解しやすい形にします。

{
    "message": {
        "A": ["あなたは", "(", "@value1", ")", "を選びました。"],
        "B": ["あなたは", "(", "@value2", ")", "を選びました。"],
        "C": ["あなたは", "(", "@value3", ")", "を選びました。"],
        "Invalid": ["無効な入力です。", "A, B, C のいずれかを入力してください。"]
    },
    "Comma": ",",
    "LeftParenthesis": "(",
    "RightParenthesis": ")"
}

この修正により、メッセージ配列内で括弧を直接表現しています。具体的に "LeftParenthesis""RightParenthesis""("")" に変更しました。これにより、メッセージがどのように構成されているかがより直感的に理解できると思います。

この修正後、プログラムでこのJSONファイルを読み込み、メッセージを生成する際に正しく括弧を使うことができます。

ak358ak358

クラスライブラリを追加する際の一般的な設計手順を以下に示します。

  1. ニーズの明確化: 追加するクラスライブラリが何を提供するか、どのような機能や機能性を持つかを明確にします。これにより、ライブラリが解決する課題や提供する価値が理解できます。

  2. ライブラリの選定: 既存のクラスライブラリがある場合はそれを使用するか、新たに開発するかを決定します。選定基準としては、ライブラリの成熟度、メンテナンス性、ライセンス、パフォーマンスなどが考慮されます。

  3. API設計: クラスライブラリのAPI設計を行います。これには、ライブラリの使用方法、公開する関数やメソッド、パラメータ、戻り値の定義が含まれます。APIは直感的で使いやすく、一貫性があり、将来の拡張性を考慮して設計されるべきです。

  4. 実装: 定義したAPIに基づいてクラスライブラリを実装します。開発環境(WindowsであればPowerShellなど)に適したプログラミング言語を選び、適切なデザインパターンやベストプラクティスに基づいて実装を進めます。

  5. テスト: ユニットテストや統合テストを実施して、クラスライブラリが期待通りに機能し、品質が保証されていることを確認します。特に、エラーハンドリングや境界値条件などの異常系のテストも重要です。

  6. ドキュメント化: ライブラリの使用方法やAPIドキュメントを整備し、他の開発者が容易にライブラリを理解し利用できるようにします。これにはサンプルコードや使用例も含めると良いでしょう。

  7. リリースとデプロイ: 完成したクラスライブラリをリリースし、適切なリポジトリやパッケージマネージャーにデプロイします。バージョン管理を行い、必要に応じて更新や修正を行います。

この手順に沿ってクラスライブラリを追加すると、効率的で堅牢な開発プロセスが確立できます。

ak358ak358

以下は、JSONファイルを読み込み、メッセージを生成するC#のクラスです。このクラスは、Newtonsoft.Jsonライブラリ(Json.NET)を使用してJSONデータを読み込みます。まず、JSONファイルを読み込み、その後、適切なメッセージを生成するためのメソッドを持つクラスを作成します。

1. 必要なNuGetパッケージのインストール

まず、Newtonsoft.JsonをNuGetからインストールしてください。

dotnet add package Newtonsoft.Json

2. C#クラスの実装

using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;

public class MessageGenerator
{
    private readonly Dictionary<string, string[]> messages;
    private readonly string comma;
    private readonly string leftParenthesis;
    private readonly string rightParenthesis;

    public MessageGenerator(string jsonFilePath)
    {
        // JSONファイルを読み込み
        var jsonData = File.ReadAllText(jsonFilePath);
        
        // JSONデータを辞書に変換
        var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonData);
        
        // メッセージ辞書を初期化
        messages = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(data["message"].ToString());
        
        // その他の文字列を初期化
        comma = data["Comma"].ToString();
        leftParenthesis = data["LeftParenthesis"].ToString();
        rightParenthesis = data["RightParenthesis"].ToString();
    }

    public string GenerateMessage(string input, string value)
    {
        if (messages.ContainsKey(input))
        {
            return FormatMessage(messages[input], value);
        }
        else
        {
            return string.Join("", messages["Invalid"]);
        }
    }

    private string FormatMessage(string[] template, string value)
    {
        string message = string.Join("", template);
        message = message.Replace("@value1", value)
                         .Replace("@value2", value)
                         .Replace("@value3", value);
        return message;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        // JSONファイルのパスを指定
        string jsonFilePath = "path/to/your/jsonfile.json";
        
        // MessageGeneratorクラスをインスタンス化
        MessageGenerator generator = new MessageGenerator(jsonFilePath);

        // 入力例
        string input = "A";
        string value = "選択肢1";

        // メッセージを生成して出力
        string result = generator.GenerateMessage(input, value);
        Console.WriteLine(result);

        // 別の入力例
        input = "B";
        result = generator.GenerateMessage(input, value);
        Console.WriteLine(result);

        // 無効な入力例
        input = "X";
        result = generator.GenerateMessage(input, value);
        Console.WriteLine(result);
    }
}

説明

  1. JSONファイル読み込み:

    • MessageGeneratorクラスのコンストラクタで、指定されたファイルパスからJSONファイルを読み込みます。
    • JsonConvert.DeserializeObjectを使用して、JSONデータを辞書形式に変換します。
  2. メッセージ生成:

    • GenerateMessageメソッドで、入力に応じたメッセージテンプレートを選択し、FormatMessageメソッドでプレースホルダーを実際の値で置換します。
  3. プログラムのエントリーポイント:

    • ProgramクラスのMainメソッドで、MessageGeneratorをインスタンス化し、サンプルの入力値と値を使用してメッセージを生成し、コンソールに出力します。

この方法で、JSONファイルから動的にメッセージを読み込み、生成することができます。

ak358ak358

Dictionary<string, string[]>を使ってキーに基づいて文字列配列を検索するのは高速です。Dictionary<TKey, TValue>はハッシュテーブルを内部的に使用しており、キーに基づいた検索、挿入、および削除操作が平均的にO(1)の時間で行われるため、非常に効率的です。

以下は、Dictionaryを使用してメッセージを検索する例を含む完全なC#コードです。

C#クラスの完全な実装例

using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;

public class MessageGenerator
{
    private readonly Dictionary<string, string[]> messages;
    private readonly string comma;
    private readonly string leftParenthesis;
    private readonly string rightParenthesis;

    public MessageGenerator(string jsonFilePath)
    {
        // JSONファイルを読み込み
        var jsonData = File.ReadAllText(jsonFilePath);
        
        // JSONデータを辞書に変換
        var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonData);
        
        // メッセージ辞書を初期化
        messages = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(data["message"].ToString());
        
        // その他の文字列を初期化
        comma = data["Comma"].ToString();
        leftParenthesis = data["LeftParenthesis"].ToString();
        rightParenthesis = data["RightParenthesis"].ToString();
    }

    public string GenerateMessage(string input, string value)
    {
        if (messages.ContainsKey(input))
        {
            return FormatMessage(messages[input], value);
        }
        else
        {
            return string.Join("", messages["Invalid"]);
        }
    }

    private string FormatMessage(string[] template, string value)
    {
        string message = string.Join("", template);
        message = message.Replace("@value1", value)
                         .Replace("@value2", value)
                         .Replace("@value3", value);
        return message;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        // JSONファイルのパスを指定
        string jsonFilePath = "path/to/your/jsonfile.json";
        
        // MessageGeneratorクラスをインスタンス化
        MessageGenerator generator = new MessageGenerator(jsonFilePath);

        // 入力例
        string input = "A";
        string value = "選択肢1";

        // メッセージを生成して出力
        string result = generator.GenerateMessage(input, value);
        Console.WriteLine(result);

        // 別の入力例
        input = "B";
        result = generator.GenerateMessage(input, value);
        Console.WriteLine(result);

        // 無効な入力例
        input = "X";
        result = generator.GenerateMessage(input, value);
        Console.WriteLine(result);
    }
}

高速性の説明

  1. Dictionaryの内部構造:

    • Dictionary<TKey, TValue>は、キーのハッシュコードを計算し、そのハッシュコードに基づいて値を格納するハッシュテーブルを使用します。このため、キーに基づいた検索は非常に高速です。
  2. O(1)の時間複雑度:

    • ハッシュテーブルを使用することで、検索操作の平均時間複雑度はO(1)となります。これは、入力サイズが大きくなっても、検索にかかる時間が一定であることを意味します。
  3. メモリ効率:

    • Dictionaryはメモリを効率的に使用し、高速な検索を提供します。ただし、メモリ消費が重要な場合は、辞書のサイズや負荷率を調整することができます。

Dictionaryを使用することで、文字列配列を効率的に管理し、高速に検索することが可能です。このアプローチは、多くのキーと値のペアが存在する場合でも、高いパフォーマンスを維持することができます。

ak358ak358

JSONファイルの中身が変わらない場合、クラスをstaticにするのは適切なアプローチです。staticクラスを使用することで、メモリ効率が向上し、クラスのインスタンス化を省略できます。以下は、JSONファイルを読み込むstaticクラスの実装例です。

C#のstaticクラスの実装

using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;

public static class MessageGenerator
{
    private static readonly Dictionary<string, string[]> messages;
    private static readonly string comma;
    private static readonly string leftParenthesis;
    private static readonly string rightParenthesis;

    // staticコンストラクタでJSONファイルを読み込む
    static MessageGenerator()
    {
        // JSONファイルのパスを指定
        string jsonFilePath = "path/to/your/jsonfile.json";
        
        // JSONファイルを読み込み
        var jsonData = File.ReadAllText(jsonFilePath);
        
        // JSONデータを辞書に変換
        var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonData);
        
        // メッセージ辞書を初期化
        messages = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(data["message"].ToString());
        
        // その他の文字列を初期化
        comma = data["Comma"].ToString();
        leftParenthesis = data["LeftParenthesis"].ToString();
        rightParenthesis = data["RightParenthesis"].ToString();
    }

    public static string GenerateMessage(string input, string value)
    {
        if (messages.ContainsKey(input))
        {
            return FormatMessage(messages[input], value);
        }
        else
        {
            return string.Join("", messages["Invalid"]);
        }
    }

    private static string FormatMessage(string[] template, string value)
    {
        string message = string.Join("", template);
        message = message.Replace("@value1", value)
                         .Replace("@value2", value)
                         .Replace("@value3", value);
        return message;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        // 入力例
        string input = "A";
        string value = "選択肢1";

        // メッセージを生成して出力
        string result = MessageGenerator.GenerateMessage(input, value);
        Console.WriteLine(result);

        // 別の入力例
        input = "B";
        result = MessageGenerator.GenerateMessage(input, value);
        Console.WriteLine(result);

        // 無効な入力例
        input = "X";
        result = MessageGenerator.GenerateMessage(input, value);
        Console.WriteLine(result);
    }
}

説明

  1. staticコンストラクタ:

    • staticコンストラクタを使用して、クラスが初めてアクセスされたときにJSONファイルを読み込みます。
    • このコンストラクタはクラスがロードされるときに一度だけ呼び出され、メッセージ辞書および他のフィールドを初期化します。
  2. staticメソッド:

    • GenerateMessageメソッドおよびFormatMessageメソッドはstaticとして定義されています。
    • これにより、クラスをインスタンス化せずにこれらのメソッドにアクセスすることができます。
  3. メモリ効率:

    • クラス全体がstaticであるため、JSONデータはアプリケーションのライフタイム全体で一度だけ読み込まれ、メモリに保持されます。
    • インスタンスを作成するオーバーヘッドがないため、パフォーマンスが向上します。

この方法で、JSONデータが固定されている場合に効率的にメッセージを生成することができます。

ak358ak358

あ、TKey:1.TValue:111
TKey:2,TValue:111のとき、
TKey:2,TValue:TKey1と同じ
ってすればいいの?

ak358ak358

複数の検索文字列を外部ファイルから読み込み、それらをカンマ区切りで扱う方法を以下に示します。この例では、検索文字列がカンマ区切りで記載されたファイル(例:search_strings.txt)を使用して、指定フォルダ内のすべてのファイルから該当する行を抽出します。

import os

# 入力フォルダ名
input_folder = 'input_folder'
# 出力ファイル名
output_file_name = 'output.txt'
# 検索文字列が記載されたファイル名
search_strings_file = 'search_strings.txt'

# 検索文字列を読み込む
with open(search_strings_file, 'r', encoding='utf-8') as file:
    search_strings = file.read().strip().split(',')

# 出力ファイルを開く
with open(output_file_name, 'w', encoding='utf-8') as output_file:
    # 入力フォルダ内のすべてのファイルを処理
    for file_name in os.listdir(input_folder):
        # ファイルのパスを取得
        file_path = os.path.join(input_folder, file_name)
        # ファイルかどうかを確認
        if os.path.isfile(file_path):
            # 入力ファイルを開いて内容を読み込み、検索文字列が含まれる行を抽出する
            with open(file_path, 'r', encoding='utf-8') as input_file:
                for line in input_file:
                    if any(search_string in line for search_string in search_strings):
                        output_file.write(f'{file_name}: {line}')

print(f'検索結果を {output_file_name} に保存しました。')

このコードでは、以下の手順で処理を行います:

  1. input_folder に対象のフォルダ名を指定します。
  2. output_file_name に検索結果を保存するファイル名を指定します。
  3. search_strings_file にカンマ区切りの検索文字列が記載されたファイル名を指定します。
  4. 検索文字列を外部ファイルから読み込み、カンマで分割してリストにします。
  5. os.listdir(input_folder) を使ってフォルダ内のすべてのファイルを取得し、それぞれのファイルを処理します。
  6. 各ファイルを開き、検索文字列のいずれかが含まれる行を検索し、見つかった行を出力ファイルに書き込みます。ファイル名と行の内容も一緒に出力します。

これにより、指定したフォルダ内のすべてのファイルから、外部ファイルで指定された複数の検索文字列を含む行を抽出して、出力ファイルに保存できます。

ak358ak358

IDが1のトップ5行とIDが3のトップ5行をそれぞれ取得するためには、以下のようにUNION ALLを使用して2つのSELECTクエリを結合します。

SELECT TOP 5 A 
FROM ATABLE 
WHERE ID = '1' 
ORDER BY ID

UNION ALL

SELECT TOP 5 A 
FROM ATABLE 
WHERE ID = '3' 
ORDER BY ID

このクエリでは、まずIDが1のトップ5行を取得し、その後IDが3のトップ5行を取得します。UNION ALLを使用することで、重複を排除せずにすべての行を結合します。

ak358ak358

その方法で問題ありません。以下のように、クラスのインスタンスを JsonConvert.SerializeObject を使ってJSON文字列に変換し、それをSQLパラメータとしてデータベースに格納することができます。

サンプルコード

using Newtonsoft.Json;
using System.Data.SqlClient;

public class MyClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<string> Items { get; set; }
}

public void SaveToJsonInDatabase()
{
    var myObject = new MyClass 
    {
        Id = 1,
        Name = "Example",
        Items = new List<string> { "Item1", "Item2" }
    };

    // クラスのインスタンスをJSONにシリアライズ
    string json = JsonConvert.SerializeObject(myObject);

    // データベースに接続して保存
    using (SqlConnection connection = new SqlConnection("YourConnectionString"))
    {
        connection.Open();
        using (SqlCommand command = new SqlCommand("INSERT INTO YourTable (JsonColumn) VALUES (@jsonParam)", connection))
        {
            // JSON文字列をパラメータとして追加
            command.Parameters.AddWithValue("@jsonParam", json);
            
            // SQLコマンドを実行
            command.ExecuteNonQuery();
        }
    }
}

説明

  • JsonConvert.SerializeObject(myObject):

    • myObject をJSON形式の文字列に変換します。このJSON文字列がデータベースに格納されるデータになります。
  • command.Parameters.AddWithValue("@jsonParam", json):

    • JSON文字列をSQLパラメータ @jsonParam にセットします。この方法でSQLインジェクション攻撃を防ぎつつ、安全にデータをデータベースに挿入できます。

注意点

  1. データベースの型:

    • JSONデータを格納するデータベースのカラムの型は NVARCHARTEXT など、文字列型に適しているものを使用してください。
  2. データサイズ:

    • JSONデータが非常に大きくなる場合、データベースのカラムのサイズ制限に注意が必要です。

この方法で、クラスのデータを安全にJSON形式でデータベースに格納することができます。

ak358ak358

リモートデスクトップで「資格情報を入力してください」ダイアログが表示されない場合、以下の問題と対策が考えられます:

1. リモートデスクトップ設定の確認

  • リモートデスクトップの設定: サーバー側でリモートデスクトップが正しく設定されているか確認します。特に、リモートデスクトップの設定で「ネットワークレベル認証(NLA)」が有効になっている場合、クライアントがそれに対応している必要があります。

2. リモートデスクトップクライアントの確認

  • リモートデスクトップアプリケーションのバージョン: 使用しているリモートデスクトップクライアントが最新であるか確認します。古いバージョンのクライアントが問題を引き起こすことがあります。
  • 接続設定: リモートデスクトップクライアントの設定で「資格情報を自動的に保存しない」オプションが有効になっていると、資格情報ダイアログが表示されないことがあります。設定を確認し、資格情報を手動で入力するように変更します。

3. ネットワーク接続の確認

  • VPN接続: VPNを使用している場合、VPN接続が正常であるか確認します。VPN接続が不安定であると、リモートデスクトップ接続に影響を与えることがあります。
  • ファイアウォール設定: クライアント側とサーバー側のファイアウォール設定を確認し、リモートデスクトップ接続に必要なポート(デフォルトで3389)が開放されているか確認します。

4. 資格情報の保存

  • 資格情報の保存: クライアント側で以前に資格情報が保存されている場合、資格情報ダイアログが表示されないことがあります。資格情報マネージャーで保存された資格情報を確認し、削除または更新してみます。

5. トラブルシューティング

  • リモートデスクトップの再起動: リモートデスクトップクライアントとサーバーの両方を再起動してみます。時折、再起動することで問題が解決することがあります。
  • イベントログの確認: サーバー側のイベントログでエラーメッセージがないか確認します。特に、リモートデスクトップ関連のエラーが記録されているか確認します。

6. リモートデスクトップのセッション設定

  • セッションの設定: リモートデスクトップのセッション設定で、ユーザーがログインする際に資格情報を求めるように設定されているか確認します。

7. クライアントソフトの再インストール

  • クライアントソフトの再インストール: リモートデスクトップクライアントをアンインストールし、再インストールしてみるのも一つの手です。

まとめ

リモートデスクトップで「資格情報を入力してください」ダイアログが表示されない場合、クライアントの設定、サーバーの設定、ネットワーク接続、ファイアウォール設定など、さまざまな要因が考えられます。これらのチェックポイントを順に確認し、問題の原因を特定して対処することが重要です。

ak358ak358

Npcap Loopback Adapter が生きているかどうかを ipconfig で確認することはできます。ipconfig コマンドは、ネットワークアダプタの設定やステータスを表示するため、Npcap Loopback Adapter が表示されるかどうかでそのアダプタが有効かどうかを判断できます。

確認手順

  1. コマンドプロンプトを開く:

    • Win + R キーを押して「ファイル名を指定して実行」を開き、cmd と入力して Enter を押します。
  2. ipconfig コマンドを実行する:

    • コマンドプロンプトで以下のコマンドを入力し、Enter を押します。
      ipconfig /all
      
  3. 出力を確認する:

    • 出力された情報の中に Npcap Loopback Adapter という名前のアダプタが表示されます。これが表示されていれば、そのアダプタが有効であることを意味します。
    • 各アダプタの下に、IP AddressSubnet Mask などの情報が表示されます。Npcap Loopback Adapter の情報が表示されるはずです。

出力例として、以下のように Npcap Loopback Adapter が表示されます:

Ethernet adapter Npcap Loopback Adapter:

   Connection-specific DNS Suffix  . : 
   Link-local IPv6 Address . . . . . : fe80::1a2b:3c4d:5e6f:7890%4
   IPv4 Address. . . . . . . . . . : 127.0.0.1
   Subnet Mask . . . . . . . . . . : 255.0.0.0
   ...

もし表示されない場合

  • Npcap Loopback Adapter が表示されない場合は、アダプタが無効化されているか、インストールされていない可能性があります。その場合は、デバイスマネージャでアダプタの状態を確認したり、再インストールを試みたりする必要があります。

この手順で、Npcap Loopback Adapter が正常に機能しているかどうかを確認できます。

ak358ak358

ASP.NET Webフォームのコードでは、ファイルのバリデーションはアノテーションではなく、主にプログラムコードによって行います。アノテーションは、通常、ASP.NET MVCやASP.NET Coreのようなモデルバインディングで使用されることが多いです。

以下に、アノテーションとプログラムコードによるバリデーションの違いと、それぞれの使い方を説明します。

プログラムコードによるバリデーション (Webフォームの場合)

ASP.NET Webフォームでは、ファイルアップロードのバリデーションは主にサーバーサイドで行います。具体的には、HttpPostedFile クラスを使ってファイルの拡張子やサイズをチェックします。

例えば、以下のようにプログラムコードでファイルの拡張子をバリデートします:

protected void UploadMultipleFiles(object sender, EventArgs e)
{
    foreach (HttpPostedFile postedFile in FileUpload1.PostedFiles)
    {
        string fileName = Path.GetFileName(postedFile.FileName);
        string fileExtension = Path.GetExtension(fileName).ToLower();

        if (fileExtension == ".jpg" || fileExtension == ".jpeg" || fileExtension == ".png")
        {
            // ファイルを保存
            postedFile.SaveAs(Server.MapPath("~/Uploads/") + fileName);
            Information.Text = "ファイルが正常にアップロードされました。";
        }
        else
        {
            Information.Text = "ファイルの形式が無効です。";
        }
    }
}

アノテーションによるバリデーション (MVCの場合)

ASP.NET MVCやASP.NET Coreでは、モデルのプロパティにアノテーションを使用してバリデーションを定義することができます。アノテーションは、データの整合性や形式を簡単に指定できるため、非常に便利です。

以下は、ASP.NET MVCのモデルにおけるバリデーションの例です:

public class FileUploadModel
{
    [Required]
    [FileExtensions(Extensions = "jpg,jpeg,png", ErrorMessage = "Invalid file type.")]
    public HttpPostedFileBase File { get; set; }
}

この例では、FileExtensions アノテーションを使用して、ファイルの拡張子が .jpg.jpeg、または .png であることを指定しています。

まとめ

  • ASP.NET Webフォーム: プログラムコードでファイルのバリデーションを行います。ファイルの拡張子やサイズなどを手動でチェックします。
  • ASP.NET MVC / ASP.NET Core: モデルのプロパティにアノテーションを使ってバリデーションを簡単に指定します。

Webフォームではアノテーションはサポートされていないため、バリデーションはコードで実装する必要があります。一方、MVCやCoreではアノテーションを利用することで、より簡潔にバリデーションを設定できます。

ak358ak358

[FileExtensions] アノテーションは、ファイルの拡張子に対するバリデーションを提供しますが、ファイルの数の上限設定には対応していません。ファイルの数の上限を設定するには、他の方法を使う必要があります。以下に、ASP.NET MVCやASP.NET Coreでファイルの数に対するバリデーションを行う方法を説明します。

1. ASP.NET MVC / ASP.NET Coreでのファイル数の制限

ファイル数の制限を実装するためには、モデルのプロパティに対して追加のバリデーションを行うカスタムバリデータを作成するか、コントローラで直接検証を行うことができます。

カスタムバリデータを使った方法

まず、カスタムバリデータを作成します。

using System;
using System.ComponentModel.DataAnnotations;
using System.Web;

public class MaxFileCountAttribute : ValidationAttribute
{
    private readonly int _maxFiles;

    public MaxFileCountAttribute(int maxFiles)
    {
        _maxFiles = maxFiles;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var files = value as IEnumerable<HttpPostedFileBase>;
        if (files == null || files.Count() <= _maxFiles)
        {
            return ValidationResult.Success;
        }

        return new ValidationResult($"You can only upload up to {_maxFiles} files.");
    }
}

次に、このカスタムアノテーションをモデルに適用します。

public class FileUploadModel
{
    [Required]
    [FileExtensions(Extensions = "jpg,jpeg,png", ErrorMessage = "Invalid file type.")]
    [MaxFileCount(5, ErrorMessage = "You can only upload up to 5 files.")]
    public IEnumerable<HttpPostedFileBase> Files { get; set; }
}

コントローラでのバリデーション

また、コントローラ内で直接ファイル数を検証する方法もあります。以下にその例を示します。

[HttpPost]
public ActionResult UploadFiles(IEnumerable<HttpPostedFileBase> files)
{
    if (files.Count() > 5)
    {
        ModelState.AddModelError("", "You can only upload up to 5 files.");
    }

    foreach (var file in files)
    {
        if (file != null && file.ContentLength > 0)
        {
            // バリデーションや保存処理
        }
    }

    if (!ModelState.IsValid)
    {
        return View();
    }

    // 成功処理
    return RedirectToAction("Index");
}

2. ASP.NET Webフォームでのファイル数の制限

ASP.NET Webフォームでは、ファイル数の制限はサーバーサイドでコードを使って検証する必要があります。以下はその例です。

protected void UploadMultipleFiles(object sender, EventArgs e)
{
    int maxFiles = 5;
    
    if (FileUpload1.PostedFiles.Count > maxFiles)
    {
        Information.Text = $"You can only upload up to {maxFiles} files.";
        return;
    }

    foreach (HttpPostedFile postedFile in FileUpload1.PostedFiles)
    {
        string fileName = Path.GetFileName(postedFile.FileName);
        string fileExtension = Path.GetExtension(fileName).ToLower();

        if (fileExtension == ".jpg" || fileExtension == ".jpeg" || fileExtension == ".png")
        {
            postedFile.SaveAs(Server.MapPath("~/Uploads/") + fileName);
            Information.Text = $"{FileUpload1.PostedFiles.Count} files have been uploaded successfully.";
        }
        else
        {
            Information.Text = "Some files have invalid formats.";
        }
    }
}

まとめ

  • ASP.NET MVC / ASP.NET Core: FileExtensions アノテーションでファイルの形式を検証し、カスタムバリデータを使ってファイル数の制限を設定できます。
  • ASP.NET Webフォーム: コード内でファイルの数を手動で検証し、制限を設ける必要があります。

どちらのアプローチでも、要件に応じて適切な方法を選んで実装してください。

ak358ak358

質問

質問:
ASP.NET MVCで複数のファイルをアップロードする際に、拡張子とファイルサイズのバリデーションをどのように実装できますか?特に、List<IFormFile>として複数のファイルを扱う場合の例を教えてください。


回答

回答:

複数のファイルに対する拡張子とファイルサイズのバリデーションは、ASP.NET MVCでは以下の2つの方法で実装できます。

1. IValidatableObject インターフェースを使用する方法

IValidatableObject インターフェースを実装することで、カスタムバリデーションロジックをモデルに追加できます。

モデル:

public class UserViewModel : IValidatableObject
{
    public IList<IFormFile> Photo { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var photos = Photo;
        foreach (var photo in photos)
        {
            var extension = Path.GetExtension(photo.FileName);
            var size = photo.Length;

            if (extension.ToLower() != ".jpg" && extension.ToLower() != ".png")
                yield return new ValidationResult($"{photo.FileName}'s file extension is not valid.");

            if (size > (5 * 1024 * 1024)) // 5MB
                yield return new ValidationResult($"{photo.FileName}'s file size is bigger than 5MB.");
        }
    }
}

2. カスタムバリデーションアトリビュートを使用する方法

カスタムアトリビュートを作成して、モデルのプロパティに対して拡張子とサイズのバリデーションを行います。

カスタムバリデーションアトリビュート:

ファイルサイズ制限アトリビュート:

public class MaxFileSizeAttribute : ValidationAttribute
{
    private readonly int _maxFileSize;
    public MaxFileSizeAttribute(int maxFileSize)
    {
        _maxFileSize = maxFileSize;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var files = value as IList<IFormFile>;
        foreach (var file in files)
        {
            if (file != null && file.Length > _maxFileSize)
            {
                return new ValidationResult(GetErrorMessage(file.FileName));
            }
        }
        return ValidationResult.Success;
    }

    public string GetErrorMessage(string name)
    {
        return $"{name}'s size is out of range. Maximum allowed file size is {_maxFileSize} bytes.";
    }
}

拡張子制限アトリビュート:

public class AllowedExtensionsAttribute : ValidationAttribute
{
    private readonly string[] _extensions;
    public AllowedExtensionsAttribute(string[] extensions)
    {
        _extensions = extensions;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var files = value as IList<IFormFile>;
        foreach (var file in files)
        {
            var extension = Path.GetExtension(file.FileName);
            if (file != null && !_extensions.Contains(extension.ToLower()))
            {
                return new ValidationResult(GetErrorMessage(file.FileName));
            }
        }
        return ValidationResult.Success;
    }

    public string GetErrorMessage(string name)
    {
        return $"{name} extension is not allowed!";
    }
}

モデル:

public class UserViewModel
{
    [MaxFileSize(5 * 1024 * 1024)] // 5MB
    [AllowedExtensions(new string[] { ".jpg", ".png" })]
    public IList<IFormFile> Photo { get; set; }
}

ビュー (Upload.cshtml):

@model UserViewModel

<form method="post" asp-action="Upload" asp-controller="Home" enctype="multipart/form-data">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>

    <input asp-for="Photo" type="file" multiple />
    <span asp-validation-for="Photo" class="text-danger"></span>
    <input type="submit" value="Upload" />
</form>

コントローラー:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Upload(UserViewModel userViewModel)
{
    if (!ModelState.IsValid)
    {                
        return View("Upload");
    }
    
    // ファイル処理ロジック
    return View("Index");
}

これらの方法で、複数のファイルに対して拡張子とサイズのバリデーションを実装できます。最初の方法では、モデル内でバリデーションロジックを管理し、2番目の方法ではカスタムアトリビュートを使用して、より柔軟なバリデーションを実現します。

ak358ak358

ASP.NET Coreのデフォルトのバリデーションアノテーションには、ファイルの数を制限するものは含まれていません。標準のデータ注釈(Data Annotations)では、ファイルの拡張子やサイズの検証は行えますが、複数ファイルの数に関する制限はサポートされていません。

ただし、ファイルの数を制限するためには、カスタムバリデーションを使用する必要があります。以下に、ファイルの数を制限するためのカスタムバリデーションの実装例を示します。

カスタムバリデーションアトリビュートの作成

複数ファイルの数を制限するためのカスタムアトリビュートを作成します。

MaxFileCountAttribute クラス:

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;

public class MaxFileCountAttribute : ValidationAttribute
{
    private readonly int _maxFileCount;

    public MaxFileCountAttribute(int maxFileCount)
    {
        _maxFileCount = maxFileCount;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var files = value as IList<IFormFile>;
        if (files != null && files.Count > _maxFileCount)
        {
            return new ValidationResult($"最大ファイル数は {_maxFileCount} です。");
        }

        return ValidationResult.Success;
    }
}

モデルでの使用

カスタムバリデーションアトリビュートをモデルに適用します。

UserViewModel クラス:

using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

public class UserViewModel
{
    [MaxFileCount(5)] // 最大5ファイル
    [MaxFileSize(5 * 1024 * 1024)] // 5MB
    [AllowedExtensions(new string[] { ".jpg", ".png" })]
    public IList<IFormFile> Photo { get; set; }
}

コントローラーでのバリデーション処理

コントローラーでモデルのバリデーションを行います。

HomeController クラス:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Upload(UserViewModel userViewModel)
{
    if (!ModelState.IsValid)
    {
        return View("Upload");
    }

    // ファイル処理ロジック
    foreach (var file in userViewModel.Photo)
    {
        var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/uploads", file.FileName);
        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await file.CopyToAsync(stream);
        }
    }

    return RedirectToAction("Index");
}

まとめ

デフォルトのアノテーションにはファイル数を制限する機能はないため、カスタムバリデーションアトリビュートを作成してファイル数の制限を行う必要があります。これにより、モデルのプロパティに対して柔軟で強力なバリデーションが可能になります。