File: DynamicViewData.cs
Web Access
Project: src\aspnetcore\src\Mvc\Mvc.ViewFeatures\src\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj (Microsoft.AspNetCore.Mvc.ViewFeatures)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using Microsoft.AspNetCore.Shared;

namespace Microsoft.AspNetCore.Mvc.ViewFeatures;

[DebuggerDisplay("Count = {ViewData.Count}")]
[DebuggerTypeProxy(typeof(DynamicViewDataDebugView))]
internal sealed class DynamicViewData : DynamicObject
{
    private readonly Func<ViewDataDictionary> _viewDataFunc;

    public DynamicViewData(Func<ViewDataDictionary> viewDataFunc)
    {
        ArgumentNullException.ThrowIfNull(viewDataFunc);

        _viewDataFunc = viewDataFunc;
    }

    private ViewDataDictionary ViewData
    {
        get
        {
            var viewData = _viewDataFunc();
            if (viewData == null)
            {
                throw new InvalidOperationException(Resources.DynamicViewData_ViewDataNull);
            }

            return viewData;
        }
    }

    // Implementing this function extends the ViewBag contract, supporting or improving some scenarios. For example
    // having this method improves the debugging experience as it provides the debugger with the list of all
    // properties currently defined on the object.
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return ViewData.Keys;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        ArgumentNullException.ThrowIfNull(binder);

        result = ViewData[binder.Name];

        // ViewDataDictionary[key] will never throw a KeyNotFoundException.
        // Similarly, return true so caller does not throw.
        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        ArgumentNullException.ThrowIfNull(binder);

        ViewData[binder.Name] = value;

        // Can always add / update a ViewDataDictionary value.
        return true;
    }

    private sealed class DynamicViewDataDebugView(DynamicViewData dictionary)
    {
        private readonly ViewDataDictionary _dictionary = dictionary.ViewData;

        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
        public DictionaryItemDebugView<string, object>[] Items => _dictionary.Select(pair => new DictionaryItemDebugView<string, object>(pair)).ToArray();
    }
}