WebSurfer's Home

Filter by APML

Controller で作成された匿名型は View でアクセス不可

by WebSurfer 11. October 2024 15:20

.NET Framework 版の ASP.NET MVC アプリでは、Controller で作成されて View に渡された匿名型のオブジェクトには View 内部ではアクセスできず、 アクセスしようとすると以下の画像のように RuntimeBinderException がスローされるということを書きます。

RuntimeBinderException

理由は、Microsoft のドキュメント「匿名型」に書いてあるように、「匿名型のアクセシビリティ レベルは internal であるため」です。internal 型またはメンバは、同じアセンブリのファイル内でのみしかアクセスできません。

.NET Framework 版の MVC アプリでは、Controller など拡張子が cs のファイルは Visual Studio で単一アセンブリにコンパイルされ、bin フォルダに配置されます。

一方、View (.cshtml) は、デフォルトではランタイムコンパイルとなり、アプリをデプロイした後サーバーで動的にアセンブリにコンパイルされ、サーバーの Temporary ASP.NET Files フォルダに保存されます。

という訳で、Controller と View とは違うアセンブリになるため、Controller で作成された匿名クラスのプロパティは View では見えず、アクセスしようとすると上の画像のように RuntimeBinderException がスローされます。

ただし、ASP.NET Core アプリの場合は、Controller と View はデフォルトで単一アセンブリにコンパイルされるので、上に書いたような問題は起きません。(ASP.NET Core のコンパイルについて、詳しくは Microsoft のドキュメント「ASP.NET Core での Razor ファイルのコンパイル」を見てください)

上の画像を表示した MVC アプリの Controler と View のコードを以下に載せておきます。Visual Studio 2022 のテンプレートを使って作成した .NET Framework 4.8 の MVC5 アプリです。

Controller

using System;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Net;
using System.Web.Mvc;
using Mvc5App2.Models;

namespace Mvc5App2.Controllers
{
    public class ProductsController : Controller
    {
        private NORTHWINDEntities db = new NORTHWINDEntities();


        public async Task<ActionResult> Test()
        {
            var products = db.Products
                .Select(p => new
                {
                    Id = p.ProductID,
                    Name = p.ProductName,
                    Price = p.UnitPrice
                });

            ViewBag.List = await products.ToListAsync();

            return View();
        }
    }
}

View

@{
    ViewBag.Title = "Test";
}

<h2>Test</h2>

<br />
<table  class="table">
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th>Price</th>
    </tr>
    @foreach (var item in ViewBag.List)
    {
        <tr>
            <td>@item.Id</td>
            <td>@item.Name</td>
            <td>@item.Price</td>
        </tr>
    }
</table>

解決策は、匿名型を使うのは止めて、以下のようなカスタムクラスを定義し、

public class DTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal? Price { get; set; }
}

以下のように List<DTO> 型のデータを生成して View に渡すことです。

public async Task<ActionResult> Test()
{
    var products = db.Products
        .Select(p => new DTO  // List<DTO> を生成
        {
            Id = p.ProductID,
            Name = p.ProductName,
            Price = p.UnitPrice
        });

    ViewBag.List = await products.ToListAsync();

    return View();
}

なお、上にも書きましたように、ASP.NET Core アプリの場合は、Controller と View はデフォルトで同じアセンブリにコンパイルされるので、上に書いた問題は起きません。

なので、匿名型を使っても以下の画像の通り期待した結果が得られます。

ASP.NET Core での結果

Tags: , ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  January 2025  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar