Dynamic view of anonymous type missing member issue - MVC3

前端 未结 6 1997
春和景丽
春和景丽 2021-02-06 17:50

I have an MVC3 site that I\'ve setup for testing another site - most of it has been quick and dirty, and so I\'ve not gone to town creating model and view model types for all th

相关标签:
6条回答
  • 2021-02-06 18:32

    If you're just going for pure quick'n'dirty, you can always use Tuples in situations like this.

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

    -

    @foreach(dynamic item in ViewBag.SomeData)
    {
        @:Number: @item.Item1
    }
    
    0 讨论(0)
  • 2021-02-06 18:35

    Do you know the name of the assembly generated by the build manager? If so, you should be able to apply InternalsVisibleTo in the controller assembly, and it will all work fine.

    EDIT: One possible solution: create a public type extending DynamicObject in the model assembly which proxies any requests for properties down to your anonymous type via reflection. It would be ugly, but I think it should work... effectively making the anonymous types public.

    0 讨论(0)
  • 2021-02-06 18:45

    Okay sorry I just don't agree that this is horrible etc etc. Yes I would never consider doing this stuff on a production site - but for a prototyping, non-commercial, internal-only, for-testing-purposes I think using this approach is perfectly valid.

    This is what I've done (and I stress this only really addresses the issue adequately for anonymous types); lifting the members out and pushing them into an ExpandoObject.

    My initial change was to make the projection a multi-statement that returns an ExpandoObject:

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

    Which is almost as short as the anonymous type just not as clean.

    But then I wondered if I could possibly get away with grabbing the publicly readable members out of the anonymous type (possibly relies on compiler internals - the type is internal, but the properties it generates are public):

    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;
      }
    }
    

    Which means I can then use my original code - but with a little extension method call tagged on the end:

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

    And do you know what - I know it's not efficient, and most people will say it's ugly; but it'll saved me time and allow me to focus on writing features in my test site for my QA team to use in testing the real thing (whose code is, of course immaculate throughout :) ).

    0 讨论(0)
  • 2021-02-06 18:54

    I think ToSafeDynamic is a good solution. But I'd like to share some other options using the open source ImpromptuInterface (in nuget) that would work well in this situation.

    One option, being a DynamicObject based proxy, ImpromptuGet which will just forward calls to the annonymous type, similar to using dynamic (it uses the same api's except it sets the context to the anonymous type's self so the internal access doesn't matter).

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

    This option doesn't look as clean as ToSafeDynamic, but it has a small distinction in that it is only calling the properties when they are used, rather than copying all the data upfront.

    However, a better solution from ImpromptuInterface is it's quick syntax for creating prototype dynamic objects.

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

    that will create an expando-like object, but you also have the option to create literal ExpandoObjects (which in my testing offer the same performance on getters as POCO objects cast to dynamic).

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

    Also the creation setup portion can be stored in a temporary variable or fields to shorten the lambda even more.

    var Expando =Build<ExpandoObject>.NewObject;
    ViewBag.SomeData = Enumerable.Range(1,10).Select(i => Expando(Value:i));
    
    0 讨论(0)
  • 2021-02-06 18:55

    ASP.NET MVC dynamic view sections

    How about this?

    0 讨论(0)
  • 2021-02-06 18:57

    Obviously a 'proper' model type would solve the issue - but let's say I simply don't want to do that because that's my prerogative(!)

    You cannot have such prerogative. Sorry, there is absolutely no excuse for this :-) Not to mention that what you are trying to achieve is impossible because dynamic types are internal to the declaring assembly. Razor views are compiled into a separate dynamic assembly at runtime by the ASP.NET engine.

    So back to the topic: never pass anonymous objects as models to views. Always define use view models. Like this:

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

    and then:

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

    and then use a strongly typed view:

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

    and inside the corresponding display template which will automatically be rendered for each element of the collection so that you don't have to write any loops in your views (~/Views/Shared/DisplayTemplates/MyViewModel.cshtml):

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

    Things that we have improved from the initial version:

    • We have strongly typed views with Intellisense (and if you activate compilation for views even compile time safety)
    • Usage of strongly typed view models adapted to the specific requirements of your views.
    • Getting rid of ViewBag/ViewData which imply weak typing
    • Usage of display templates which avoid you writing ugly loops in your views => you rely on conventions and the framework does the rest
    0 讨论(0)
提交回复
热议问题