Add `ModelMetadata` to `ViewData`
- demonstrate `ModelMetadata` is available in a view Also - simplify `View()` overloads in `Controller`, pending #110 decisions - make `Model` in `RazorView<T>` readonly
This commit is contained in:
parent
857a239990
commit
32d031c6eb
|
|
@ -11,7 +11,26 @@ namespace MvcSample.Web
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that exercises query\form based model binding.
|
||||
/// Action that shows metadata when model is <c>null</c>.
|
||||
/// </summary>
|
||||
public IActionResult Create()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that shows metadata when model is non-<c>null</c>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IActionResult Edit()
|
||||
{
|
||||
ViewBag.Gift = "the banana";
|
||||
ViewData.Model = new User { Name = "Name", Address = "Address in a State", Age = 37, };
|
||||
return View("Create");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that exercises query\form based model binding.
|
||||
/// </summary>
|
||||
public IActionResult SaveUser(User user)
|
||||
{
|
||||
|
|
@ -55,6 +74,9 @@ namespace MvcSample.Web
|
|||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that exercises default view names.
|
||||
/// </summary>
|
||||
public IActionResult MyView()
|
||||
{
|
||||
return View(User());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
@using MvcSample.Web.Models
|
||||
@model User
|
||||
@{
|
||||
string nullValue = null;
|
||||
ViewBag.Title = (Model == null) ? "Create Page" : "Edit Page";
|
||||
if (ViewData["Gift"] == null)
|
||||
{
|
||||
ViewBag.Gift = "nothing";
|
||||
}
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<h2 title="@ViewBag.Title" class="@nullValue">@ViewBag.Title</h2>
|
||||
<h3 title="Thanks" class="@nullValue">Thanks for @ViewBag.Gift</h3>
|
||||
@if (Model == null)
|
||||
{
|
||||
<h4 title ="Null Model" class="@nullValue">Howdy, your model is null.</h4>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h4 title="@Model.Name" class="@nullValue">Hello @Model.Name! Happy @Model.Age birthday.</h4>
|
||||
}
|
||||
|
||||
@{
|
||||
var metadata = ViewData.ModelMetadata;
|
||||
if (metadata != null)
|
||||
{
|
||||
var typeName = metadata.ModelType.Name;
|
||||
var description = metadata.Description;
|
||||
<p>@typeName has description '@description' and contains</p>
|
||||
<ul>
|
||||
@foreach (var property in metadata.Properties)
|
||||
{
|
||||
var propertyName = property.PropertyName;
|
||||
var propertyTypeName = property.ModelType.Name;
|
||||
var propertyDescription = property.Description;
|
||||
<li>Property @propertyName has type @propertyTypeName and description '@propertyDescription'</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
|
@ -33,9 +33,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
context.HttpContext.Response.ContentType = "text/html";
|
||||
using (var writer = new StreamWriter(context.HttpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
||||
{
|
||||
var viewContext = new ViewContext(_serviceProvider, context.HttpContext, context.RouteValues, ViewData)
|
||||
var viewContext = new ViewContext(_serviceProvider, context.HttpContext, context.RouteValues)
|
||||
{
|
||||
Url = new UrlHelper(context.HttpContext, context.Router, context.RouteValues),
|
||||
ViewData = ViewData,
|
||||
Writer = writer,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
using Microsoft.AspNet.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class Controller
|
||||
{
|
||||
public void Initialize(IActionResultHelper actionResultHelper)
|
||||
public void Initialize(IActionResultHelper actionResultHelper, IModelMetadataProvider metadataProvider)
|
||||
{
|
||||
Result = actionResultHelper;
|
||||
ViewData = new ViewData<object>();
|
||||
ViewData = new ViewData<object>(metadataProvider);
|
||||
}
|
||||
|
||||
public IActionResultHelper Result { get; private set; }
|
||||
|
|
@ -31,21 +32,24 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
public IActionResult View(string view)
|
||||
{
|
||||
return View(view, model: (object)null);
|
||||
return View(view, model: null);
|
||||
}
|
||||
|
||||
public IActionResult View<TModel>(TModel model)
|
||||
// TODO #110: May need <TModel> here and in the overload below.
|
||||
public IActionResult View(object model)
|
||||
{
|
||||
return View(view: null, model: model);
|
||||
}
|
||||
|
||||
public IActionResult View<TModel>(string view, TModel model)
|
||||
public IActionResult View(string view, object model)
|
||||
{
|
||||
var viewDataDictionary = new ViewData<TModel>
|
||||
// Do not override ViewData.Model unless passed a non-null value.
|
||||
if (model != null)
|
||||
{
|
||||
Model = model
|
||||
};
|
||||
return Result.View(view, viewDataDictionary);
|
||||
ViewData.Model = model;
|
||||
}
|
||||
|
||||
return Result.View(view, ViewData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
private string BodyContent { get; set; }
|
||||
|
||||
public virtual async Task RenderAsync(ViewContext context, TextWriter writer)
|
||||
public virtual async Task RenderAsync([NotNull] ViewContext context, [NotNull] TextWriter writer)
|
||||
{
|
||||
Context = context;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,49 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.DependencyInjection;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public abstract class RazorView<TModel> : RazorView
|
||||
{
|
||||
public TModel Model { get; set; }
|
||||
public TModel Model
|
||||
{
|
||||
get
|
||||
{
|
||||
return ViewData == null ? default(TModel) : ViewData.Model;
|
||||
}
|
||||
}
|
||||
|
||||
public dynamic ViewBag
|
||||
{
|
||||
get { return ViewData; }
|
||||
}
|
||||
|
||||
public ViewData<TModel> ViewData { get; set; }
|
||||
public ViewData<TModel> ViewData { get; private set; }
|
||||
|
||||
public HtmlHelper<TModel> Html { get; set; }
|
||||
|
||||
public override Task RenderAsync(ViewContext context, TextWriter writer)
|
||||
public override Task RenderAsync([NotNull] ViewContext context, [NotNull] TextWriter writer)
|
||||
{
|
||||
var viewData = context.ViewData as ViewData<TModel>;
|
||||
ViewData = viewData ?? new ViewData<TModel>(context.ViewData);
|
||||
Model = ViewData.Model;
|
||||
ViewData = context.ViewData as ViewData<TModel>;
|
||||
if (ViewData == null)
|
||||
{
|
||||
if (context.ViewData != null)
|
||||
{
|
||||
ViewData = new ViewData<TModel>(context.ViewData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var metadataProvider = context.ServiceProvider.GetService<IModelMetadataProvider>();
|
||||
ViewData = new ViewData<TModel>(metadataProvider);
|
||||
}
|
||||
|
||||
// Have new ViewData; make sure it's visible everywhere.
|
||||
context.ViewData = ViewData;
|
||||
}
|
||||
|
||||
InitHelpers(context);
|
||||
|
||||
return base.RenderAsync(context, writer);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
{
|
||||
public interface IView
|
||||
{
|
||||
Task RenderAsync(ViewContext context, TextWriter writer);
|
||||
Task RenderAsync([NotNull] ViewContext context, [NotNull] TextWriter writer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
{
|
||||
public class ViewContext
|
||||
{
|
||||
public ViewContext(IServiceProvider serviceProvider, HttpContext httpContext, IDictionary<string, object> viewEngineContext, ViewData viewData)
|
||||
public ViewContext(IServiceProvider serviceProvider, HttpContext httpContext, IDictionary<string, object> viewEngineContext)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
HttpContext = httpContext;
|
||||
ViewEngineContext = viewEngineContext;
|
||||
ViewData = viewData;
|
||||
}
|
||||
|
||||
public HttpContext HttpContext { get; private set; }
|
||||
|
|
@ -21,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
|
||||
public IUrlHelper Url { get; set; }
|
||||
|
||||
public ViewData ViewData { get; private set; }
|
||||
public ViewData ViewData { get; set; }
|
||||
|
||||
public IDictionary<string, object> ViewEngineContext { get; private set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
|
|
@ -8,15 +9,20 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
{
|
||||
private readonly Dictionary<object, dynamic> _data;
|
||||
private object _model;
|
||||
private ModelMetadata _modelMetadata;
|
||||
private IModelMetadataProvider _metadataProvider;
|
||||
|
||||
public ViewData()
|
||||
public ViewData([NotNull] IModelMetadataProvider metadataProvider)
|
||||
{
|
||||
_data = new Dictionary<object, dynamic>();
|
||||
_metadataProvider = metadataProvider;
|
||||
}
|
||||
|
||||
public ViewData([NotNull] ViewData source)
|
||||
{
|
||||
_data = source._data;
|
||||
_modelMetadata = source.ModelMetadata;
|
||||
_metadataProvider = source.MetadataProvider;
|
||||
SetModel(source.Model);
|
||||
}
|
||||
|
||||
|
|
@ -44,13 +50,34 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
}
|
||||
set
|
||||
{
|
||||
_data[index] = (dynamic)value;
|
||||
_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 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;
|
||||
|
|
@ -58,7 +85,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
|
||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
||||
{
|
||||
// This cast should always succeed assuming TValue is dynamic.
|
||||
// This cast should always succeed.
|
||||
dynamic v = value;
|
||||
_data[binder.Name] = v;
|
||||
return true;
|
||||
|
|
@ -84,7 +111,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
}
|
||||
|
||||
object index = indexes[0];
|
||||
// This cast should always succeed assuming TValue is dynamic.
|
||||
|
||||
// This cast should always succeed.
|
||||
this[(string)index] = value;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -95,6 +123,16 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,32 @@
|
|||
using System;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||
using System;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
public class ViewData<TModel> : ViewData
|
||||
{
|
||||
public ViewData()
|
||||
: base()
|
||||
// Fallback ModelMetadata based on TModel. Used when Model is null and base ViewData class is unable to
|
||||
// determine the correct metadata.
|
||||
private readonly ModelMetadata _defaultModelMetadata;
|
||||
|
||||
public ViewData([NotNull] IModelMetadataProvider metadataProvider)
|
||||
: base(metadataProvider)
|
||||
{
|
||||
_defaultModelMetadata = MetadataProvider.GetMetadataForType(null, typeof(TModel));
|
||||
}
|
||||
|
||||
public ViewData(ViewData source) :
|
||||
base(source)
|
||||
public ViewData(ViewData source)
|
||||
: base(source)
|
||||
{
|
||||
var original = source as ViewData<TModel>;
|
||||
if (original != null)
|
||||
{
|
||||
_defaultModelMetadata = original._defaultModelMetadata;
|
||||
}
|
||||
else
|
||||
{
|
||||
_defaultModelMetadata = MetadataProvider.GetMetadataForType(null, typeof(TModel));
|
||||
}
|
||||
}
|
||||
|
||||
public new TModel Model
|
||||
|
|
@ -21,6 +35,14 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
set { SetModel(value); }
|
||||
}
|
||||
|
||||
public override ModelMetadata ModelMetadata
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.ModelMetadata ?? _defaultModelMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetModel(object value)
|
||||
{
|
||||
// IsCompatibleObject verifies if the value is either an instance of TModel or (if value is null) that
|
||||
|
|
@ -42,6 +64,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
{
|
||||
message = Resources.FormatViewData_WrongTModelType(value.GetType(), typeof(TModel));
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering.Test
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
public class ViewDataOfTTest
|
||||
{
|
||||
|
|
@ -9,7 +10,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Test
|
|||
public void SettingModelThrowsIfTheModelIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var viewDataOfT = new ViewData<int>();
|
||||
var viewDataOfT = new ViewData<int>(new DataAnnotationsModelMetadataProvider());
|
||||
ViewData viewData = viewDataOfT;
|
||||
|
||||
// Act and Assert
|
||||
|
|
@ -21,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Test
|
|||
public void SettingModelThrowsIfTheModelIsIncompatible()
|
||||
{
|
||||
// Arrange
|
||||
var viewDataOfT = new ViewData<string>();
|
||||
var viewDataOfT = new ViewData<string>(new DataAnnotationsModelMetadataProvider());
|
||||
ViewData viewData = viewDataOfT;
|
||||
|
||||
// Act and Assert
|
||||
|
|
@ -34,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Test
|
|||
{
|
||||
// Arrange
|
||||
string value = "some value";
|
||||
var viewDataOfT = new ViewData<object>();
|
||||
var viewDataOfT = new ViewData<object>(new DataAnnotationsModelMetadataProvider());
|
||||
ViewData viewData = viewDataOfT;
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
|
||||
"Microsoft.AspNet.PipelineCore": "0.1-alpha-*",
|
||||
"Microsoft.AspNet.Mvc.ModelBinding" : "",
|
||||
"Microsoft.AspNet.Mvc.Rendering" : "",
|
||||
"TestCommon" : "",
|
||||
"Xunit.KRunner": "0.1-alpha-*",
|
||||
|
|
|
|||
Loading…
Reference in New Issue