Update ViewDataOfT.SetModel to check for type compatibility when setting

model instance.
This commit is contained in:
Pranav K 2014-02-04 16:33:03 -08:00
parent 6c8485b1ef
commit b51fd08bab
19 changed files with 361 additions and 8 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.21005.1
VisualStudioVersion = 12.0.30110.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -39,6 +39,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{222CA408
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k10", "k10", "{CE037E26-9EB5-48E2-B73B-06C6FF6CC9F5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Mvc.ModelBinding.Test.net45", "test\Microsoft.AspNet.Mvc.ModelBinding.Test\Microsoft.AspNet.Mvc.ModelBinding.Test.net45.csproj", "{42195A56-42C0-4CFF-A982-B6E24EFC6356}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -93,6 +97,10 @@ Global
{236CDB04-8FDA-4152-9A5B-7F98C19C663A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{236CDB04-8FDA-4152-9A5B-7F98C19C663A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{236CDB04-8FDA-4152-9A5B-7F98C19C663A}.Release|Any CPU.Build.0 = Release|Any CPU
{42195A56-42C0-4CFF-A982-B6E24EFC6356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42195A56-42C0-4CFF-A982-B6E24EFC6356}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42195A56-42C0-4CFF-A982-B6E24EFC6356}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42195A56-42C0-4CFF-A982-B6E24EFC6356}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -114,5 +122,6 @@ Global
{BA88E212-5889-48DC-823F-A3A67DDEF123} = {4EB70D0E-E27E-4C42-AB58-BC8B325EDFB3}
{501817DD-8143-4A50-888D-99896A82CD12} = {222CA408-93EE-473A-9325-D04989EC9FEF}
{A7D7CD66-A407-4144-8AB7-07F895F87137} = {CE037E26-9EB5-48E2-B73B-06C6FF6CC9F5}
{42195A56-42C0-4CFF-A982-B6E24EFC6356} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,23 @@
using System;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
{
public static class TypeExtensions
{
public static bool IsCompatibleObject<T>(this object value)
{
return (value is T || (value == null && TypeAllowsNullValue(typeof(T))));
}
public static bool IsNullableValueType(this Type type)
{
return Nullable.GetUnderlyingType(type) != null;
}
public static bool TypeAllowsNullValue(this Type type)
{
return (!type.GetTypeInfo().IsValueType || IsNullableValueType(type));
}
}
}

View File

@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.34003
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.AspNet.Mvc.ModelBinding {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Mvc.ModelBinding.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to The model item passed into the ViewData is of type &apos;{0}&apos;, but this ViewData instance requires a model item of type &apos;{1}&apos;..
/// </summary>
internal static string ViewData_WrongTModelType {
get {
return ResourceManager.GetString("ViewData_WrongTModelType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The model item passed into the ViewData is null, but this ViewData instance requires a non-null model item of type &apos;{0}&apos;..
/// </summary>
internal static string ViewDataDictionary_ModelCannotBeNull {
get {
return ResourceManager.GetString("ViewDataDictionary_ModelCannotBeNull", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ViewDataDictionary_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>
</data>
<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>
</data>
</root>

View File

@ -2,7 +2,7 @@
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Mvc.Routing;
namespace Microsoft.AspNet.Mvc
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ViewContext : RequestContext
{

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Dynamic;
namespace Microsoft.AspNet.Mvc
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ViewData : DynamicObject
{
@ -16,6 +16,11 @@ namespace Microsoft.AspNet.Mvc
public ViewData(ViewData source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
_data = source._data;
SetModel(source.Model);
}

View File

@ -1,5 +1,8 @@

namespace Microsoft.AspNet.Mvc
using System;
using System.Globalization;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ViewData<TModel> : ViewData
{
@ -21,8 +24,26 @@ namespace Microsoft.AspNet.Mvc
protected override void SetModel(object value)
{
// TODO: Add checks for cast
base.SetModel((TModel)value);
// IsCompatibleObject verifies if the value is either an instance of TModel or if value happens to be null that TModel is nullable type.
bool castWillSucceed = value.IsCompatibleObject<TModel>();
if (castWillSucceed)
{
base.SetModel(value);
}
else
{
string message;
if (value == null)
{
message = String.Format(CultureInfo.CurrentCulture, Resources.ViewDataDictionary_ModelCannotBeNull, typeof(TModel));
}
else
{
message = String.Format(CultureInfo.CurrentCulture, Resources.ViewData_WrongTModelType, value.GetType(), typeof(TModel));
}
throw new InvalidOperationException(message);
}
}
}
}

View File

@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc.Razor
{

View File

@ -1,5 +1,6 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc.Razor
{

View File

@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net;
using System.Text;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{

View File

@ -1,4 +1,5 @@
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{

View File

@ -1,5 +1,6 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{

View File

@ -1,4 +1,5 @@
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{

View File

@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{

View File

@ -1,4 +1,5 @@
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{

View File

@ -1,4 +1,5 @@

using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
public interface IActionResultHelper

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
public class ViewDataOfTTest
{
[Fact]
public void SettingModelThrowsIfTheModelIsNull()
{
// Arrange
var viewDataOfT = new ViewData<int>();
ViewData viewData = viewDataOfT;
// Act and Assert
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);
}
[Fact]
public void SettingModelThrowsIfTheModelIsIncompatible()
{
// Arrange
var viewDataOfT = new ViewData<string>();
ViewData viewData = viewDataOfT;
// Act and Assert
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);
}
[Fact]
public void SettingModelWorksForCompatibleTypes()
{
// Arrange
string value = "some value";
var viewDataOfT = new ViewData<object>();
ViewData viewData = viewDataOfT;
// Act
viewData.Model = value;
// Assert
Assert.Same(value, viewDataOfT.Model);
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
public class ViewDataDictionaryTest
{
[Fact]
public void ConstructorThrowsIfParameterIsNull()
{
// Act & Assert
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => new ViewData(source: null));
Assert.Equal("source", ex.ParamName);
}
}
}

View File

@ -0,0 +1,12 @@
{
"version" : "0.1-alpha-*",
"dependencies": {
"Microsoft.AspNet.Mvc.ModelBinding" : "0.1-alpha-*",
"Moq": "4.0.10827",
"Xunit": "1.9.1",
"Xunit.extensions": "1.9.1"
},
"configurations": {
"net45": { }
}
}