質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.83%

  • C#

    6017questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • ASP.NET MVC Framework

    42questions

    ASP.NET MVC Frameworkは、MVCパターンをベースとした、マイクロソフトのウェブアプリケーション開発用のフレームワークです。

ASP.NET MVC: 自作クラスのstringへのキャスト&検証属性の利用

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 92

sk_3122

score 979

 前提

  • ASP.NET MVC 5 (C#) を使用したシステムを開発しています。
  • モデルの実装についての質問です。

 実現したいこと・試したこと

  • 1 つのフィールドに対し、複数の値を持たせたい(?)です。

↓ 現行

// 一行分のデータのモデル
public class HogeRow
{
    public string FugaName { get; set; }   // ★現行はただの string 型プロパティ
}
...

var model = new HogeRow();
model.FugaName = "ふがふが";

↓ こんな感じで持ちたい

// 一行分のデータのモデル
public class HogeRow
{
    public MyField FugaName { get; set; }  // ★自作クラス型のプロパティにする
}
...

var model = new HogeRow();
model.FugaName.Value = "ふがふが";   // プロパティ値。
model.FugaName.Org = "フガフガ";     // [参照用として持ちたい] 下位権限者が入力した値が入る
model.FugaName.Mod = "FUGA";        // [参照用として持ちたい] 上位権限者側で修正した値が入る

...
public class MyField
{
    public string Value { get; set; }
    public string Org { get; set; }
    public string Mod { get; set; }
    ...
    // ToString() をオーバーライド
    public override string ToString()
    {
        return this.Value;
    }
    // MyField → string への変換演算子を定義する (implicit = 暗黙の変換)
    public static implicit operator string(MyField val)
    {
        if (val == null) return null;
        return val.Value;
    }
}
  • 『 基本的に 通常の string 型プロパティと同じように扱いたいが、+α の値も持ちたい 』
    というのが最終的な希望です。

 問題点1:キャスト

最初は string 型を継承した自作クラスを作りたい・・・ と思ったのですが、シール型は継承できないと怒られたので前出のようにしてみました。

public static implicit operator string(MyField val)
を実装したので、以下のキャストは通ります。

var str = (string)model.FugaName;  // OK

しかし、as でのキャストがエラーになります。

var str = model.FugaName as string;   // 'MyFieldValue': シール型 'string' から派生することはできません。

【質問1】
上記が通るようにする方法はないでしょうか・・・?
(別の掲示板 stackoverflow を見てみたら "ないよ!" という書き込みがあったのですが・・・ どうしてもありませんか・・・)

 問題点2:検証属性

  • 件のモデルでは、検証属性を色々使っています。
// 一行分のデータのモデル
public class HogeRow
{
    [Required]
    [StringLength(10, ErrorMessage = "10桁以下で入力してください")]
    public MyField FugaName { get; set; }
}

【質問2】
String 値を検証する 検証属性を使いたいのですが、
これも都度 「StringLengthAttribute を継承した MyStringLengthAttribute ...」 などを実装するしかないでしょうか?
(MyField.Value を検証して欲しいです)

public static implicit operator string(MyField val)
を実装したのでそのまま検証してくれないかな、とちょっと期待したのですがエラーが出ました。
('MyField': シール型 'string' から派生することはできません。)

使いたい検証属性を全部実装するしかないでしょうか

 追記:そもそもの修正要件

現状、単純に
「下位権限者が入力する」
「それを上位権限側で確認する(参照するのみ)」
というシステムがあります。

それに対し、
「上位権限者側でも修正を入れたい」
という要件が上がってきました。

なので、それぞれの入力フィールドに対し
「下位権限者による入力値」Org
「上位権限者による入力値」Mod
「最終的な採用値」Value
を持ちたいと思っています。
(Org, Mod は画面表示用として持ちたいだけ)

 検討1:それぞれプロパティを個別に用意する

まず考えたのは以下ですが、プロパティが多く、同様のモデルも多い為、ひとまず無しとしました。

// 一行分のデータのモデル
public class HogeRow
{
    public string FugaName { get; set; }
    public string OrgFugaName { get; set; }
    public string ModFugaName { get; set; }
    /*
      さらに
      「Orgのデータがあるか」
      「Modのデータがあるか」
      「上位権限者による編集が行われているか」
      などの判定ロジックも欲しい。MyField のように自作クラス化するならその中に作ってしまえるけど・・・
    */
    ...
}
 検討2:モデルの入れ子にする

次に以下のパターンも考えました。

// 一行分のデータのモデル
public class HogeRow
{
    public HogeRow Org { get; }
    public HogeRow Mod { get; }

    public string FugaName { get; set; }
    ...
}
/*
  model.FugaName
  model.Org.FugaName
  model.Mod.FugaName
  のようにアクセスする
*/

このケースについては、以下が気になった為 なしとしました。

・HogeRow には更新系のメソッドなども定義しているが、Org や Mod ではそれらのメソッドを呼んで欲しくない。(呼ばなければ良いだけなのですが、呼んでほしくないメソッドが定義されているというのが気になりました)
⇒ なお、サンプルコードでは省いていますが、HogeRow は既に別の基底クラスを継承しています。
なので「プロパティだけを定義したクラスを別に作って、Org, Mod はそのクラスで定義、HogeRow はそのクラスを継承」という方法もちょっと難しいです。

・やはり プロパティ単位で Org や Mod を扱いたい。
(model.Org.FugaName ではなく model.FugaName.Org で扱いたい)
「上位権限者による編集が行われているか」の判定等をするのにもその方がやりやすいので・・・

 検討3:ディクショナリで持つ

参照用の値はディクショナリに溜めようかとも考えたのですが、
文字列でアクセスするのがちょっと嫌だったので却下しました。

// 一行分のデータのモデル
public class HogeRow
{
    public string FugaName { get; set; }
    ...

    // key=権限区分@プロパティ名で Org, Mod の値を持つ
    private Dictionary<string, string> dict = new Dictionary<string, string>();
    public string GetFieldValue(string kengenKbn, string fieldName)
    {
        // 取得用メソッドなどを用意
    }
}
/*
  model.FugaName
  model.GetFieldValue("1", "FugaName")
  model.GetFieldValue("2", "FugaName")
  のようにアクセスする
*/

以上です。
もし分かる方がいらっしゃいましたら よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • sk_3122

    2018/05/15 16:41

    【as 演算子とか is 演算子】 共通の Util などでそれを使っている箇所があったので、as での変換ができるようになれば Util は修正しなくても良いのにな・・・ という気持ちだったので、致し方ないとあれば粛々と修正します。

    キャンセル

  • sk_3122

    2018/05/15 16:41

    【問題点2について】 やっぱり CustomValidationAttribute でしょうか・・・。こちらについても、「StringLengthAttribute などがそのまま使えれば 現在各プロパティにしていしている検証属性を全部修正して回らなくても済む」という思いだったので、CustomValidationAttribute を作って 参照箇所を全部直せばおそらくいけるとは思います。(修正箇所が結構多いので躊躇していました)

    キャンセル

  • sk_3122

    2018/05/15 16:42

    > 別々のスレッドに分けて質問した方が   そうですね。ちょっと自分でも質問内容が整理しきれていない部分があったかもしれません。ありがとうございました。

    キャンセル

回答 1

0

結局こうしました、というのを一応 書いておきます。

 現行のモデル

  • 入力フィールドに対応する string 型のプロパティを持っている
  • string の値を検証する 検証属性を指定している
public class HogeRow
{
    [Required]
    [StringLength(20)]
    public string HogeName { get; set; }

    [Required]
    [MyNumberRange(0, 9, Dot=False)]
    public string HogeKingaku { get; set; }
    ....
}

 修正後

  • 入力フィールドに対応するプロパティは MyField 型で定義
  • 検証属性も MyField 型のものに差し替え
public class HogeRow
{
    [MyField.Required]
    [MyField.StringLength(20)]
    public MyField HogeName { get; set; }

    [MyField.Required]
    [MyField.MyNumberRange(0, 9, Dot=False)]
    public MyField HogeKingaku { get; set; }
    ....
}
  • MyField を定義
public partial class MyField
{
    // フィールド値
    public string Value
    {
        get { return this._value; }
        set { this._value = value; }
    }
    private string _value = null;


    private string _org = null;
    private bool _existsOrg = false;

    // 下位権限者 入力データ
    public string Org
    {
        get { return this._org; }
        protected internal set
        {
            this._org = value;
            this._existsOrg = true;
        }
    }
    // 下位権限者 入力データがあるか
    public bool ExistsOrg
    {
        get { return this._existsOrg; }
    }


    private string _mod = null;
    private bool _existsMod = false;

    // 上位権限者 修正データ
    public string Mod
    {
        get { return this._mod; }
        protected internal set
        {
            this._mod = value;
            this._existsMod = true;
        }
    }
    // 上位権限者 修正データがあるか
    public bool ExistsMod
    {
        get { return _existsMod; }
    }


    // 修正されているかどうか
    public bool IsModified
    {
        get
        {
            // 修正データがない場合は「修正なし」
            if (this.ExistsMod == false) return false;

            // 両方空の場合は「修正なし」
            if (string.IsNullOrEmpty(this.Org) && string.IsNullOrEmpty(this.Mod)) return false;

            // 片方だけ空の場合は「修正あり」
            if (string.IsNullOrEmpty(this.Org) || string.IsNullOrEmpty(this.Mod)) return true;

            // 両方値がある場合は、値が異なるなら「修正あり」
            return (this.Org != this.Mod);
        }
    }

    // ToString() をオーバーライド
    public override string ToString()
    {
        return this.Value;
    }

    // MyField → string への変換演算子を定義する (implicit = 暗黙の変換)
    public static implicit operator string(MyField val)
    {
        if (val == null) return null;
        return val.Value;
    }
}
  • 必要な検証属性の MyField 対応版を用意する
public partial class MyField
{
    /// <summary>
    /// StringLengthAttribute を MyField に対応させる
    /// </summary>
    public class StringLengthAttribute : System.ComponentModel.DataAnnotations.StringLengthAttribute
    {
        public StringLengthAttribute(int maximumLength) : base(maximumLength)
        {
        }

        public override bool IsValid(object value)
        {
            var field = value as MyField;
            return base.IsValid(field.Value);
        }
    }

    ...
}
  • あとは表示部分はほぼ修正なしでOK(string への暗黙のキャストを定義している為)

  • 共通 Util などで as や is で見ている部分に関しては適宜修正する

  • プロパティに値を突っ込んでいるところは修正が必要

//row.FugaName = "あいう";
row.FugaName.Value = "あいう";

とりあえずこれで進めようと思います・・・が 今の時点においてもどういう実装が良いのか分からず 迷走している感・・・

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.83%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C#

    6017questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • ASP.NET MVC Framework

    42questions

    ASP.NET MVC Frameworkは、MVCパターンをベースとした、マイクロソフトのウェブアプリケーション開発用のフレームワークです。