ASP.NETでViewからControllerへ値を投げる方法!

みなさんこんにちは!

初めは難しいと思っていたことでも、毎日ASP.NETの勉強をしていると、徐々に理解度が高くなってくるものですよね!

そんなこんなで 最近ASP.NETと親友になりつつあるひろぽんです!

今回は割とありがちなViewからControllerに値を渡したいときに覚えておきたい手法です!

私の場合Createメソッドを呼び出す際、GETで投げた値をPOSTでも利用したいのに、どうやって投げればわからない!!というところからスタートしています!

では早速!

目次

前提条件の確認!

💡 Controller を WinForms の Form_Load の延長として理解するなら、別記事 Controller は WinForms の Form_Load 拡張版だと理解する で WinForms 経験者向けに整理してます。

まずはこの記事で書いていくプログラムの前提内容を書いていきたいと思います!

このシステムを使っている人は車屋さんで、車輌に紐づく車検情報を登録して、顧客管理をしています。

車輛ページ(Details)には、車輛の詳細のほか、車検情報の一覧を見ることができます。

車輛ページ(Details)からできることは、車検証の登録、詳細閲覧、編集、削除です。

今回車輛の詳細と車検情報の一覧を表示するということで、CarAndInspectionというViewModelを作成しました。

実際のコードは以下の通り。

今回関係してくるコードはCarAndInspectionとInspectionLicenseだけなので、その点に絞って書いていきます!

CarAndInspection

using CarInspection.Data;
using CarInspection.Models;
using CarInspection.Models.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace CarInspection.Controllers {

/// <summary>
/// 車両情報と車検情報をを一覧で表示する
/// </summary>
[Authorize]
public class CarAndInspectionController : Controller
{

    private CarInspectionContext db = new CarInspectionContext();

    // GET: CarAndInspection
    public ActionResult Index()
    {
        return View();
    }

    [Authorize]
    public ActionResult Details(int? id)
    {
        var loginUser = GetLoginUser();

        if (id == null)
        {
            return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest);
        }
        var carAndInspection = new CarAndInspectionViewModel();

        var car = loginUser.Cars.FirstOrDefault(c => c.Id == id);

        if(car == null)
        {
            return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest);
        }

        carAndInspection.Car = car;
        carAndInspection.inspectionLicenses = car.InspectionLicenses;

        return View(carAndInspection);

    }

    private User GetLoginUser()
    {
        return db.Users.FirstOrDefault(u => u.UserName == User.Identity.Name);
    }
}

}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace CarInspection.Models.ViewModel { public class CarAndInspectionViewModel { public Car Car { get; set; } public IEnumerable<InspectionLicense> inspectionLicenses { get; set; } } }

InspectionLicense

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using CarInspection.Data;
using CarInspection.Models;
using CarInspection.Models.ViewModel;

namespace CarInspection.Controllers { [Authorize] public class InspectionLicensesController : Controller { private CarInspectionContext db = new CarInspectionContext();

    // GET: InspectionLicenses/Details/5
    public ActionResult Details(int? id,int? ParentCarId)
    {
        if (id == null || ParentCarId == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        InspectionLicense inspectionLicense = db.InspectionLicenses.Find(id);
        if (inspectionLicense == null)
        {
            return HttpNotFound();
        }

        ViewBag.ParentCarId = ParentCarId;
        return View(inspectionLicense);
    }

    // GET: InspectionLicenses/Create
    public ActionResult Create(int? ParentCarId)
    {
        if(ParentCarId == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        ViewBag.ParentCarId = ParentCarId; 
        return View();
    }

    // POST: InspectionLicenses/Create
    // 過多ポスティング攻撃を防止するには、バインド先とする特定のプロパティを有効にしてください。
    // 詳細については、https://go.microsoft.com/fwlink/?LinkId=317598 を参照してください。
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = &quot;Id,Name,Memo,Expiration,CarId&quot;)] InspectionLicense inspectionLicense)
    {
        var carid = inspectionLicense.CarId;

    
        var car = db.Cars.FirstOrDefault(c =&gt; c.Id == inspectionLicense.CarId);

        if (ModelState.IsValid)
        {
            inspectionLicense.Created = DateTime.Now;
            inspectionLicense.Updated = DateTime.Now;
            inspectionLicense.Car = car;
            db.InspectionLicenses.Add(inspectionLicense);
            db.SaveChanges();
            return RedirectToCarAndInspectionDetails(inspectionLicense.CarId);
        }

        return RedirectToCarAndInspectionDetails(inspectionLicense.CarId);
    }

    // GET: InspectionLicenses/Edit/5
    public ActionResult Edit(int? id,int? ParentCarId)
    {
        if (id == null || ParentCarId == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        InspectionLicense inspectionLicense = db.InspectionLicenses.Find(id);
        if (inspectionLicense == null)
        {
            return HttpNotFound();
        }

        ViewBag.ParentCarId = ParentCarId;
        return View(inspectionLicense);
    }

    // POST: InspectionLicenses/Edit/5
    // 過多ポスティング攻撃を防止するには、バインド先とする特定のプロパティを有効にしてください。
    // 詳細については、https://go.microsoft.com/fwlink/?LinkId=317598 を参照してください。
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = &quot;Id,Name,Memo,Expiration,CarId&quot;)] InspectionLicense inspectionLicense)
    {
        if (ModelState.IsValid)
        {
            inspectionLicense.Updated = DateTime.Now;
            db.Entry(inspectionLicense).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToCarAndInspectionDetails(inspectionLicense.CarId);
        }
        return RedirectToCarAndInspectionDetails(inspectionLicense.CarId);
    }

    // GET: InspectionLicenses/Delete/5
    public ActionResult Delete(int? id,int? ParentCarId)
    {
        if (id == null || ParentCarId == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        InspectionLicense inspectionLicense = db.InspectionLicenses.Find(id);
        if (inspectionLicense == null)
        {
            return HttpNotFound();
        }
        ViewBag.ParentCarId = ParentCarId;
        return View(inspectionLicense);
    }

    // POST: InspectionLicenses/Delete/5
    [HttpPost, ActionName(&quot;Delete&quot;)]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int? id, int? ParentCarId)
    {
        if (ParentCarId == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        InspectionLicense inspectionLicense = db.InspectionLicenses.Find(id);
        db.InspectionLicenses.Remove(inspectionLicense);
        db.SaveChanges();

        return RedirectToCarAndInspectionDetails(ParentCarId);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }

    private ActionResult RedirectToCarAndInspectionDetails(int? carId)
    {
        if(carId == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        else
        {
            return RedirectToAction(&quot;Details&quot;, &quot;CarAndInspection&quot;,new { id = carId});
        }

    }
}

}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
using Microsoft.SqlServer.Server;

namespace CarInspection.Models { public class InspectionLicense { public int Id { get; set; }

    [DisplayName(&quot;車検証名&quot;)]
    public string Name { get; set; }
    
    [DisplayName(&quot;備考&quot;)]
    public string Memo { get; set; }
    
    [DisplayName(&quot;満了日&quot;)]
    [DataType(DataType.Date)]
    public DateTime Expiration { get; set; }

    public DateTime Created { get; set; }
    public DateTime Updated { get; set; }

    public virtual Car Car { get; set; }

    [NotMapped]
    public int CarId { get; set; }

}

}

どのように値を投げていくのか?

続いて全体的にどのような流れで値を投げていくのかについて解説していきます。

  • CarAndInspectionのDetailsのViewからInspectionLicenseのGetのCreateメソッドへDetailsで現在見ているIDを持たせたい。
  • CreateのGetメソッドから、Postメソッドに上記で受け取ったIDを投げたい
  • CreateでPostが終わったら、CarAndInspectionのDetailsに上記IDとともに返したい。

DetailsのViewsからGETのCreateへ値を投げる。

これは割とシンプルです!

GetのCreateに引数を書きます。

        // GET: InspectionLicenses/Create
        public ActionResult Create(int? ParentCarId)
        {
            if(ParentCarId == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            ViewBag.ParentCarId = ParentCarId; 
            return View();
        }

Views側で上記パラメーターを渡します。

        <td colspan="4">
            @Html.ActionLink("車検証追加","Create",new {Controller = "InspectionLicenses",ParentCarId = Model.Car.Id})
        </td>

GetからPOSTへ値を引き継ぐ

まずはGetで呼ばれたCreateメソッド内で、ViewBagを使って Viewにデータを渡します。

            ViewBag.ParentCarId = ParentCarId; 
            return View();

上記コードのこの部分ですね!

続いてControllerに対するModelに値を補完する用の箱を用意します。

        [NotMapped]
        public int CarId { get; set; }

DBにカラムを作成したくないので、NotMappedアノテーションを設定します。

続いてCreateのViewsで上記箱にViewBagのデータを入れていきます。

        @Html.Hidden("CarId",(int)@ViewBag.ParentCarId)

この際@ViewBagはDynamic型なので、任意の型にCastします。

このようにすれば、PostのCreateメソッドでCarIdを受け取れるはずです。

        // POST: InspectionLicenses/Create
        // 過多ポスティング攻撃を防止するには、バインド先とする特定のプロパティを有効にしてください。
        // 詳細については、https://go.microsoft.com/fwlink/?LinkId=317598 を参照してください。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "Id,Name,Memo,Expiration,CarId")] InspectionLicense inspectionLicense)
        {
            var carid = inspectionLicense.CarId;

        
        var car = db.Cars.FirstOrDefault(c =&gt; c.Id == inspectionLicense.CarId);

        if (ModelState.IsValid)
        {
            inspectionLicense.Created = DateTime.Now;
            inspectionLicense.Updated = DateTime.Now;
            inspectionLicense.Car = car;
            db.InspectionLicenses.Add(inspectionLicense);
            db.SaveChanges();
            return RedirectToCarAndInspectionDetails(inspectionLicense.CarId);
        }

        return RedirectToCarAndInspectionDetails(inspectionLicense.CarId);
    }</code></pre></div>

このようにしてModel経由で値を受け取れれば、最後はRedirectします。

        private ActionResult RedirectToCarAndInspectionDetails(int? carId)
        {
            if(carId == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            else
            {
                return RedirectToAction("Details", "CarAndInspection",new { id = carId});
            }

    }</code></pre></div>

以上!

💡 補足: 業務系の現場でよくハマるパターン

俺もこの View → Controller の値渡し、何度もやってますが、ハマるところは決まってます。

① Model Binder の name 属性ミス

HTML の name="UserName" が C# の Model プロパティ名と一致してないと、 Controller 側で値が null になる。 form 要素の name = Model property name が絶対則。 Razor の @Html.TextBoxFor(m => m.UserName) を使うと自動で揃うので推奨。

② [HttpPost] / [HttpGet] 指定漏れで GET POST 両方ヒット

Action メソッドに HTTP verb 属性を付け忘れると GET も POST もヒットする。 ブラウザ Back で意図せず Action 再実行 → 二重登録の事故。 form 送信用 Action は必ず [HttpPost]

③ AntiForgeryToken 未配置で CSRF

View 側 @Html.AntiForgeryToken() / Controller 側 [ValidateAntiForgeryToken] はワンセット。片方だけだと CSRF 対策にならない。フォーム作る時の儀式として叩き込む。

❓ よくある質問

Q1. ViewBag / ViewData / TempData の使い分けは?

A. ViewBag/ViewData は同一リクエスト内(Controller → View)、TempData は次リクエストまで保持(リダイレクト跨ぎ)。型安全じゃないので、本来は Model 経由が定石。

Q2. ModelState.IsValid って何を見てる?

A. Model クラスに付けた [Required][StringLength] 等の DataAnnotation バリデーションの結果。 Controller の入口で必ずチェックする。

Q3. Web API と MVC Controller の違いは?

A. MVC Controller は View を返す前提(HTML)、 Web API は JSON / XML を返す前提(データ)。 .NET Core 以降は統合されて Controller + ControllerBase で書き分け。

Q4. Form POST 後にデータが消える対策は?

A. PRG パターン(Post-Redirect-Get)。 POST 処理後に RedirectToAction で GET にリダイレクトすると、ブラウザリロードで二重送信しない。 TempData でメッセージ持ち回り。

Q5. WinForms から MVC に移行する時の判断軸は?

A. 業務系の社内ツールなら Blazor Server の方がスムーズ(WinForms に近い)。 外部公開や複数端末対応なら MVC / Razor Pages。 WinForms と MVC の対応関係 で詳しく書いてます。

📚 関連記事

この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次