IT박스

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

itboxs 2020. 6. 7. 10:47
반응형

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


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

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

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

오류 스크린 샷

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


내부 속성을 가진 익명 유형은 .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>
}

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

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

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


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

익명 형식에서 모델을 만든 다음 익명 개체를 다음 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

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


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

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


콘솔 응용 프로그램을 작성하고 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을 사용하여 접근성을 내부에서 공개로 변경하면 문제가 해결됩니다.

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


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

코드는 다음과 같습니다.

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

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


https://stackoverflow.com/a/7478600/37055 에서 약간의 도둑질을 할 것입니다.

패키지 다이나마이트 를 설치 하면 다음을 수행 할 수 있습니다.

return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));

그리고 농민들은 기뻐합니다.


The reason of RuntimeBinderException triggered, I think there have good answer in other posts. I just focus to explain how I actually make it work.

By refer to answer @DotNetWise and Binding views with Anonymous type collection in ASP.NET MVC,

Firstly, Create a static class for extension

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

In controller

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

In View, @model IEnumerable (dynamic, not a model class), this is very important as we are going to bind the anonymous type object.

@model IEnumerable<dynamic>

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

The type in foreach, I have no error either using var or dynamic.

By the way, create a new ViewModel that is matching the new fields also can be the way to pass the result to the view.


Now in recursive flavor

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

Using the ExpandoObject Extension works but breaks when using nested anonymous objects.

Such as

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

var workitem = WorkBL.Get(id);

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

To accomplish this I use this.

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;

참고 URL : https://stackoverflow.com/questions/5120317/dynamic-anonymous-type-in-razor-causes-runtimebinderexception

반응형