Razor'daki Dinamik Anonim türü RuntimeBinderException özelliğine neden oluyor


156

Aşağıdaki hatayı alıyorum:

'object', 'RatingName' için bir tanım içermiyor

Anonim dinamik türe baktığınızda RatingName açıkça var.

Hatanın Ekran Görüntüsü

Bunu bir Tuple ile yapabileceğimin farkındayım, ancak hata mesajının neden oluştuğunu anlamak istiyorum.

Yanıtlar:


240

İç özelliklere sahip anonim türler, bence kötü bir .NET çerçeve tasarımı kararıdır.

İşte anonim nesneyi hemen bir ExpandoObject'e dönüştürerek bu sorunu çözmek için hızlı ve güzel bir uzantı .

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> anonymousDictionary =  new RouteValueDictionary(anonymousObject);
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var item in anonymousDictionary)
        expando.Add(item);
    return (ExpandoObject)expando;
}

Kullanımı çok kolay :

return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());

Tabii ki sizce:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}

2
+1 Özellikle HtmlHelper.AnonymousObjectToHtmlAttributes arıyordum Bu kesinlikle zaten pişmiş olduğunu biliyordu ve benzer handrolled kod ile tekerleği yeniden keşfetmek istemiyordu.
Chris Marisic

3
Güçlü tipte bir destek modeli yapmakla karşılaştırıldığında, bunun gibi bir performans nedir?
GONeale

@DotNetWise, IDictionary <string, object> anonymousDictionary = yeni RouteDictionary (nesne) yapabildiğinizde neden HtmlHelper.AnonymousObjectToHtmlAttributes kullanıyorsunuz?
Jeremy Boyd

HtmlHelper.AnonymousObjectToHtmlAttributes test ettik ve beklendiği gibi çalışıyor. Çözümünüz de işe yarayabilir. Hangisi daha kolay görünüyorsa kullanın :)
Adaptabi

Kalıcı bir çözüm olmasını istiyorsanız, denetleyicinizdeki davranışı geçersiz kılabilirsiniz, ancak anonim türleri tanımlayabilmek ve türden dize / nesne sözlüğünü kendiniz oluşturmak gibi birkaç geçici çözüm daha gerektirir. Bunu yaparsanız, bunu geçersiz kılabilirsiniz: korumalı geçersiz kılma System.Web.Mvc.ViewResult View (dize viewName, dize masterName, nesne modeli)
Johny

50

Cevabı ilgili bir soruda buldum . Cevap David Ebbo'nun blog yazısında belirtildi MVC görünümlerine anonim nesneler geçirme ve onlara dinamik olarak erişme

Bunun nedeni, anonim türün denetleyicide dahili olarak geçirilmesidir, bu nedenle yalnızca bildirildiği derlemeden erişilebilir. Görünümler ayrı olarak derlendiğinden, dinamik bağlayıcı bu montaj sınırını aşamayacağından şikayet eder.

Ancak düşünürseniz, dinamik bağlayıcıdan kaynaklanan bu kısıtlama aslında oldukça yapaydır, çünkü özel yansımayı kullanırsanız, hiçbir şey sizi bu iç üyelere erişmenizi engellemez (evet, Orta güvende bile çalışır). Dolayısıyla, varsayılan dinamik bağlayıcı, CLR çalışma zamanının izin verdiği şeyi yapmanıza izin vermek yerine, C # derleme kurallarını (dahili üyelere erişemediğiniz yerlerde) uygulamak için kendi yolundan gidiyor.


Beni alt :) benim Jilet Engine (on birine habercisi olan bu sorunu koştu razorengine.codeplex.com )
Buildstarted

Bu gerçekten bir cevap değil, "kabul edilen cevap" hakkında daha fazla şey söylemiyor!
Adaptabi

4
@DotNetWise: Sorunun neden hatanın oluştuğunu açıklar. Ayrıca güzel bir çözüm sağlamak için benim oy almak :)
Lucas

Bilginize: bu cevap şimdi çok güncel değil - yazar referans alınan blog
yazısının

@Simon_Weaver Ancak yayın güncellemesi MVC3 + 'da nasıl çalışması gerektiğini açıklamıyor. - MVC 4 aynı sorunu vurdu. Şu anda 'mübarek' dinamik kullanma yolu üzerinde herhangi bir işaretçi?
Cristian Diaconescu

24

ToExpando yöntemini kullanmak en iyi çözümdür.

System.Web derlemesi gerektirmeyen sürüm :

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(anonymousObject))
    {
        var obj = propertyDescriptor.GetValue(anonymousObject);
        expando.Add(propertyDescriptor.Name, obj);
    }

    return (ExpandoObject)expando;
}

1
Daha iyi bir cevap. HtmlHelper'ın alternatif cevapta alt çizgi ile ne yaptığını bilmiyorum.
Den

Genel amaçlı cevap için +1, bu ASP / MVC dışında yararlıdır
codenheim

iç içe dinamik özellikler ne olacak? dinamik olmaya devam edecekler ... ör .: {{foo: "foo", nestedDynamic: {blah: "blah"}}
spor

16

Anonim türden bir model oluşturmak ve anonim nesneyi buna benzer bir şekilde dönüştürmeye çalışmak yerine ExpandoObject...

var model = new 
{
    Profile = profile,
    Foo = foo
};

return View(model.ToExpando());  // not a framework method (see other answers)

ExpandoObjectDoğrudan doğrudan oluşturabilirsiniz :

dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;

return View(model);

Ardından görünümünüzde model türünü dinamik olarak ayarlarsınız @model dynamicve özelliklere doğrudan erişebilirsiniz:

@Model.Profile.Name
@Model.Foo

Normalde çoğu görünüm için kuvvetle yazılan görünüm modellerini öneririm, ancak bazen bu esneklik kullanışlıdır.


@yohal kesinlikle yapabilirsin - sanırım bu kişisel tercihtir. Belki şablona bağlı ve birincil model olarak Modeli tutmak - Ben sayfa modeline genellikle ilgisiz muhtelif sayfa verileri için ViewBag kullanmayı tercih
Simon_Weaver

2
BTW, varsayılan olarak model dinamik eklemek zorunda değilsiniz
yoel halb

tam olarak ne gerekli, anon objs genişletmek nesnelerine dönüştürmek için yöntem uygulamak çok fazla zaman alıyordu ...... teşekkürler yığınlar
h-rai

5

Anonim bir türü bir arabirime sarmak için framework doğaçlama arabirimini kullanabilirsiniz.

Sadece dönmek IEnumerable<IMadeUpInterface>ve Linq sonunda .AllActLike<IMadeUpInterface>();bu anonim türü bildirilen derleme bağlamıyla DLR kullanarak anonim özelliği çağırır çünkü bu işleri kullanın .


1
Harika küçük hile :) En azından bu durumda , kamu mülkleri bir grup ile sadece düz bir sınıf daha iyi olup olmadığını bilmiyorum .
Andrew Backer

4

Bir konsol uygulaması yazdı ve referans olarak Mono.Cecil'i ekleyin (şimdi NuGet'ten ekleyebilirsiniz ), ardından kod parçasını yazın:

static void Main(string[] args)
{
    var asmFile = args[0];
    Console.WriteLine("Making anonymous types public for '{0}'.", asmFile);

    var asmDef = AssemblyDefinition.ReadAssembly(asmFile, new ReaderParameters
    {
        ReadSymbols = true
    });

    var anonymousTypes = asmDef.Modules
        .SelectMany(m => m.Types)
        .Where(t => t.Name.Contains("<>f__AnonymousType"));

    foreach (var type in anonymousTypes)
    {
        type.IsPublic = true;
    }

    asmDef.Write(asmFile, new WriterParameters
    {
        WriteSymbols = true
    });
}

Yukarıdaki kod, derleme dosyasını giriş argümanlarından alır ve erişilebilirliği içten herkese değiştirmek için Mono.Cecil kullanır ve bu da sorunu çözer.

Programı web sitesinin Post Build etkinliğinde çalıştırabiliriz. Çince ile ilgili bir blog yazısı yazdım ama sadece kodu ve anlık görüntüleri okuyabileceğine inanıyorum. :)


2

Kabul edilen cevaba dayanarak, genel olarak ve sahne arkasında çalışmasını sağlamak için kontrolörde geçersiz kıldım.

İşte kod:

protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
    base.OnResultExecuting(filterContext);

    //This is needed to allow the anonymous type as they are intenal to the assembly, while razor compiles .cshtml files into a seperate assembly
    if (ViewData != null && ViewData.Model != null && ViewData.Model.GetType().IsNotPublic)
    {
       try
       {
          IDictionary<string, object> expando = new ExpandoObject();
          (new RouteValueDictionary(ViewData.Model)).ToList().ForEach(item => expando.Add(item));
          ViewData.Model = expando;
       }
       catch
       {
           throw new Exception("The model provided is not 'public' and therefore not avaialable to the view, and there was no way of handing it over");
       }
    }
}

Şimdi anonim bir nesneyi model olarak geçirebilirsiniz ve beklendiği gibi çalışacaktır.



0

RuntimeBinderException nedeni tetiklendi, diğer yazılarda iyi bir cevap olduğunu düşünüyorum. Sadece nasıl çalıştığını açıklamaya odaklanıyorum.

ASP.NET MVC'de Anonim tür koleksiyonuyla answer @DotNetWise ve Bağlama görünümlerine bakın ,

İlk olarak, uzantı için statik bir sınıf oluşturun

public static class impFunctions
{
    //converting the anonymous object into an ExpandoObject
    public static ExpandoObject ToExpando(this object anonymousObject)
    {
        //IDictionary<string, object> anonymousDictionary = new RouteValueDictionary(anonymousObject);
        IDictionary<string, object> anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject);
        IDictionary<string, object> expando = new ExpandoObject();
        foreach (var item in anonymousDictionary)
            expando.Add(item);
        return (ExpandoObject)expando;
    }
}

Denetleyicide

    public ActionResult VisitCount()
    {
        dynamic Visitor = db.Visitors
                        .GroupBy(p => p.NRIC)
                        .Select(g => new { nric = g.Key, count = g.Count()})
                        .OrderByDescending(g => g.count)
                        .AsEnumerable()    //important to convert to Enumerable
                        .Select(c => c.ToExpando()); //convert to ExpandoObject
        return View(Visitor);
    }

Görünümde, model IEnumerable (dinamik, model sınıfı değil), anonim tip nesnesini bağlayacağımız için bu çok önemlidir.

@model IEnumerable<dynamic>

@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
    <div>x=@item.nric, y=@item.count</div>
}

Foreach yazın, ben var veya dinamik kullanarak hiçbir hata var .

Bu arada, yeni alanlarla eşleşen yeni bir ViewModel oluşturmak da sonucu görünüme geçirmenin yolu olabilir.


0

Şimdi yinelemeli lezzette

public static ExpandoObject ToExpando(this object obj)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        new RouteValueDictionary(obj).ForEach(o => expandoObject.Add(o.Key, o.Value == null || new[]
        {
            typeof (Enum),
            typeof (String),
            typeof (Char),
            typeof (Guid),

            typeof (Boolean),
            typeof (Byte),
            typeof (Int16),
            typeof (Int32),
            typeof (Int64),
            typeof (Single),
            typeof (Double),
            typeof (Decimal),

            typeof (SByte),
            typeof (UInt16),
            typeof (UInt32),
            typeof (UInt64),

            typeof (DateTime),
            typeof (DateTimeOffset),
            typeof (TimeSpan),
        }.Any(oo => oo.IsInstanceOfType(o.Value))
            ? o.Value
            : o.Value.ToExpando()));

        return (ExpandoObject) expandoObject;
    }

0

ExpandoObject Uzantısını kullanmak iç içe anonim nesneler kullanılırken çalışır ancak kesilir.

Gibi

var projectInfo = new {
 Id = proj.Id,
 UserName = user.Name
};

var workitem = WorkBL.Get(id);

return View(new
{
  Project = projectInfo,
  WorkItem = workitem
}.ToExpando());

Bunu başarmak için kullanıyorum.

public static class RazorDynamicExtension
{
    /// <summary>
    /// Dynamic object that we'll utilize to return anonymous type parameters in Views
    /// </summary>
    public class RazorDynamicObject : DynamicObject
    {
        internal object Model { get; set; }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (binder.Name.ToUpper() == "ANONVALUE")
            {
                result = Model;
                return true;
            }
            else
            {
                PropertyInfo propInfo = Model.GetType().GetProperty(binder.Name);

                if (propInfo == null)
                {
                    throw new InvalidOperationException(binder.Name);
                }

                object returnObject = propInfo.GetValue(Model, null);

                Type modelType = returnObject.GetType();
                if (modelType != null
                    && !modelType.IsPublic
                    && modelType.BaseType == typeof(Object)
                    && modelType.DeclaringType == null)
                {
                    result = new RazorDynamicObject() { Model = returnObject };
                }
                else
                {
                    result = returnObject;
                }

                return true;
            }
        }
    }

    public static RazorDynamicObject ToRazorDynamic(this object anonymousObject)
    {
        return new RazorDynamicObject() { Model = anonymousObject };
    }
}

Denetleyicideki kullanım, ToExpando () yerine ToRazorDynamic () kullanmanız dışında aynıdır.

Anonim nesnenin tamamını almak için görünümünüzde sonuna ".AnonValue" ekleyin.

var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;

0

ExpandoObject denedim ama böyle bir iç içe anonim karmaşık türü ile işe yaramadı:

var model = new { value = 1, child = new { value = 2 } };

Benim çözümüm bir JObject to View modelini döndürmekti:

return View(JObject.FromObject(model));

ve .cshtml dosyasında dinamiğe dönüştürün:

@using Newtonsoft.Json.Linq;
@model JObject

@{
    dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.