Fix WebFx-169 and #118
- move `DynamicObject` derivation up to new `DynamicViewData` class, fixing [WebFx-169](http://projectk-tc:8080/browse/WEBFX-169) - avoid direct `_data` lookup in previous `TryGetMember()`, fixing [#118](https://github.com/aspnet/WebFx/issues/118) - rename ViewData -> ViewDataDictionary Also - flesh out `IDictionary<string, object>` implementation in `ViewData` - provide `ViewData` copy constructor that allows TModel to change - remove `TryGetIndex()` and `TrySetIndex()` implementations; use `ViewData[]` instead - restore `ViewContext.ViewBag` from legacy MVC
This commit is contained in:
parent
af7d61113b
commit
8ed5b7b079
|
|
@ -1,24 +1,23 @@
|
||||||
@using MvcSample.Web.Models
|
@using MvcSample.Web.Models
|
||||||
@model User
|
@model User
|
||||||
@{
|
@{
|
||||||
string nullValue = null;
|
|
||||||
ViewBag.Title = (Model == null) ? "Create Page" : "Edit Page";
|
ViewBag.Title = (Model == null) ? "Create Page" : "Edit Page";
|
||||||
if (ViewData["Gift"] == null)
|
if (ViewBag.Gift == null)
|
||||||
{
|
{
|
||||||
ViewBag.Gift = "nothing";
|
ViewBag.Gift = "nothing";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h2 title="@ViewBag.Title" class="@nullValue">@ViewBag.Title</h2>
|
<h2 title="@ViewBag.Title" class="@ViewBag.NullValue">@ViewBag.Title</h2>
|
||||||
<h3 title="Thanks" class="@nullValue">Thanks for @ViewBag.Gift</h3>
|
<h3 title="Thanks" class="@ViewBag.NullValue">Thanks for @ViewBag.Gift</h3>
|
||||||
@if (Model == null)
|
@if (Model == null)
|
||||||
{
|
{
|
||||||
<h4 title ="Null Model" class="@nullValue">Howdy, your model is null.</h4>
|
<h4 title ="Null Model" class="@ViewBag.NullValue">Howdy, your model is null.</h4>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<h4 title="@Model.Name" class="@nullValue">Hello @Model.Name! Happy @Model.Age birthday.</h4>
|
<h4 title="@Model.Name" class="@ViewBag.NullValue">Hello @Model.Name! Happy @(Model.Age)th birthday.</h4>
|
||||||
}
|
}
|
||||||
|
|
||||||
@{
|
@{
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return new JsonResult(value);
|
return new JsonResult(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult View(string view, ViewData viewData)
|
public IActionResult View(string view, ViewDataDictionary viewData)
|
||||||
{
|
{
|
||||||
return new ViewResult(_serviceProvider, _viewEngine)
|
return new ViewResult(_serviceProvider, _viewEngine)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
public string ViewName {get; set; }
|
public string ViewName {get; set; }
|
||||||
|
|
||||||
public ViewData ViewData { get; set; }
|
public ViewDataDictionary ViewData { get; set; }
|
||||||
|
|
||||||
public async Task ExecuteResultAsync([NotNull] ActionContext context)
|
public async Task ExecuteResultAsync([NotNull] ActionContext context)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public class Controller
|
public class Controller
|
||||||
{
|
{
|
||||||
|
private DynamicViewData _viewBag;
|
||||||
|
|
||||||
public void Initialize(IActionResultHelper actionResultHelper)
|
public void Initialize(IActionResultHelper actionResultHelper)
|
||||||
{
|
{
|
||||||
Result = actionResultHelper;
|
Result = actionResultHelper;
|
||||||
|
|
@ -33,11 +35,19 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
public IUrlHelper Url { get; set; }
|
public IUrlHelper Url { get; set; }
|
||||||
|
|
||||||
public ViewData<object> ViewData { get; set; }
|
public ViewDataDictionary<object> ViewData { get; set; }
|
||||||
|
|
||||||
public dynamic ViewBag
|
public dynamic ViewBag
|
||||||
{
|
{
|
||||||
get { return ViewData; }
|
get
|
||||||
|
{
|
||||||
|
if (_viewBag == null)
|
||||||
|
{
|
||||||
|
_viewBag = new DynamicViewData(() => ViewData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _viewBag;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult View()
|
public IActionResult View()
|
||||||
|
|
|
||||||
|
|
@ -52,15 +52,19 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
foreach (var prop in controllerType.GetRuntimeProperties())
|
foreach (var prop in controllerType.GetRuntimeProperties())
|
||||||
{
|
{
|
||||||
if(prop.Name == "ActionContext" && prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(ActionContext).GetTypeInfo()))
|
if(prop.Name == "ActionContext" &&
|
||||||
|
prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(ActionContext).GetTypeInfo()))
|
||||||
{
|
{
|
||||||
prop.SetValue(controller, actionContext);
|
prop.SetValue(controller, actionContext);
|
||||||
}
|
}
|
||||||
else if (prop.Name == "ViewData" && prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(ViewData<object>).GetTypeInfo()))
|
else if (prop.Name == "ViewData" &&
|
||||||
|
prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(ViewDataDictionary<object>).GetTypeInfo()))
|
||||||
{
|
{
|
||||||
prop.SetValue(controller, new ViewData<object>(_serviceProvider.GetService<IModelMetadataProvider>(), actionContext.ModelState));
|
prop.SetValue(controller, new ViewDataDictionary<object>(
|
||||||
|
_serviceProvider.GetService<IModelMetadataProvider>(), actionContext.ModelState));
|
||||||
}
|
}
|
||||||
else if (prop.Name == "Url" && prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(IUrlHelper).GetTypeInfo()))
|
else if (prop.Name == "Url" &&
|
||||||
|
prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(IUrlHelper).GetTypeInfo()))
|
||||||
{
|
{
|
||||||
var urlHelper = new UrlHelper(
|
var urlHelper = new UrlHelper(
|
||||||
actionContext.HttpContext,
|
actionContext.HttpContext,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
IActionResult Content(string value);
|
IActionResult Content(string value);
|
||||||
IActionResult Content(string value, string contentType);
|
IActionResult Content(string value, string contentType);
|
||||||
IJsonResult Json(object value);
|
IJsonResult Json(object value);
|
||||||
IActionResult View(string view, ViewData viewData);
|
IActionResult View(string view, ViewDataDictionary viewData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,10 @@ namespace Microsoft.AspNet.Mvc
|
||||||
prop.SetValue(component, context);
|
prop.SetValue(component, context);
|
||||||
}
|
}
|
||||||
else if (prop.Name == "ViewData" &&
|
else if (prop.Name == "ViewData" &&
|
||||||
typeof(ViewData).GetTypeInfo().IsAssignableFrom(prop.PropertyType.GetTypeInfo()))
|
typeof(ViewDataDictionary).GetTypeInfo().IsAssignableFrom(prop.PropertyType.GetTypeInfo()))
|
||||||
{
|
{
|
||||||
// We're flowing the viewbag across, but the concept of model doesn't really apply here
|
// We're flowing the viewbag across, but the concept of model doesn't really apply here
|
||||||
var viewData = new ViewData(context.ViewData);
|
var viewData = new ViewDataDictionary(context.ViewData);
|
||||||
viewData.Model = null;
|
viewData.Model = null;
|
||||||
|
|
||||||
prop.SetValue(component, viewData);
|
prop.SetValue(component, viewData);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return new JsonViewComponentResult(value);
|
return new JsonViewComponentResult(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IViewComponentResult View([NotNull] string viewName, [NotNull] ViewData viewData)
|
public IViewComponentResult View([NotNull] string viewName, [NotNull] ViewDataDictionary viewData)
|
||||||
{
|
{
|
||||||
return new ViewViewComponentResult(_viewEngine, viewName, viewData);
|
return new ViewViewComponentResult(_viewEngine, viewName, viewData);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
IViewComponentResult Json([NotNull] object value);
|
IViewComponentResult Json([NotNull] object value);
|
||||||
|
|
||||||
IViewComponentResult View([NotNull] string viewName, [NotNull] ViewData viewData);
|
IViewComponentResult View([NotNull] string viewName, [NotNull] ViewDataDictionary viewData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
public ViewContext ViewContext { get; set; }
|
public ViewContext ViewContext { get; set; }
|
||||||
|
|
||||||
public ViewData ViewData { get; set; }
|
public ViewDataDictionary ViewData { get; set; }
|
||||||
|
|
||||||
public void Initialize(IViewComponentResultHelper result)
|
public void Initialize(IViewComponentResultHelper result)
|
||||||
{
|
{
|
||||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
public IViewComponentResult View<TModel>(string viewName, TModel model)
|
public IViewComponentResult View<TModel>(string viewName, TModel model)
|
||||||
{
|
{
|
||||||
var viewData = new ViewData<TModel>(ViewData);
|
var viewData = new ViewDataDictionary<TModel>(ViewData);
|
||||||
if (model != null)
|
if (model != null)
|
||||||
{
|
{
|
||||||
viewData.Model = model;
|
viewData.Model = model;
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
private readonly IViewEngine _viewEngine;
|
private readonly IViewEngine _viewEngine;
|
||||||
private readonly string _viewName;
|
private readonly string _viewName;
|
||||||
private readonly ViewData _viewData;
|
private readonly ViewDataDictionary _viewData;
|
||||||
|
|
||||||
public ViewViewComponentResult([NotNull] IViewEngine viewEngine, [NotNull] string viewName, ViewData viewData)
|
public ViewViewComponentResult([NotNull] IViewEngine viewEngine, [NotNull] string viewName,
|
||||||
|
ViewDataDictionary viewData)
|
||||||
{
|
{
|
||||||
_viewEngine = viewEngine;
|
_viewEngine = viewEngine;
|
||||||
_viewName = viewName;
|
_viewName = viewName;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,14 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
get { return Context == null ? null : Context.Url; }
|
get { return Context == null ? null : Context.Url; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dynamic ViewBag
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (Context == null) ? null : Context.ViewBag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string BodyContent { get; set; }
|
private string BodyContent { get; set; }
|
||||||
|
|
||||||
public virtual async Task RenderAsync([NotNull] ViewContext context)
|
public virtual async Task RenderAsync([NotNull] ViewContext context)
|
||||||
|
|
|
||||||
|
|
@ -16,31 +16,26 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public dynamic ViewBag
|
public ViewDataDictionary<TModel> ViewData { get; private set; }
|
||||||
{
|
|
||||||
get { return ViewData; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public ViewData<TModel> ViewData { get; private set; }
|
|
||||||
|
|
||||||
public HtmlHelper<TModel> Html { get; set; }
|
public HtmlHelper<TModel> Html { get; set; }
|
||||||
|
|
||||||
public override Task RenderAsync([NotNull] ViewContext context)
|
public override Task RenderAsync([NotNull] ViewContext context)
|
||||||
{
|
{
|
||||||
ViewData = context.ViewData as ViewData<TModel>;
|
ViewData = context.ViewData as ViewDataDictionary<TModel>;
|
||||||
if (ViewData == null)
|
if (ViewData == null)
|
||||||
{
|
{
|
||||||
if (context.ViewData != null)
|
if (context.ViewData != null)
|
||||||
{
|
{
|
||||||
ViewData = new ViewData<TModel>(context.ViewData);
|
ViewData = new ViewDataDictionary<TModel>(context.ViewData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var metadataProvider = context.ServiceProvider.GetService<IModelMetadataProvider>();
|
var metadataProvider = context.ServiceProvider.GetService<IModelMetadataProvider>();
|
||||||
ViewData = new ViewData<TModel>(metadataProvider);
|
ViewData = new ViewDataDictionary<TModel>(metadataProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have new ViewData; make sure it's visible everywhere.
|
// Have new ViewDataDictionary; make sure it's visible everywhere.
|
||||||
context.ViewData = ViewData;
|
context.ViewData = ViewData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
public static readonly string ValidationSummaryCssClassName = "validation-summary-errors";
|
public static readonly string ValidationSummaryCssClassName = "validation-summary-errors";
|
||||||
public static readonly string ValidationSummaryValidCssClassName = "validation-summary-valid";
|
public static readonly string ValidationSummaryValidCssClassName = "validation-summary-valid";
|
||||||
|
|
||||||
public HtmlHelper([NotNull] HttpContext httpContext, ViewData viewData)
|
public HtmlHelper([NotNull] HttpContext httpContext, ViewDataDictionary viewData)
|
||||||
{
|
{
|
||||||
HttpContext = httpContext;
|
HttpContext = httpContext;
|
||||||
ViewData = viewData;
|
ViewData = viewData;
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
|
||||||
public HttpContext HttpContext { get; private set; }
|
public HttpContext HttpContext { get; private set; }
|
||||||
|
|
||||||
public ViewData ViewData
|
public ViewDataDictionary ViewData
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
{
|
{
|
||||||
public class HtmlHelper<TModel> : HtmlHelper
|
public class HtmlHelper<TModel> : HtmlHelper
|
||||||
{
|
{
|
||||||
public HtmlHelper([NotNull]HttpContext httpContext, ViewData<TModel> viewData)
|
public HtmlHelper([NotNull]HttpContext httpContext, ViewDataDictionary<TModel> viewData)
|
||||||
: base(httpContext, viewData)
|
: base(httpContext, viewData)
|
||||||
{
|
{
|
||||||
ViewData = viewData;
|
ViewData = viewData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public new ViewData<TModel> ViewData
|
public new ViewDataDictionary<TModel> ViewData
|
||||||
{
|
{
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,22 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Common_PartialViewNotFound"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("Common_PartialViewNotFound"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewData value must not be null.
|
||||||
|
/// </summary>
|
||||||
|
internal static string DynamicViewData_ViewDataNull
|
||||||
|
{
|
||||||
|
get { return GetString("DynamicViewData_ViewDataNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewData value must not be null.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatDynamicViewData_ViewDataNull()
|
||||||
|
{
|
||||||
|
return GetString("DynamicViewData_ViewDataNull");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The model item passed is null, but this ViewData instance requires a non-null model item of type '{0}'.
|
/// The model item passed is null, but this ViewData instance requires a non-null model item of type '{0}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -123,11 +123,14 @@
|
||||||
<data name="Common_PartialViewNotFound" xml:space="preserve">
|
<data name="Common_PartialViewNotFound" xml:space="preserve">
|
||||||
<value>The partial view '{0}' was not found or no view engine supports the searched locations. The following locations were searched:{1}</value>
|
<value>The partial view '{0}' was not found or no view engine supports the searched locations. The following locations were searched:{1}</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DynamicViewData_ViewDataNull" xml:space="preserve">
|
||||||
|
<value>ViewData value must not be null.</value>
|
||||||
|
</data>
|
||||||
<data name="ViewData_ModelCannotBeNull" xml:space="preserve">
|
<data name="ViewData_ModelCannotBeNull" xml:space="preserve">
|
||||||
<value>The model item passed is null, but this ViewData instance requires a non-null model item of type '{0}'.</value>
|
<value>The model item passed is null, but this ViewDataDictionary instance requires a non-null model item of type '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewData_WrongTModelType" xml:space="preserve">
|
<data name="ViewData_WrongTModelType" xml:space="preserve">
|
||||||
<value>The model item passed into the ViewData is of type '{0}', but this ViewData instance requires a model item of type '{1}'.</value>
|
<value>The model item passed into the ViewDataDictionary is of type '{0}', but this ViewDataDictionary instance requires a model item of type '{1}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewEngine_ViewNotFound" xml:space="preserve">
|
<data name="ViewEngine_ViewNotFound" xml:space="preserve">
|
||||||
<value>The view '{0}' was not found. The following locations were searched:{1}.</value>
|
<value>The view '{0}' was not found. The following locations were searched:{1}.</value>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Dynamic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
{
|
||||||
|
public class DynamicViewData : DynamicObject
|
||||||
|
{
|
||||||
|
private readonly Func<ViewDataDictionary> _viewDataFunc;
|
||||||
|
|
||||||
|
public DynamicViewData([NotNull] Func<ViewDataDictionary> viewDataFunc)
|
||||||
|
{
|
||||||
|
_viewDataFunc = viewDataFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ViewDataDictionary ViewData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ViewDataDictionary 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([NotNull] GetMemberBinder binder, out object result)
|
||||||
|
{
|
||||||
|
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([NotNull] SetMemberBinder binder, object value)
|
||||||
|
{
|
||||||
|
ViewData[binder.Name] = value;
|
||||||
|
|
||||||
|
// Can always add / update a ViewDataDictionary value.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
{
|
{
|
||||||
public class ViewContext
|
public class ViewContext
|
||||||
{
|
{
|
||||||
|
private DynamicViewData _viewBag;
|
||||||
|
|
||||||
public ViewContext(IServiceProvider serviceProvider, HttpContext httpContext, IDictionary<string, object> viewEngineContext)
|
public ViewContext(IServiceProvider serviceProvider, HttpContext httpContext, IDictionary<string, object> viewEngineContext)
|
||||||
{
|
{
|
||||||
ServiceProvider = serviceProvider;
|
ServiceProvider = serviceProvider;
|
||||||
|
|
@ -22,7 +24,20 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
|
||||||
public IUrlHelper Url { get; set; }
|
public IUrlHelper Url { get; set; }
|
||||||
|
|
||||||
public ViewData ViewData { get; set; }
|
public dynamic ViewBag
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_viewBag == null)
|
||||||
|
{
|
||||||
|
_viewBag = new DynamicViewData(() => ViewData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _viewBag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewDataDictionary ViewData { get; set; }
|
||||||
|
|
||||||
public IDictionary<string, object> ViewEngineContext { get; private set; }
|
public IDictionary<string, object> ViewEngineContext { get; private set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Dynamic;
|
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Rendering
|
|
||||||
{
|
|
||||||
public class ViewData : DynamicObject
|
|
||||||
{
|
|
||||||
private readonly Dictionary<object, dynamic> _data;
|
|
||||||
private object _model;
|
|
||||||
private ModelMetadata _modelMetadata;
|
|
||||||
private IModelMetadataProvider _metadataProvider;
|
|
||||||
|
|
||||||
public ViewData([NotNull] IModelMetadataProvider metadataProvider)
|
|
||||||
: this(metadataProvider, new ModelStateDictionary())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ViewData([NotNull] IModelMetadataProvider metadataProvider, [NotNull] ModelStateDictionary modelState)
|
|
||||||
{
|
|
||||||
ModelState = modelState;
|
|
||||||
_data = new Dictionary<object, dynamic>();
|
|
||||||
_metadataProvider = metadataProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ViewData([NotNull] ViewData source)
|
|
||||||
: this(source.MetadataProvider)
|
|
||||||
{
|
|
||||||
_modelMetadata = source.ModelMetadata;
|
|
||||||
|
|
||||||
foreach (var entry in source.ModelState)
|
|
||||||
{
|
|
||||||
ModelState.Add(entry.Key, entry.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entry in source)
|
|
||||||
{
|
|
||||||
_data.Add(entry.Key, entry.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetModel(source.Model);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Model
|
|
||||||
{
|
|
||||||
get { return _model; }
|
|
||||||
set { SetModel(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModelStateDictionary ModelState { get; private set; }
|
|
||||||
|
|
||||||
public dynamic this[string index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
dynamic result;
|
|
||||||
|
|
||||||
_data.TryGetValue(index, out result);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_data[index] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual ModelMetadata ModelMetadata
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _modelMetadata;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_modelMetadata = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provider for subclasses that need it to override <see cref="ModelMetadata"/>.
|
|
||||||
/// </summary>
|
|
||||||
protected IModelMetadataProvider MetadataProvider
|
|
||||||
{
|
|
||||||
get { return _metadataProvider; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<object, dynamic>.Enumerator GetEnumerator()
|
|
||||||
{
|
|
||||||
return _data.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool TryGetMember(GetMemberBinder binder, out object result)
|
|
||||||
{
|
|
||||||
result = _data[binder.Name];
|
|
||||||
|
|
||||||
// We return true here because ViewDataDictionary returns null if the key is not
|
|
||||||
// in the dictionary, so we simply pass on the returned value.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
|
||||||
{
|
|
||||||
// This cast should always succeed.
|
|
||||||
dynamic v = value;
|
|
||||||
_data[binder.Name] = v;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
|
|
||||||
{
|
|
||||||
if (indexes == null || indexes.Length != 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Invalid number of indexes");
|
|
||||||
}
|
|
||||||
|
|
||||||
object index = indexes[0];
|
|
||||||
result = this[(string)index];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
|
|
||||||
{
|
|
||||||
if (indexes == null || indexes.Length != 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Invalid number of indexes");
|
|
||||||
}
|
|
||||||
|
|
||||||
object index = indexes[0];
|
|
||||||
|
|
||||||
// This cast should always succeed.
|
|
||||||
this[(string)index] = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method will execute before the derived type's instance constructor executes. Derived types must
|
|
||||||
// be aware of this and should plan accordingly. For example, the logic in SetModel() should be simple
|
|
||||||
// enough so as not to depend on the "this" pointer referencing a fully constructed object.
|
|
||||||
protected virtual void SetModel(object value)
|
|
||||||
{
|
|
||||||
_model = value;
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
// Unable to determine model metadata.
|
|
||||||
_modelMetadata = null;
|
|
||||||
}
|
|
||||||
else if (_modelMetadata == null || value.GetType() != ModelMetadata.ModelType)
|
|
||||||
{
|
|
||||||
// Reset or override model metadata based on new value type.
|
|
||||||
_modelMetadata = _metadataProvider.GetMetadataForType(() => value, value.GetType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
{
|
||||||
|
public class ViewDataDictionary : IDictionary<string, object>
|
||||||
|
{
|
||||||
|
private readonly IDictionary<string, object> _data;
|
||||||
|
private object _model;
|
||||||
|
private ModelMetadata _modelMetadata;
|
||||||
|
private IModelMetadataProvider _metadataProvider;
|
||||||
|
|
||||||
|
public ViewDataDictionary([NotNull] IModelMetadataProvider metadataProvider)
|
||||||
|
: this(metadataProvider, new ModelStateDictionary())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewDataDictionary([NotNull] IModelMetadataProvider metadataProvider,
|
||||||
|
[NotNull] ModelStateDictionary modelState)
|
||||||
|
{
|
||||||
|
ModelState = modelState;
|
||||||
|
_data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
_metadataProvider = metadataProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="ViewDataDictionary"/> copy constructor for use when model type does not change.
|
||||||
|
/// </summary>
|
||||||
|
public ViewDataDictionary([NotNull] ViewDataDictionary source)
|
||||||
|
: this(source, source.Model)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="ViewDataDictionary"/> copy constructor for use when model type may change. This avoids
|
||||||
|
/// exceptions a derived class may throw when <see cref="SetModel()"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public ViewDataDictionary([NotNull] ViewDataDictionary source, object model)
|
||||||
|
: this(source.MetadataProvider)
|
||||||
|
{
|
||||||
|
_modelMetadata = source.ModelMetadata;
|
||||||
|
|
||||||
|
foreach (var entry in source.ModelState)
|
||||||
|
{
|
||||||
|
ModelState.Add(entry.Key, entry.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in source)
|
||||||
|
{
|
||||||
|
_data.Add(entry.Key, entry.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Model
|
||||||
|
{
|
||||||
|
get { return _model; }
|
||||||
|
set { SetModel(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelStateDictionary ModelState { get; private set; }
|
||||||
|
|
||||||
|
public virtual ModelMetadata ModelMetadata
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _modelMetadata;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_modelMetadata = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provider for subclasses that need it to override <see cref="ModelMetadata"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected IModelMetadataProvider MetadataProvider
|
||||||
|
{
|
||||||
|
get { return _metadataProvider; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDictionary properties
|
||||||
|
// Do not just pass through to _data: Indexer should not throw a KeyNotFoundException.
|
||||||
|
public object this[string index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
object result;
|
||||||
|
_data.TryGetValue(index, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_data[index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get { return _data.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly
|
||||||
|
{
|
||||||
|
get { return _data.IsReadOnly; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICollection<string> Keys
|
||||||
|
{
|
||||||
|
get { return _data.Keys; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICollection<object> Values
|
||||||
|
{
|
||||||
|
get { return _data.Values; }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// This method will execute before the derived type's instance constructor executes. Derived types must
|
||||||
|
// be aware of this and should plan accordingly. For example, the logic in SetModel() should be simple
|
||||||
|
// enough so as not to depend on the "this" pointer referencing a fully constructed object.
|
||||||
|
protected virtual void SetModel(object value)
|
||||||
|
{
|
||||||
|
_model = value;
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
// Unable to determine model metadata.
|
||||||
|
_modelMetadata = null;
|
||||||
|
}
|
||||||
|
else if (_modelMetadata == null || value.GetType() != ModelMetadata.ModelType)
|
||||||
|
{
|
||||||
|
// Reset or override model metadata based on new value type.
|
||||||
|
_modelMetadata = _metadataProvider.GetMetadataForType(() => value, value.GetType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDictionary methods
|
||||||
|
public void Add([NotNull] string key, object value)
|
||||||
|
{
|
||||||
|
_data.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey([NotNull] string key)
|
||||||
|
{
|
||||||
|
return _data.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove([NotNull] string key)
|
||||||
|
{
|
||||||
|
return _data.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue([NotNull] string key, out object value)
|
||||||
|
{
|
||||||
|
return _data.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add([NotNull] KeyValuePair<string, object> item)
|
||||||
|
{
|
||||||
|
_data.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_data.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains([NotNull] KeyValuePair<string, object> item)
|
||||||
|
{
|
||||||
|
return _data.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo([NotNull] KeyValuePair<string, object>[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
_data.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove([NotNull] KeyValuePair<string, object> item)
|
||||||
|
{
|
||||||
|
return _data.Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
||||||
|
{
|
||||||
|
return _data.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return _data.GetEnumerator();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,27 +3,35 @@ using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Rendering
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
{
|
{
|
||||||
public class ViewData<TModel> : ViewData
|
public class ViewDataDictionary<TModel> : ViewDataDictionary
|
||||||
{
|
{
|
||||||
// Fallback ModelMetadata based on TModel. Used when Model is null and base ViewData class is unable to
|
// Fallback ModelMetadata based on TModel. Used when Model is null and base ViewDataDictionary class is unable
|
||||||
// determine the correct metadata.
|
// to determine the correct metadata.
|
||||||
private readonly ModelMetadata _defaultModelMetadata;
|
private readonly ModelMetadata _defaultModelMetadata;
|
||||||
|
|
||||||
public ViewData([NotNull] IModelMetadataProvider metadataProvider)
|
public ViewDataDictionary([NotNull] IModelMetadataProvider metadataProvider)
|
||||||
: base(metadataProvider)
|
: base(metadataProvider)
|
||||||
{
|
{
|
||||||
_defaultModelMetadata = MetadataProvider.GetMetadataForType(null, typeof(TModel));
|
_defaultModelMetadata = MetadataProvider.GetMetadataForType(null, typeof(TModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ViewData([NotNull] IModelMetadataProvider metadataProvider, [NotNull] ModelStateDictionary modelState)
|
public ViewDataDictionary([NotNull] IModelMetadataProvider metadataProvider,
|
||||||
|
[NotNull] ModelStateDictionary modelState)
|
||||||
: base(metadataProvider, modelState)
|
: base(metadataProvider, modelState)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ViewData(ViewData source)
|
/// <inheritdoc />
|
||||||
: base(source)
|
public ViewDataDictionary([NotNull] ViewDataDictionary source)
|
||||||
|
: this(source, source.Model)
|
||||||
{
|
{
|
||||||
var original = source as ViewData<TModel>;
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ViewDataDictionary([NotNull] ViewDataDictionary source, object model)
|
||||||
|
: base(source, model)
|
||||||
|
{
|
||||||
|
var original = source as ViewDataDictionary<TModel>;
|
||||||
if (original != null)
|
if (original != null)
|
||||||
{
|
{
|
||||||
_defaultModelMetadata = original._defaultModelMetadata;
|
_defaultModelMetadata = original._defaultModelMetadata;
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Core
|
||||||
|
{
|
||||||
|
public class ControllerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SettingViewData_AlsoUpdatesViewBag()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
var controller = new Controller();
|
||||||
|
var originalViewData = controller.ViewData = new ViewDataDictionary<object>(metadataProvider);
|
||||||
|
var replacementViewData = new ViewDataDictionary<object>(metadataProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
controller.ViewBag.Hello = "goodbye";
|
||||||
|
controller.ViewData = replacementViewData;
|
||||||
|
controller.ViewBag.Another = "property";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotSame(originalViewData, controller.ViewData);
|
||||||
|
Assert.Same(replacementViewData, controller.ViewData);
|
||||||
|
Assert.Null(controller.ViewBag.Hello);
|
||||||
|
Assert.Equal("property", controller.ViewBag.Another);
|
||||||
|
Assert.Equal("property", controller.ViewData["Another"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
"Microsoft.AspNet.Mvc.Core" : "",
|
"Microsoft.AspNet.Mvc.Core" : "",
|
||||||
"Microsoft.AspNet.Mvc" : "",
|
"Microsoft.AspNet.Mvc" : "",
|
||||||
"Microsoft.AspNet.Testing": "0.1-alpha-*",
|
"Microsoft.AspNet.Testing": "0.1-alpha-*",
|
||||||
|
"Microsoft.AspNet.Mvc.ModelBinding": "",
|
||||||
|
"Microsoft.AspNet.Mvc.Rendering": "",
|
||||||
"Moq": "4.2.1312.1622",
|
"Moq": "4.2.1312.1622",
|
||||||
"Xunit.KRunner": "0.1-alpha-*",
|
"Xunit.KRunner": "0.1-alpha-*",
|
||||||
"xunit.abstractions": "2.0.0-aspnet-*",
|
"xunit.abstractions": "2.0.0-aspnet-*",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
{
|
||||||
|
public class ViewContextTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SettingViewData_AlsoUpdatesViewBag()
|
||||||
|
{
|
||||||
|
// Arrange (eventually passing null to these consturctors will throw)
|
||||||
|
var context = new ViewContext(serviceProvider: null, httpContext: null, viewEngineContext: null);
|
||||||
|
var originalViewData = context.ViewData = new ViewDataDictionary(metadataProvider: null);
|
||||||
|
var replacementViewData = new ViewDataDictionary(metadataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
context.ViewBag.Hello = "goodbye";
|
||||||
|
context.ViewData = replacementViewData;
|
||||||
|
context.ViewBag.Another = "property";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotSame(originalViewData, context.ViewData);
|
||||||
|
Assert.Same(replacementViewData, context.ViewData);
|
||||||
|
Assert.Null(context.ViewBag.Hello);
|
||||||
|
Assert.Equal("property", context.ViewBag.Another);
|
||||||
|
Assert.Equal("property", context.ViewData["Another"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,24 +10,24 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
public void SettingModelThrowsIfTheModelIsNull()
|
public void SettingModelThrowsIfTheModelIsNull()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var viewDataOfT = new ViewData<int>(new DataAnnotationsModelMetadataProvider());
|
var viewDataOfT = new ViewDataDictionary<int>(new DataAnnotationsModelMetadataProvider());
|
||||||
ViewData viewData = viewDataOfT;
|
ViewDataDictionary viewData = viewDataOfT;
|
||||||
|
|
||||||
// Act and Assert
|
// Act and Assert
|
||||||
Exception ex = Assert.Throws<InvalidOperationException>(() => viewData.Model = null);
|
Exception ex = Assert.Throws<InvalidOperationException>(() => viewData.Model = null);
|
||||||
Assert.Equal("The model item passed is null, but this ViewData instance requires a non-null model item of type 'System.Int32'.", ex.Message);
|
Assert.Equal("The model item passed is null, but this ViewDataDictionary instance requires a non-null model item of type 'System.Int32'.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SettingModelThrowsIfTheModelIsIncompatible()
|
public void SettingModelThrowsIfTheModelIsIncompatible()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var viewDataOfT = new ViewData<string>(new DataAnnotationsModelMetadataProvider());
|
var viewDataOfT = new ViewDataDictionary<string>(new DataAnnotationsModelMetadataProvider());
|
||||||
ViewData viewData = viewDataOfT;
|
ViewDataDictionary viewData = viewDataOfT;
|
||||||
|
|
||||||
// Act and Assert
|
// Act and Assert
|
||||||
Exception ex = Assert.Throws<InvalidOperationException>(() => viewData.Model = DateTime.UtcNow);
|
Exception ex = Assert.Throws<InvalidOperationException>(() => viewData.Model = DateTime.UtcNow);
|
||||||
Assert.Equal("The model item passed into the ViewData is of type 'System.DateTime', but this ViewData instance requires a model item of type 'System.String'.", ex.Message);
|
Assert.Equal("The model item passed into the ViewDataDictionary is of type 'System.DateTime', but this ViewDataDictionary instance requires a model item of type 'System.String'.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -35,8 +35,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
string value = "some value";
|
string value = "some value";
|
||||||
var viewDataOfT = new ViewData<object>(new DataAnnotationsModelMetadataProvider());
|
var viewDataOfT = new ViewDataDictionary<object>(new DataAnnotationsModelMetadataProvider());
|
||||||
ViewData viewData = viewDataOfT;
|
ViewDataDictionary viewData = viewDataOfT;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
viewData.Model = value;
|
viewData.Model = value;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue