Razor의 동적 익명 유형으로 인해 RuntimeBinderException이 발생 함


156

다음과 같은 오류가 발생합니다.

'object'에 'RatingName'에 대한 정의가 없습니다.

익명의 동적 유형을 보면 명확하게 RatingName이 있습니다.

오류 스크린 샷

Tuple 로이 작업을 수행 할 수 있다는 것을 알고 있지만 오류 메시지가 발생하는 이유를 알고 싶습니다.

답변:


240

내부 속성을 가진 익명 유형은 .NET 프레임 워크 디자인 결정이 좋지 않습니다.

다음은 익명 개체를 ExpandoObject로 즉시 변환하여이 문제를 해결 하는 빠르고 멋진 확장 입니다.

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

사용 하기 매우 쉽습니다 :

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

물론 당신의 관점에서 :

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

2
+1 나는 특별히 HtmlHelper.AnonymousObjectToHtmlAttributes를 찾고 있었는데, 이것이 이미 완전히 구워 져야한다는 것을 알고 있었고 유사한 수동 코드로 바퀴를 재발 명하고 싶지 않았습니다.
Chris Marisic

3
단순히 강력한 형식의 백업 모델을 만드는 것과 비교하여 이와 같은 성능은 무엇입니까?
GONeale

@DotNetWise, IDictionary <string, object> anonymousDictionary = new RouteDictionary (object)를 수행 할 수 있는데 왜 HtmlHelper.AnonymousObjectToHtmlAttributes를 사용합니까?
Jeremy Boyd

HtmlHelper.AnonymousObjectToHtmlAttributes를 테스트했으며 예상대로 작동합니다. 귀하의 솔루션도 효과가 있습니다. 더 쉬운 것 같아 :)
Adaptabi

영구적 인 솔루션이 되려면 컨트롤러의 동작을 재정의 할 수도 있지만 익명 형식을 식별하고 형식에서 문자열 / 개체 사전을 직접 만드는 등 몇 가지 해결 방법이 필요합니다. 그렇게하면 다음과 같이 재정의 할 수 있습니다. protected override System.Web.Mvc.ViewResult View (string viewName, string masterName, object model)
Johny Skovdal

50

관련 질문 에서 답을 찾았습니다 . 답변은 David Ebbo의 블로그 게시물에 명시되어 있습니다. 익명 객체를 MVC보기로 전달하고 동적을 사용하여 액세스

그 이유는 익명 형식이 컨트롤러의 내부에 전달되므로 선언 된 어셈블리 내에서만 액세스 할 수 있기 때문입니다. 뷰가 개별적으로 컴파일되므로 동적 바인더는 해당 어셈블리 경계를 넘을 수 없다고 불평합니다.

그러나 당신이 그것에 대해 생각하면, 동적 바인더의 이러한 제한은 실제로 매우 인공적입니다. 개인 반사를 사용하면 그 내부 구성원에 액세스하는 것을 막을 수있는 것은 없습니다 (그렇습니다, 심지어 중간 신뢰에서도 작동합니다). 따라서 기본 동적 바인더는 CLR 런타임에서 허용하는 작업을 수행하는 대신 C # 컴파일 규칙 (내부 멤버에 액세스 할 수없는 위치)을 적용하지 못하고 있습니다.


그것에 나를 이길 :) 내 면도기 엔진 (에 하나 전구체와이 문제에 달렸다 razorengine.codeplex.com )
Buildstarted

이것은 "답변 된 대답"에 대해 더 많이 말하지 않는 대답이 아닙니다!
Adaptabi

4
@DotNetWise : 오류가 발생한 이유를 설명합니다. 이것이 문제였습니다. 당신은 또한 좋은 해결 방법을 제공하는 나의 공감대를 얻습니다 :)
Lucas

참고 :이 대답은 매우 오래된 지금 - 저자가 참조 된 블로그 게시물의 시작 부분에 빨간색으로 자신을 말하는대로
Simon_Weaver

@Simon_Weaver 그러나 포스트 업데이트는 MVC3 +에서 어떻게 작동해야하는지 설명하지 않습니다. -MVC 4에서도 같은 문제가 발생했습니다. 현재 동적으로 사용하는 '축복적인'방법에 대한 조언이 있습니까?
Cristian Diaconescu

24

ToExpando 방법을 사용 하는 것이 가장 좋습니다.

System.Web 어셈블리가 필요없는 버전은 다음과 같습니다 .

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
더 나은 대답입니다. 대체 답변에서 HtmlHelper가 밑줄로 수행하는 것과 같은지 확실하지 않습니다.
Den

범용 답변 +1,이 ASP / MVC의 유용한 외부입니다
codenheim

중첩 된 동적 속성은 어떻습니까? 예를 들어`{foo : "foo", nestedDynamic : {blah : "blah"}}
sports

16

익명 형식에서 모델을 만든 다음 익명 개체를 다음 ExpandoObject과 같이 변환하려고 시도하는 대신 ...

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

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

ExpandoObject직접 만들 수 있습니다 .

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

return View(model);

그런 다음 뷰에서 모델 유형을 동적으로 설정 @model dynamic하고 속성에 직접 액세스 할 수 있습니다.

@Model.Profile.Name
@Model.Foo

일반적으로 대부분의 뷰에 대해 강력한 형식의 뷰 모델을 권장하지만 때로는 이러한 유연성이 유용합니다.


@yohal 당신은 확실히 할 수 있습니다-개인적인 취향이라고 생각합니다. 일반적으로 페이지 모델과 관련이없는 기타 페이지 데이터에 ViewBag를 사용하는 것을 선호합니다. 템플릿과 관련이있을 수 있고 Model을 기본 모델로 유지
Simon_Weaver

2
BTW @model dynamic을 기본값으로 추가하지 않아도됩니다.
yoel halb

정확히 내가 필요로하는, anon obj를 expando 객체로 변환하는 방법을 구현하는 데 너무 많은 시간이 걸렸습니다 ... 감사 힙
h-rai

5

프레임 워크 즉석 인터페이스 를 사용하여 인터페이스 에서 익명 유형을 래핑 할 수 있습니다 .

IEnumerable<IMadeUpInterface>Linq 사용을 반환 .AllActLike<IMadeUpInterface>();하면 익명 유형을 선언 한 어셈블리 컨텍스트와 함께 DLR을 사용하여 익명 속성을 호출하기 때문에 Linq 사용 이 끝납니다 .


1
굉장 작은 트릭 :이 공용 속성의 무리와 함께 더 좋은 단지 일반 클래스보다 클 경우, 음주 모르는,하지만 적어도 경우.
Andrew Backer

4

콘솔 응용 프로그램을 작성하고 Mono.Cecil을 참조로 추가 한 다음 ( NuGet 에서 추가 할 수 있음 ) 코드를 작성하십시오.

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

위의 코드는 입력 인수에서 어셈블리 파일을 가져오고 Mono.Cecil을 사용하여 액세스 가능성을 내부에서 공개로 변경하면 문제가 해결됩니다.

웹 사이트의 빌드 후 이벤트에서 프로그램을 실행할 수 있습니다. 나는 이것에 관한 블로그 게시물을 중국어로 썼지 만 코드와 스냅 샷을 읽을 수 있다고 생각합니다. :)


2

허용 된 답변을 바탕으로 컨트롤러에서 일반적인 상황과 뒤에서 작동하도록 재정의했습니다.

코드는 다음과 같습니다.

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

이제 익명 개체를 모델로 전달하면 예상대로 작동합니다.



0

RuntimeBinderException이 발생하는 이유는 다른 게시물에 좋은 대답이 있다고 생각합니다. 나는 실제로 어떻게 작동하는지 설명하는 데 집중합니다.

ASP.NET MVC의 Anonymous Type 컬렉션을 사용하여 @DotNetWise 및 바인딩 뷰 를 참조하십시오 .

먼저 확장을위한 정적 클래스 만들기

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

컨트롤러에서

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

@model IEnumerable 뷰 (모델 클래스가 아닌 동적)에서는 익명 유형 객체를 바인딩 할 때 매우 중요합니다.

@model IEnumerable<dynamic>

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

foreach의 유형으로 var 또는 dynamic을 사용하는 동안 오류가 없습니다 .

그건 그렇고, 새로운 필드와 일치하는 새로운 ViewModel을 생성하면 결과를 뷰에 전달할 수 있습니다.


0

이제 재귀 맛

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 Extension을 사용하면 중첩 된 익명 객체를 사용할 때 작동하지만 중단됩니다.

와 같은

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

var workitem = WorkBL.Get(id);

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

이것을 달성하기 위해 이것을 사용합니다.

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

컨트롤러의 사용법은 ToExpando () 대신 ToRazorDynamic ()을 사용하는 것을 제외하고 동일합니다.

전체 익명 객체를 얻으려면 끝에 ".AnonValue"를 추가하면됩니다.

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

0

ExpandoObject를 시도했지만 다음과 같이 중첩 된 익명의 복잡한 유형에서는 작동하지 않았습니다.

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

그래서 내 솔루션은 JObject를 View 모델로 반환하는 것이 었습니다.

return View(JObject.FromObject(model));

.cshtml에서 동적으로 변환하십시오.

@using Newtonsoft.Json.Linq;
@model JObject

@{
    dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.