web-dev-qa-db-ja.com

ASP.NET MVC5モデルバインディング編集ビュー

口頭で少しのコードで最もよく説明される問題の解決策を思い付くことができません。私はVS2013、MVC 5、およびEF6をコードファーストで使用しています。また、CRUD操作をサポートするコントローラーとビューを生成するMvcControllerWithContextスキャフォールドも使用しています。

簡単に言うと、CreatedDate値を含む単純なモデルがあります。

public class WarrantyModel
{
    [Key]
    public int Id { get; set; }
    public string Description { get; set; }
    DateTime CreatedDate { get; set; }
    DateTime LastModifiedDate { get; set; }
}

含まれているMVCスキャフォールドは、インデックス、作成、削除、詳細、および編集ビューに同じモデルを使用します。 I want 'create'ビューのCreatedDate; I しない編集ビューがサーバーにポストバックされたときに値を変更したくないので、「編集」ビューに表示したいのですが、だれも改ざんできないようにしたいのです。フォーム投稿中の値。

理想的には、CreatedDateが編集ビューに表示されないようにします。モデルのCreatedDateプロパティに配置できるいくつかの属性(たとえば、[ScaffoldColumn(false)])を見つけて、編集ビューに表示されないようにしましたが、CreatedDateが終了するため、ポストバックでバインディングエラーが発生します。 1/1/0001 12:00:00 AMの値で。これは、編集ビューがCreatedDateフィールドの値をコントローラーに返さないためです。

CreatedDate値を保持するテーブルにトリガーを追加するなど、SQLServerの変更を必要とするソリューションを実装したくありません。クイックフィックスを実行したい場合は、編集ビューが表示される前にCreatedDate(もちろんサーバー側)を保存してから、ポストバックでCreatedDateを復元します。これにより、0001年1月1日の日付を変更できます。ビューをレンダリングする前にデータベースからプルされたCreatedDateEF6に。そうすれば、CreatedDateを非表示のフォームフィールドとして送信し、ポストバック後にコントローラーでその値を上書きすることができますが、サーバー側の値を格納するための適切な戦略がありません(Session変数またはを使用したくないViewBag)。

[Bind(Exclude = "CreatedDate")]の使用を検討しましたが、それは役に立ちません。

コントローラのポストバックの編集機能のコードは次のようになります。

public ActionResult Edit([Bind(Include="Id,Description,CreatedDate,LastModifiedDate")] WarrantyModel warrantymodel)
{
    if (ModelState.IsValid)
    {
        db.Entry(warrantymodel).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(warrantymodel);
}

上記のifブロック内のdb.Entry(warrantymodel)オブジェクトを調べて、CreatedDateのOriginalValueを調べることができるかもしれないと思いましたが、その値にアクセスしようとすると(次に示すように)、タイプ 'System.InvalidOperationException'の例外:

var originalCreatedDate = db.Entry(warrantymodel).Property("CreatedDate").OriginalValue;

元のCreatedDate値(つまり、データベースにすでに存在する値)を正常に調べることができれば、CurrentValueが何であれ上書きすることができます。しかし、上記のコード行は例外を生成するため、他に何をすべきかわかりません。 (データベースに値を照会することを考えましたが、編集ビューがレンダリングされる前にデータベースがすでに値を照会されているため、それはばかげています)。

私が持っていた別のアイデアは、CreatedDate値のIsModified値をfalseに変更することでしたが、デバッグすると、前に示した「if」ブロックですでにfalseに設定されていることがわかりました。

bool createdDateIsModified = db.Entry(warrantymodel).Property("CreatedDate").IsModified;

私は、この一見単純な問題をどのように処理するかについての考えがありません。要約すると、モデルフィールドを編集ビューに渡したくないので、ビューの他の編集フィールドがポストバックされ、を使用してデータベースに永続化されたときに、そのフィールド(この例ではCreatedDate)が元の値を維持するようにします。 db.SaveChanges()。

どんな助け/考えも大歓迎です。

ありがとうございました。

6
Jazimov

ViewModelsを活用する必要があります。

public class WarrantyModelCreateViewModel
{
    public int Id { get; set; }
    public string Description { get; set; }
    DateTime CreatedDate { get; set; }
    DateTime LastModifiedDate { get; set; }
}

public class WarrantyModelEditViewModel
{
    public int Id { get; set; }
    public string Description { get; set; }
    DateTime LastModifiedDate { get; set; }
}

ViewModelの意図は、ドメインモデルの意図とは少し異なります。これは、適切にレンダリングするために必要な十分な情報をビューに提供します。

ViewModelsは、ドメインにまったく関係のない情報を保持することもできます。テーブルの並べ替えプロパティへの参照、または検索フィルターを保持できます。それらは確かにあなたのドメインモデルを置くのに意味がありません!

これで、コントローラーで、プロパティをViewModelsからドメインモデルにマップし、変更を永続化します。

public ActionResult Edit(WarrantyModelEditViewModel vm)
{
    if (ModelState.IsValid)
    {
        var warrant = db.Warranties.Find(vm.Id);
        warrant.Description = vm.Description;
        warrant.LastModifiedDate = vm.LastModifiedDate;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(warrantymodel);
}

さらに、ViewModelsは、複数のモデルからのデータをマージするのに最適です。保証の詳細ビューがあり、その保証の下で行われるすべてのサービスも確認したい場合はどうなりますか?次のようなViewModelを使用できます。

public class WarrantyModelDetailsViewModel
{
    public int Id { get; set; }
    public string Description { get; set; }
    DateTime CreatedDate { get; set; }
    DateTime LastModifiedDate { get; set; }
    List<Services> Services { get; set; }
}

ViewModelはシンプルで柔軟性があり、非常に人気があります。ここにそれらの良い外植があります: http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/

結局、たくさんのマッピングコードを書くことになります。 Automapperは素晴らしく、手間のかかる作業のほとんどを実行します: http://automapper.codeplex.com/

15
Mister Epic

これは質問に対する答えではありませんが、Bind()を使用していて、さまざまな問題に直面している人にとっては重要な場合があります。 「Bind()が既存のバインドされていない値をすべてクリアする理由」を検索していたときに、次のことがわかりました。

(HttpPost Edit()内)scaffolderはBind属性を生成し、モデルバインダーによって作成されたエンティティをModifiedフラグで設定されたエンティティに追加しました。 Bind属性は、Includeパラメーターにリストされていないフィールドの既存のデータをすべてクリアするため、このコードは推奨されなくなりました。将来的には、MVCコントローラースキャフォールダーが更新され、Editメソッドのバインド属性が生成されなくなります。

公式ページから(最終更新日は2015年3月): http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/implementing-basic -crud-functionality-with-the-entity-framework-in-asp-net-mvc-application#overpost

トピックによると:

  1. バインドは推奨されておらず、自動生成されたコードから将来​​削除される予定です。

  2. TryUpdateModel()が公式ソリューションになりました。

詳細については、トピックで「TryUpdateModel」を検索できます。

3
cheny

それはあなたの問題を解決するかもしれません

モデルの場合:使用しますか?

public class WarrantyModel
{
    [Key]
    public int Id { get; set; }
    public string Description { get; set; }
    DateTime? CreatedDate { get; set; }
    DateTime? LastModifiedDate { get; set; }
}

フォーム送信後:

public ActionResult Edit([Bind(Include = "Id,Description,CreatedDate,LastModifiedDate")] WarrantyModel warrantymodel)
{
    if (ModelState.IsValid)
    {
        db.Entry(warrantymodel).State = EntityState.Modified;
        db.Entry(warrantymodel).Property("CreatedDate").IsModified=false
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(warrantymodel);
}
2

チェニーの答えは+1。 Bindの代わりにTryUpdateModelを使用します。

public ActionResult Edit(int id)
{
    var warrantymodel = db.Warranties.Find(id);
    if (TryUpdateModel(warrantymodel, "", new string[] { "Id", "Description", "LastModifiedDate" }))
    {
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(warrantymodel);
}

ビューモデルを使用する場合は、オートマッパーを使用してnull値をスキップするように構成し、既存のデータがドメインモデルに引き続き存在するようにすることができます。

例:

モデル:

public class WarrantyModel
{
    public int Id { get; set; }
    public string Description { get; set; }
    DateTime CreatedDate { get; set; }
    DateTime? LastModifiedDate { get; set; }
}

ViewModel:

public class WarrantyViewModel
{
    public int Id { get; set; }
    public string Description { get; set; }
    DateTime? CreatedDate { get; set; }
    DateTime? LastModifiedDate { get; set; }
}

コントローラ:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="Id,Description,LastModifiedDate")] WarrantyViewModel warrantyViewModel)
{
    var warrantyModel = db.Warranties.Find(warrantyViewModel.Id);
    Mapper.Map(warrantyViewModel, warrantyModel);
    if (ModelState.IsValid)
    {
        db.Entry(warrantyModel).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(warrantyModel);
}

オートマッパー:

Mapper.CreateMap<WarrantyViewModel, WarrantyModel>()
    .ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
1
stenlytw

[編集]ビューの[日付プロンプトの作成]テキストボックスを削除してみてください。私のアプリケーションでは、スキャフォールドで生成された編集ビューと作成ビューには、データベースで生成された主キーが含まれています。

0
Barakat