web-dev-qa-db-ja.com

enum値が解析されないときにenumを文字列バインディングエラーとして処理する方法

私たちのASP.net Core Web APIアプリケーションでは、ENUMが文字列として逆シリアル化されたときに、コントローラーメソッドがENUMプロパティを持つ複雑なオブジェクトを受け入れるときに、バインディングエラーをキャッチする方法を探しています。

例えば。

class Person
{
    public string Name {get; set;}
    public SexEnum Sex {get; set;}
}

enum SexEnum
{
    Male,
    Female
}

システム全体でStringEnumConverterを使用しているため、PersonのJSONシリアル化インスタンスは次のようになります。

{
    "name": "Ann",
    "sex": "female"
}

次に、このJSONを投稿した場合(sexプロパティのタイプミスに注意してください):

{
    "name": "Ann",
    "sex": "femal"
}

バインディングが失敗したため、コントローラーメソッドによって受信されたオブジェクト全体がNULLです。

そのバインディングエラーをキャッチし、何も問題がないかのようにパイプラインをコントローラーに送る代わりに、バインドに失敗したプロパティ値の詳細を含むBAD REQUESTをクライアントに返します。

逆シリアル化しようとしている型を知っています。逆シリアル化しようとしているプロパティの型を知っています。値が型に解析されないことがわかります。そのため、クライアントにその詳細を提供する方法が必要だと思います。これをどこにどのように接続するかわからない。

モデルのプロパティや列挙自体に属性を設定する必要なく、すべての列挙がカバーされるように、ソリューションをシステム全体に適用したいと考えています。 (これは、APIモデルを依存関係のないnugetパッケージとして配布するためです。)

7
Viktor

上記のSimply Gedの回答AFAICSをフォローアップしています。モデルバインディングの例外が飲み込まれているため、これは実際には実行できません( https://github.com/aspnet/Mvc/issues/3898

ModelStateにはモデルバインディングエラーが含まれており、そこからいくつかの情報を取得できます。現在はJSONシリアル化のみを使用しているため、ModelStateJsonSerializationExceptionエラーをチェックするフィルターを実装することになりました。それは例えば完璧ではありません。 JsonSerializationExceptionから(バインドに失敗した)要求された値を取得するには、内部例外メッセージを解析する必要があります。

誰かがより良い解決策を見つけたら、私は聞いてうれしいです。

1
Viktor

最近この問題が発生し、それを処理するための独自の属性を記述しました。

public class ValidEnumValueAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        Type enumType = value.GetType();
        bool valid = Enum.IsDefined(enumType, value);

        if(!valid)
        {
            return new ValidationResult($"{value} is not a valid value for type {enumType.Name}");
        }

        return ValidationResult.Success;
    }
}

class Person
{
    public string Name {get; set;}

    [ValidEnumValue]
    public SexEnum Sex {get; set;}
}

次に、エラーがModelStateに追加されるため、ModelState.IsValid値が有効かどうかを確認します。

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

[〜#〜]編集[〜#〜]

属性を使用したくない場合は、NewtonSoft StringEnumConverterから新しいコンバーターを派生させ、jsonを読み取る前に値が有効であることを確認することができます。

public class validEnumConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if(!Enum.IsDefined(objectType, reader.Value))
        {
            throw new ArgumentException("Invalid enum value");
        }

        return base.ReadJson(reader, objectType, existingValue, serializer);
    }
}

これは、スタートアップクラスのJsonOptionsに追加されます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddJsonOptions(options =>
    {
        options.SerializerSettings.Converters.Add(new validEnumConverter());
    });
}
6
Simply Ged

@Simply Gedの優れた答えの2番目の部分を拡張すると、null可能列挙型を使用してSystem.ArgumentException: 'Type provided must be an Enum.'例外。 null可能な列挙型、または列挙型のnull値を処理するには、追加の手順が必要です。

public class validEnumConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        Type enumType = (Nullable.GetUnderlyingType(objectType) ?? objectType);
        if(!Enum.IsDefined(enumType, reader.Value ?? string.Empty))
        {
            throw new ArgumentException("Invalid enum value");
        }

        return base.ReadJson(reader, objectType, existingValue, serializer);
    }
}
0
Mark