Динамическое представление об отсутствии члена анонимного типа - MVC3 - PullRequest
3 голосов
/ 15 сентября 2011

У меня есть сайт MVC3, который я настроил для тестирования другого сайта - большинство из них были быстрыми и грязными, и поэтому я не ездил в город, создавая модели и типы моделей для всех видов - только там, где ввод запрашивается у пользователя.

Хорошо, у меня есть метод контроллера, который проецирует последовательность Linq и устанавливает ее в ViewBag.

ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new { Value = i });

На мой взгляд (Razor C #) я хочу прочитать это - довольно просто:

@foreach(dynamic item in ViewBag.SomeData)
{
  @:Number: @item.i
}

За исключением, конечно, я получаю RuntimeBinderException, потому что анонимный тип, созданный в контроллере, является внутренним по отношению к выходной сборке веб-проекта, и фактический код Razor здесь будет выполняться в другой сборке, сгенерированной менеджером сборки, поэтому в целом ОТКАЗАНО!

Очевидно, что «правильный» тип модели решит проблему - но, скажем, я просто не хочу этого делать, потому что это моя прерогатива (!) - как лучше всего свести код к минимуму и сохранить динамичность здесь

Ответы [ 6 ]

4 голосов
/ 15 сентября 2011

Хорошо, извините, я просто не согласен с тем, что это ужасно и т. Д. И т. Д. Да, я бы никогда не подумал о том, чтобы заняться этим на производственной площадке - но для прототипирования, некоммерческого, только для внутреннего использования, для целей тестирования ядумаю, что использование этого подхода вполне допустимо.

Это то, что я сделал (и я подчеркиваю, что это действительно решает проблему только адекватно для анонимных типов);поднимая элементы и выталкивая их в ExpandoObject.

Моим первоначальным изменением было сделать проекцию мульти-оператором, который возвращает ExpandoObject:

ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> {
  var toReturn = new ExpandoObject();
  toReturn.Value = i;
  return toReturn;
});

, что почтиКороче говоря, анонимный тип просто не так чист.

Но потом я подумал, можно ли мне сойти с рук, извлекая общедоступные члены из анонимного типа (возможно, полагается на внутренние компоненты компилятора - тип внутренний,но генерируемые им свойства являются общедоступными):

public static class SO7429957
{
  public static dynamic ToSafeDynamic(this object obj)
  {
    //would be nice to restrict to anonymous types - but alas no.
    IDictionary<string, object> toReturn = new ExpandoObject();

    foreach (var prop in obj.GetType().GetProperties(
      BindingFlags.Public | BindingFlags.Instance)
      .Where(p => p.CanRead))
    {
      toReturn[prop.Name] = prop.GetValue(obj, null);
    }

    return toReturn;
  }
}

Это означает, что я могу затем использовать свой исходный код - но с небольшим вызовом метода расширения, помеченным в конце:

ViewBag.SomeData=Enumerable.Range(1,10).Select(i=> new { Value = i }.ToSafeDynamic());

ИЗнаете ли вы, что - я знаю, что это не эффективно, и большинство людей скажут, что это уродливо;но это сэкономит мне время и позволит сосредоточиться на написании функций на моем тестовом сайте, чтобы моя команда QA могла использовать их для тестирования реальных вещей (чей код, конечно, безупречен во всем :)).

1 голос
/ 15 сентября 2011

Я думаю ToSafeDynamic - хорошее решение.Но я хотел бы поделиться некоторыми другими опциями, использующими открытый исходный код ImpromptuInterface (в nuget), которые бы хорошо работали в этой ситуации.

Один из вариантов - использование прокси на основе DynamicObject, ImpromptuGet который будет просто переадресовывать вызовы к анонимному типу, аналогично использованию динамического (он использует те же API-интерфейсы, за исключением того, что устанавливает контекст для анонимного типа self, поэтому internal доступ не имеет значения).

ViewBag.SomeData = Enumerable.Range(1,10)
                           .Select(i => ImpromptuGet.Create(new { Value = i }));

Эта опция не выглядит такой же чистой, как ToSafeDynamic, но имеет небольшое отличие в том, что она вызывает только свойства, когда они используются, а не копирует все данные заранее.

Однако лучшерешение ImpromptuInterface - это быстрый синтаксис для создания прототипов динамических объектов.

ViewBag.SomeData = Enumerable.Range(1,10).Select(i => Build.NewObject(Value:i));

, который создаст объект, подобный эксплоо, но у вас также есть возможность создать литерал ExpandoObject s (которые в моем тестировании показали ту же производительность на геттерах, что и объекты POCO, приведенные к динамическим).

ViewBag.SomeData = Enumerable.Range(1,10)
                           .Select(i => Build<ExpandoObject>.NewObject(Value:i));

Также можно ограничить часть настройки созданиядобавьте во временную переменную или поля, чтобы еще больше сократить лямбду.

var Expando =Build<ExpandoObject>.NewObject;
ViewBag.SomeData = Enumerable.Range(1,10).Select(i => Expando(Value:i));
1 голос
/ 15 сентября 2011

Очевидно, что «правильный» тип модели решит проблему - но допустим, я просто не хочу этого делать, потому что это моя прерогатива (!)

У вас не может быть такихпрерогатива.К сожалению, нет абсолютно никакого оправдания этому :-) Не говоря уже о том, что то, что вы пытаетесь достичь, невозможно, потому что динамические типы являются внутренними для декларирующей сборки.Представления Razor компилируются в отдельную динамическую сборку во время выполнения движком ASP.NET.

Итак, вернемся к теме: никогда не передавайте анонимные объекты в качестве моделей в представления.Всегда определяйте модели использования вида.Например:

public class MyViewModel
{
    public int Value { get; set; }
}

, затем:

public ActionResult Index()
{
    var model = Enumerable.Range(1, 10).Select(i => new MyViewModel { Value = i });
    return View(model);
}

, а затем используйте строго типизированное представление:

@model IEnumerable<MyViewModel>
@Html.DisplayForModel()

и внутри соответствующего шаблона отображения, который автоматическиотображаться для каждого элемента коллекции, чтобы вам не приходилось писать какие-либо циклы в ваших представлениях (~/Views/Shared/DisplayTemplates/MyViewModel.cshtml):

@model MyViewModel
@:Number: @Html.DisplayFor(x => x.Value)

Вещи, которые мы улучшили по сравнению с первоначальной версией:

  • Мы имеем строго типизированные представления с Intellisense (и если вы активируете компиляцию для представлений, даже безопасность во время компиляции)
  • Использование строго типизированных моделей представлений, адаптированных к конкретным требованиям ваших представлений.
  • Избавление от ViewBag / ViewData, которые подразумевают слабую типизацию
  • Использование шаблонов отображения, которые не позволяют вам создавать некрасивые циклы в ваших представлениях => вы полагаетесь на соглашения, а остальное делает инфраструктура
0 голосов
/ 04 апреля 2013

Если вы просто стремитесь к чистому быстрому и грязному, вы всегда можете использовать кортежи в подобных ситуациях.

ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new Tuple<int>(i);

-

@foreach(dynamic item in ViewBag.SomeData)
{
    @:Number: @item.Item1
}
0 голосов
/ 15 сентября 2011
0 голосов
/ 15 сентября 2011

Знаете ли вы имя сборки, сгенерированной менеджером сборки? Если это так, вы сможете применить InternalsVisibleTo в сборке контроллера, и все будет работать нормально.

РЕДАКТИРОВАТЬ: Одно из возможных решений: создать общедоступный тип с расширением DynamicObject в сборке модели, который через рефлексию передает любые запросы свойств вплоть до вашего анонимного типа. Это было бы ужасно, но я думаю это должно сработать ... эффективно сделать анонимные типы общедоступными.

...