Pull to refresh

Передача анонимных объектов в View

Reading time3 min
Views3.2K
Original author: davidebb

Идея состоит в том чтобы пользоваться моделью с новым более удобным dynamic синтаксисом. Главное ограничение тут в том, что нельзя просто передать анонимный объект как модель, потому что анонимные типы имеют модификатор доступа internal.


Предположим, что мы пытаемся написать этот код в контроллере:

public class HomeController : Controller
{
  public ActionResult Index()
  {
    return View(
      new
    {
        Message = "Welcome to ASP.NET MVC!",
        Date = DateTime.Now
      });
  }
}

* This source code was highlighted with Source Code Highlighter.


Обратите внимание, что мы передаем анонимный объект в модель. Главная причина этого — это избежать создание необходимого внешнего ViewModel типа. Очевидно, что это несколько спорный момент, но оно должно выглядеть проще чем такая альтернатива:

ViewData["Message"] = "Welcome to ASP.NET MVC!";
ViewData["Date"] = DateTime.Now;
return View();

* This source code was highlighted with Source Code Highlighter.


Затем мы изменяем View таким образом, чтобы он имел
Inherits="System.Web.Mvc.ViewPage<dynamic>"

* This source code was highlighted with Source Code Highlighter.


В идеале это должно позволить нам написать что-то типа этого:

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
  <h2><%= Model.Message %></h2>
  <p>
    The date is <%= Model.Date %>
  </p>
</asp:Content>

* This source code was highlighted with Source Code Highlighter.


Но по умолчанию dynamic binder не даст нам сделать это!

К сожалению, если вы попытаетесь запустить этот код, то будет:
error: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: '<>f__AnonymousType1<string,System.DateTime>.Message' is inaccessible due to its protection level
Причина этого анонимный тип в контроллере является internal, так что он может быть доступен только из той сборки в которой он определен. Так как view компилируется отдельно, dynamic binder жалуется, что не может выйти за пределы этой сборки. Но достаточно просто можно написать свой DynamicObject, который связывает через private reflection.

Здесь мы собираеся написать не только свой DynamicObject, но также свой DynamicViewPage, который использует его. Вот полная реализация:

public class DynamicViewPage : ViewPage {
  // Скрываем модель класса предка и заменяем его на dynamic
  public new dynamic Model { get; private set; }

  protected override void SetViewData(ViewDataDictionary viewData) {
    base.SetViewData(viewData);

    // Создаем динамический объект, который использует private рефлексию над объектом модели
    Model = new ReflectionDynamicObject() { RealObject = ViewData.Model };
  }

  class ReflectionDynamicObject : DynamicObject {
    internal object RealObject { get; set; }

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
      // Возвращаем значение свойства
      result = RealObject.GetType().InvokeMember(
        binder.Name,
        BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
        null,
        RealObject,
        null);

      // Всегда возвращаем true, так как InvokeMember вызовет исключение, если что-то пойдет не так
      return true;
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Как вы видите здесь все просто. Все что нам нужно это узнать значение свойства используя private reflection. Вот и все, осталось только использовать это как наш базовый класс для View.
<%@ Page Language=«C#» MasterPageFile="~/Views/Shared/Site.Master" Inherits=«MvcHelpers.DynamicViewPage» %>

Tags:
Hubs:
Total votes 26: ↑16 and ↓10+6
Comments6

Articles