Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/aspnetwebstack.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorraghuramn <ranadimi@microsoft.com>2012-09-18 06:06:08 +0400
committerraghuramn <ranadimi@microsoft.com>2012-09-24 21:27:07 +0400
commitbc22f1bdcca80b04fe18abc19df46d2e026edfb8 (patch)
tree4dc3314582abc4e0f19818cb95b212cc6130e2d6
parent3a669e7112e77f3b625291f099460fb60fe9161d (diff)
Adding inheritance support in the odata formatter
Formatter can now 1) Serialize a Motorcycle (told to be a Vehicle). 2) Serialize a collection of Vehicles (containing cars, motorcycles). 3) Serialize a sportbike as a motorcycle (derives from motorcycle) when the edm model doesn't have a definition for sportbike. A) Deserialize a Motorcycle (when the action is expecting a Vehicle).
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs24
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntityDeserializer.cs32
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/EdmLibHelpers.cs15
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/ODataMediaTypeFormatter.cs70
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/Serialization/DefaultODataSerializerProvider.cs26
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/Serialization/ODataFeedSerializer.cs21
-rw-r--r--src/System.Web.Http.OData/Properties/SRResources.Designer.cs18
-rw-r--r--src/System.Web.Http.OData/Properties/SRResources.resx6
-rw-r--r--test/System.Web.Http.OData.Test/OData/Builder/TestModels/InheritanceModels.cs5
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntityDeserializerTests.cs12
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/EdmLibHelpersTests.cs66
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/InheritanceTests.cs267
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/Serialization/DefaultODataSerializerProviderTests.cs1
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataEntityTypeSerializerTests.cs5
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataFeedSerializerTests.cs4
-rw-r--r--test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj1
16 files changed, 507 insertions, 66 deletions
diff --git a/src/System.Web.Http.OData/OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs b/src/System.Web.Http.OData/OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs
index 4bf49d0a..d2f048f1 100644
--- a/src/System.Web.Http.OData/OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs
+++ b/src/System.Web.Http.OData/OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs
@@ -1,11 +1,16 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+using System.Collections.Concurrent;
using Microsoft.Data.Edm;
namespace System.Web.Http.OData.Formatter.Deserialization
{
internal class DefaultODataDeserializerProvider : ODataDeserializerProvider
{
+ // cache the clrtype to ODataDeserializer mappings as we might have to crawl the
+ // inheritance hirerachy to find the mapping.
+ private ConcurrentDictionary<Type, ODataDeserializer> _clrTypeMappingCache = new ConcurrentDictionary<Type, ODataDeserializer>();
+
public DefaultODataDeserializerProvider(IEdmModel edmModel)
: base(edmModel)
{
@@ -54,15 +59,18 @@ namespace System.Web.Http.OData.Formatter.Deserialization
return new ODataEntityReferenceLinkDeserializer();
}
- IEdmTypeReference edmType = EdmModel.GetEdmTypeReference(type);
- if (edmType == null)
- {
- return null;
- }
- else
+ return _clrTypeMappingCache.GetOrAdd(type, (t) =>
{
- return GetODataDeserializer(edmType);
- }
+ IEdmTypeReference edmType = EdmModel.GetEdmTypeReference(t);
+ if (edmType == null)
+ {
+ return null;
+ }
+ else
+ {
+ return GetODataDeserializer(edmType);
+ }
+ });
}
}
}
diff --git a/src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntityDeserializer.cs b/src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntityDeserializer.cs
index 9d1a9853..fece8659 100644
--- a/src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntityDeserializer.cs
+++ b/src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntityDeserializer.cs
@@ -48,16 +48,34 @@ namespace System.Web.Http.OData.Formatter.Deserialization
throw Error.Argument("entry", SRResources.ItemMustBeOfType, typeof(ODataEntry).Name);
}
- ODataEntryAnnotation entryAnnotation = entry.GetAnnotation<ODataEntryAnnotation>();
- Contract.Assert(entryAnnotation != null);
+ if (EdmEntityType.FullName() != entry.TypeName)
+ {
+ // received a derived type in a base type deserializer.
+ // delegate it to the appropriate derived type deserializer.
+ IEdmEntityType entityType = EdmModel.FindType(entry.TypeName) as IEdmEntityType;
+ Contract.Assert(entityType != null, "edmlib should have already validated that it knows the edm type and is the same as or derives from EdmEntityType");
- CreateEntityResource(entryAnnotation, EdmEntityType, readContext);
+ if (entityType.IsAbstract)
+ {
+ throw Error.InvalidOperation(SRResources.CannotInstantiateAbstractEntityType, entry.TypeName);
+ }
- RecurseEnter(readContext);
- ApplyEntityProperties(entry, entryAnnotation, readContext);
- RecurseLeave(readContext);
+ ODataEntityDeserializer deserializer = DeserializerProvider.GetODataDeserializer(new EdmEntityTypeReference(entityType, isNullable: false)) as ODataEntityDeserializer;
+ return deserializer.ReadInline(entry, readContext);
+ }
+ else
+ {
+ ODataEntryAnnotation entryAnnotation = entry.GetAnnotation<ODataEntryAnnotation>();
+ Contract.Assert(entryAnnotation != null);
+
+ CreateEntityResource(entryAnnotation, EdmEntityType, readContext);
- return entryAnnotation.EntityResource;
+ RecurseEnter(readContext);
+ ApplyEntityProperties(entry, entryAnnotation, readContext);
+ RecurseLeave(readContext);
+
+ return entryAnnotation.EntityResource;
+ }
}
internal static ODataItem ReadEntryOrFeed(ODataReader odataReader, ODataDeserializerContext readContext)
diff --git a/src/System.Web.Http.OData/OData/Formatter/EdmLibHelpers.cs b/src/System.Web.Http.OData/OData/Formatter/EdmLibHelpers.cs
index 16dc5f18..09868009 100644
--- a/src/System.Web.Http.OData/OData/Formatter/EdmLibHelpers.cs
+++ b/src/System.Web.Http.OData/OData/Formatter/EdmLibHelpers.cs
@@ -95,8 +95,13 @@ namespace System.Web.Http.OData.Formatter
throw Error.ArgumentNull("edmModel");
}
- IEdmPrimitiveType primitiveType = GetEdmPrimitiveTypeOrNull(clrType);
- if (primitiveType != null)
+ if (clrType == null)
+ {
+ throw Error.ArgumentNull("clrType");
+ }
+
+ IEdmPrimitiveType primitiveType;
+ if (_builtInTypesMapping.TryGetValue(clrType, out primitiveType))
{
return primitiveType;
}
@@ -124,6 +129,12 @@ namespace System.Web.Http.OData.Formatter
// default to the EdmType with the same name as the ClrType name
returnType = returnType ?? edmModel.FindType(clrType.EdmFullName());
+
+ if (clrType.BaseType != null)
+ {
+ // go up the inheritance tree to see if we have a mapping defined for the base type.
+ returnType = returnType ?? edmModel.GetEdmType(clrType.BaseType);
+ }
return returnType;
}
}
diff --git a/src/System.Web.Http.OData/OData/Formatter/ODataMediaTypeFormatter.cs b/src/System.Web.Http.OData/OData/Formatter/ODataMediaTypeFormatter.cs
index 7ea293ec..83747e58 100644
--- a/src/System.Web.Http.OData/OData/Formatter/ODataMediaTypeFormatter.cs
+++ b/src/System.Web.Http.OData/OData/Formatter/ODataMediaTypeFormatter.cs
@@ -185,43 +185,55 @@ namespace System.Web.Http.OData.Formatter
throw Error.ArgumentNull("readStream");
}
- object result = null;
-
- HttpContentHeaders contentHeaders = content == null ? null : content.Headers;
- // If content length is 0 then return default value for this type
- if (contentHeaders != null && contentHeaders.ContentLength == 0)
- {
- result = GetDefaultValueForType(type);
- }
- else
+ return TaskHelpers.RunSynchronously<object>(() =>
{
- bool isPatchMode = TryGetInnerTypeForDelta(ref type);
- ODataDeserializer deserializer = ODataDeserializerProvider.GetODataDeserializer(type);
- if (deserializer == null)
- {
- throw Error.Argument("type", SRResources.FormatterReadIsNotSupportedForType, type.FullName, GetType().FullName);
- }
+ object result;
- ODataMessageReader oDataMessageReader = null;
- ODataMessageReaderSettings oDataReaderSettings = new ODataMessageReaderSettings { DisableMessageStreamDisposal = true };
- try
+ HttpContentHeaders contentHeaders = content == null ? null : content.Headers;
+ // If content length is 0 then return default value for this type
+ if (contentHeaders != null && contentHeaders.ContentLength == 0)
{
- IODataRequestMessage oDataRequestMessage = new ODataMessageWrapper(readStream, contentHeaders);
- oDataMessageReader = new ODataMessageReader(oDataRequestMessage, oDataReaderSettings, ODataDeserializerProvider.EdmModel);
- ODataDeserializerContext readContext = new ODataDeserializerContext { IsPatchMode = isPatchMode, PatchKeyMode = PatchKeyMode };
-
- result = deserializer.Read(oDataMessageReader, readContext);
+ result = GetDefaultValueForType(type);
}
- finally
+ else
{
- if (oDataMessageReader != null)
+ bool isPatchMode = TryGetInnerTypeForDelta(ref type);
+ ODataDeserializer deserializer = ODataDeserializerProvider.GetODataDeserializer(type);
+ if (deserializer == null)
+ {
+ throw Error.Argument("type", SRResources.FormatterReadIsNotSupportedForType, type.FullName, GetType().FullName);
+ }
+
+ ODataMessageReader oDataMessageReader = null;
+ ODataMessageReaderSettings oDataReaderSettings = new ODataMessageReaderSettings { DisableMessageStreamDisposal = true };
+ try
{
- oDataMessageReader.Dispose();
+ IODataRequestMessage oDataRequestMessage = new ODataMessageWrapper(readStream, contentHeaders);
+ oDataMessageReader = new ODataMessageReader(oDataRequestMessage, oDataReaderSettings, ODataDeserializerProvider.EdmModel);
+ ODataDeserializerContext readContext = new ODataDeserializerContext { IsPatchMode = isPatchMode, PatchKeyMode = PatchKeyMode };
+ result = deserializer.Read(oDataMessageReader, readContext);
+ }
+ catch (Exception e)
+ {
+ if (formatterLogger == null)
+ {
+ throw;
+ }
+
+ formatterLogger.LogError(String.Empty, e);
+ result = GetDefaultValueForType(type);
+ }
+ finally
+ {
+ if (oDataMessageReader != null)
+ {
+ oDataMessageReader.Dispose();
+ }
}
}
- }
- return TaskHelpers.FromResult(result);
+ return result;
+ });
}
/// <inheritdoc/>
@@ -261,6 +273,8 @@ namespace System.Web.Http.OData.Formatter
odataFormat = GetODataFormat(contentHeaders);
}
+ // get the most appropriate serializer given that we support inheritance.
+ type = value == null ? type : value.GetType();
ODataSerializer serializer = ODataSerializerProvider.GetODataPayloadSerializer(type);
if (serializer == null)
{
diff --git a/src/System.Web.Http.OData/OData/Formatter/Serialization/DefaultODataSerializerProvider.cs b/src/System.Web.Http.OData/OData/Formatter/Serialization/DefaultODataSerializerProvider.cs
index d9ee6aa6..d2f6b2c5 100644
--- a/src/System.Web.Http.OData/OData/Formatter/Serialization/DefaultODataSerializerProvider.cs
+++ b/src/System.Web.Http.OData/OData/Formatter/Serialization/DefaultODataSerializerProvider.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+using System.Collections.Concurrent;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
using Microsoft.Data.OData;
@@ -11,6 +12,10 @@ namespace System.Web.Http.OData.Formatter.Serialization
/// </summary>
internal class DefaultODataSerializerProvider : ODataSerializerProvider
{
+ // cache the clrtype to ODataSerializer mappings as we might have to crawl the
+ // inheritance hirerachy to find the mapping.
+ private ConcurrentDictionary<Type, ODataSerializer> _clrTypeMappingCache = new ConcurrentDictionary<Type, ODataSerializer>();
+
public DefaultODataSerializerProvider(IEdmModel edmModel)
: base(edmModel)
{
@@ -70,21 +75,24 @@ namespace System.Web.Http.OData.Formatter.Serialization
{
return new ODataErrorSerializer();
}
- else if (type == typeof(IEdmModel))
+ else if (typeof(IEdmModel).IsAssignableFrom(type))
{
return new ODataMetadataSerializer();
}
// if it is not a special type, assume it has a corresponding EdmType.
- IEdmTypeReference edmType = EdmModel.GetEdmTypeReference(type);
- if (edmType != null)
- {
- return GetEdmTypeSerializer(edmType);
- }
- else
+ return _clrTypeMappingCache.GetOrAdd(type, (t) =>
{
- return null;
- }
+ IEdmTypeReference edmType = EdmModel.GetEdmTypeReference(t);
+ if (edmType != null)
+ {
+ return GetEdmTypeSerializer(edmType);
+ }
+ else
+ {
+ return null;
+ }
+ });
}
}
}
diff --git a/src/System.Web.Http.OData/OData/Formatter/Serialization/ODataFeedSerializer.cs b/src/System.Web.Http.OData/OData/Formatter/Serialization/ODataFeedSerializer.cs
index b4729e42..b0b61729 100644
--- a/src/System.Web.Http.OData/OData/Formatter/Serialization/ODataFeedSerializer.cs
+++ b/src/System.Web.Http.OData/OData/Formatter/Serialization/ODataFeedSerializer.cs
@@ -67,14 +67,6 @@ namespace System.Web.Http.OData.Formatter.Serialization
private void WriteFeed(object graph, ODataWriter writer, ODataSerializerContext writeContext)
{
- ODataSerializer entrySerializer = SerializerProvider.GetEdmTypeSerializer(_edmCollectionType.ElementType());
- if (entrySerializer == null)
- {
- throw Error.NotSupported(SRResources.TypeCannotBeSerialized, _edmCollectionType.ElementType(), typeof(ODataMediaTypeFormatter).Name);
- }
-
- Contract.Assert(entrySerializer.ODataPayloadKind == ODataPayloadKind.Entry);
-
IEnumerable enumerable = graph as IEnumerable; // Data to serialize
if (enumerable != null)
{
@@ -105,6 +97,19 @@ namespace System.Web.Http.OData.Formatter.Serialization
foreach (object entry in enumerable)
{
+ if (entry == null)
+ {
+ throw Error.NotSupported(SRResources.NullElementInCollection);
+ }
+
+ ODataSerializer entrySerializer = SerializerProvider.GetODataPayloadSerializer(entry.GetType());
+ if (entrySerializer == null)
+ {
+ throw Error.NotSupported(SRResources.TypeCannotBeSerialized, entry.GetType(), typeof(ODataMediaTypeFormatter).Name);
+ }
+
+ Contract.Assert(entrySerializer.ODataPayloadKind == ODataPayloadKind.Entry);
+
entrySerializer.WriteObjectInline(entry, writer, writeContext);
}
diff --git a/src/System.Web.Http.OData/Properties/SRResources.Designer.cs b/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
index 5ef39551..b9d46ea7 100644
--- a/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
+++ b/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
@@ -133,6 +133,15 @@ namespace System.Web.Http.OData.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to An instance of the abstract entity type &apos;{0}&apos; was found. Abstract entity types cannot be instantiated..
+ /// </summary>
+ internal static string CannotInstantiateAbstractEntityType {
+ get {
+ return ResourceManager.GetString("CannotInstantiateAbstractEntityType", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Failed to convert &apos;{0}&apos; to an integer..
/// </summary>
internal static string CanNotParseInteger {
@@ -655,6 +664,15 @@ namespace System.Web.Http.OData.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to Collections cannot contain null elements..
+ /// </summary>
+ internal static string NullElementInCollection {
+ get {
+ return ResourceManager.GetString("NullElementInCollection", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Unknown function &apos;{0}&apos;..
/// </summary>
internal static string ODataFunctionNotSupported {
diff --git a/src/System.Web.Http.OData/Properties/SRResources.resx b/src/System.Web.Http.OData/Properties/SRResources.resx
index d23f41aa..203d445c 100644
--- a/src/System.Web.Http.OData/Properties/SRResources.resx
+++ b/src/System.Web.Http.OData/Properties/SRResources.resx
@@ -402,6 +402,12 @@
<data name="FunctionNotSupportedOnEnum" xml:space="preserve">
<value>The '{0}' function cannot be applied to an enumeration-typed argument.</value>
</data>
+ <data name="CannotInstantiateAbstractEntityType" xml:space="preserve">
+ <value>An instance of the abstract entity type '{0}' was found. Abstract entity types cannot be instantiated.</value>
+ </data>
+ <data name="NullElementInCollection" xml:space="preserve">
+ <value>Collections cannot contain null elements.</value>
+ </data>
<data name="WriteToStreamAsyncMustHaveRequest" xml:space="preserve">
<value>The OData formatter does not support writing client requests. This formatter instance must have an associated request.</value>
</data>
diff --git a/test/System.Web.Http.OData.Test/OData/Builder/TestModels/InheritanceModels.cs b/test/System.Web.Http.OData.Test/OData/Builder/TestModels/InheritanceModels.cs
index e2a4b6e5..552f393d 100644
--- a/test/System.Web.Http.OData.Test/OData/Builder/TestModels/InheritanceModels.cs
+++ b/test/System.Web.Http.OData.Test/OData/Builder/TestModels/InheritanceModels.cs
@@ -12,7 +12,7 @@ namespace System.Web.Http.OData.Builder.TestModels
[Key]
public string Name { get; set; }
- public abstract int WheelCount { get; }
+ public abstract int WheelCount { get; set; }
}
public class Car : Vehicle
@@ -20,6 +20,7 @@ namespace System.Web.Http.OData.Builder.TestModels
public override int WheelCount
{
get { return 4; }
+ set { }
}
public int SeatingCapacity { get; set; }
@@ -30,6 +31,7 @@ namespace System.Web.Http.OData.Builder.TestModels
public override int WheelCount
{
get { return 2; }
+ set { }
}
public bool CanDoAWheelie { get; set; }
@@ -39,5 +41,6 @@ namespace System.Web.Http.OData.Builder.TestModels
public class SportBike : Motorcycle
{
+ public int SportBikeProperty_NotVisible { get; set; }
}
}
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntityDeserializerTests.cs b/test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntityDeserializerTests.cs
index ff3a51c0..0f835f5d 100644
--- a/test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntityDeserializerTests.cs
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntityDeserializerTests.cs
@@ -119,6 +119,18 @@ namespace System.Web.Http.OData.Formatter.Deserialization
Assert.Equal("123456", (supplier as dynamic).Address.ZipCode);
}
+ [Fact]
+ public void Read_Throws_On_UnknownEntityType()
+ {
+ IEdmEntityType supplierEntityType = EdmTestHelpers.GetModel().FindType("ODataDemo.Supplier") as IEdmEntityType;
+
+ ODataEntityDeserializer deserializer = new ODataEntityDeserializer(_productEdmType, _deserializerProvider);
+
+ Assert.Throws<ODataException>(
+ () => deserializer.Read(GetODataMessageReader(GetODataMessage(BaselineResource.SuppliersInsertData), _edmModel), _readContext),
+ "An entry with type 'ODataDemo.Supplier' was found, but it is not assignable to the expected type 'ODataDemo.Product'. The type specified in the entry must be equal to either the expected type or a derived type.");
+ }
+
private static Type EdmTypeResolver(IEdmTypeReference edmType)
{
return Type.GetType(edmType.FullName());
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/EdmLibHelpersTests.cs b/test/System.Web.Http.OData.Test/OData/Formatter/EdmLibHelpersTests.cs
index 7b148a2b..11a8ae11 100644
--- a/test/System.Web.Http.OData.Test/OData/Formatter/EdmLibHelpersTests.cs
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/EdmLibHelpersTests.cs
@@ -2,8 +2,11 @@
using System.Collections.Generic;
using System.Data.Linq;
+using System.Linq;
+using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Formatter.Serialization.Models;
using System.Xml.Linq;
+using Microsoft.Data.Edm;
using Microsoft.TestCommon;
namespace System.Web.Http.OData.Formatter
@@ -61,5 +64,68 @@ namespace System.Web.Http.OData.Formatter
Assert.False(isNonstandardEdmPrimtive);
Assert.Equal(primitiveType, resultMappedType);
}
+
+ [Fact]
+ public void GetEdmType_ReturnsBaseType()
+ {
+ IEdmModel model = GetEdmModel();
+ Assert.Equal(model.GetEdmType(typeof(BaseType)), model.SchemaElements.OfType<IEdmEntityType>().Where(t => t.Name == "BaseType").Single());
+ }
+
+ [Fact]
+ public void GetEdmType_ReturnsDerivedType()
+ {
+ IEdmModel model = GetEdmModel();
+ Assert.Equal(model.GetEdmType(typeof(DerivedTypeA)), model.SchemaElements.OfType<IEdmEntityType>().Where(t => t.Name == "DerivedTypeA").Single());
+ Assert.Equal(model.GetEdmType(typeof(DerivedTypeB)), model.SchemaElements.OfType<IEdmEntityType>().Where(t => t.Name == "DerivedTypeB").Single());
+ }
+
+ [Fact]
+ public void GetEdmType_Returns_NearestDerivedType()
+ {
+ IEdmModel model = GetEdmModel();
+ Assert.Equal(model.GetEdmType(typeof(DerivedTypeAA)), model.SchemaElements.OfType<IEdmEntityType>().Where(t => t.Name == "DerivedTypeA").Single());
+ }
+
+ [Fact]
+ public void GetEdmType_ReturnsNull_ForUnknownType()
+ {
+ IEdmModel model = GetEdmModel();
+ Assert.Null(model.GetEdmType(typeof(TypeNotInModel)));
+ }
+
+ private static IEdmModel GetEdmModel()
+ {
+ ODataModelBuilder modelBuilder = new ODataModelBuilder();
+ modelBuilder
+ .Entity<DerivedTypeA>()
+ .DerivesFrom<BaseType>();
+
+ modelBuilder
+ .Entity<DerivedTypeB>()
+ .DerivesFrom<BaseType>();
+
+ return modelBuilder.GetEdmModel();
+ }
+
+ public class BaseType
+ {
+ }
+
+ public class DerivedTypeA : BaseType
+ {
+ }
+
+ public class DerivedTypeB : BaseType
+ {
+ }
+
+ public class DerivedTypeAA : DerivedTypeA
+ {
+ }
+
+ public class TypeNotInModel
+ {
+ }
}
}
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/InheritanceTests.cs b/test/System.Web.Http.OData.Test/OData/Formatter/InheritanceTests.cs
new file mode 100644
index 00000000..829588f6
--- /dev/null
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/InheritanceTests.cs
@@ -0,0 +1,267 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Web.Http.OData.Builder;
+using System.Web.Http.OData.Builder.TestModels;
+using System.Web.Http.OData.Formatter;
+using System.Xml.Linq;
+using Microsoft.Data.Edm;
+using Microsoft.TestCommon;
+using Newtonsoft.Json.Linq;
+
+namespace System.Web.Http.OData.OData.Formatter
+{
+ public class InheritanceTests
+ {
+ ODataMediaTypeFormatter _formatter;
+ HttpConfiguration _configuration;
+ HttpServer _server;
+ HttpClient _client;
+ XNamespace _atomNamespace = "http://www.w3.org/2005/Atom";
+
+ public InheritanceTests()
+ {
+ _configuration = new HttpConfiguration();
+ _formatter = new ODataMediaTypeFormatter(GetEdmModel());
+
+ _configuration.Formatters.Clear();
+ _configuration.SetODataFormatter(_formatter);
+
+ _configuration.Routes.MapHttpRoute("default", "{action}", new { Controller = "Inheritance" });
+
+ _configuration.Routes.MapHttpRoute(ODataRouteNames.GetById, "{controller}({id})");
+ _configuration.Routes.MapHttpRoute(ODataRouteNames.Default, "{controller}");
+
+ _server = new HttpServer(_configuration);
+ _client = new HttpClient(_server);
+ }
+
+ [Fact]
+ public void Action_Can_Return_Entity_In_Inheritance()
+ {
+ HttpResponseMessage response = _client.SendAsync(GetODataRequest("http://localhost/GetMotorcycleAsVehicle")).Result;
+ response.EnsureSuccessStatusCode();
+
+ dynamic result = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ result = result.d;
+ ValidateMotorcycle(result);
+ }
+
+ [Fact]
+ public void Action_Can_Return_Car_As_vehicle()
+ {
+ HttpResponseMessage response = _client.SendAsync(GetODataRequest("http://localhost/GetCarAsVehicle")).Result;
+ response.EnsureSuccessStatusCode();
+
+ dynamic result = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ result = result.d;
+ ValidateCar(result);
+ }
+
+ [Fact]
+ public void Action_Can_Return_ClrType_NotInModel()
+ {
+ HttpResponseMessage response = _client.SendAsync(GetODataRequest("http://localhost/GetSportBikeAsVehicle")).Result;
+ response.EnsureSuccessStatusCode();
+
+ dynamic result = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ result = result.d;
+ ValidateSportbike(result);
+ }
+
+ [Fact]
+ public void Action_Can_Return_CollectionOfEntities()
+ {
+ HttpResponseMessage response = _client.SendAsync(GetODataRequest("http://localhost/GetVehicles")).Result;
+ response.EnsureSuccessStatusCode();
+
+ dynamic result = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ result = result.d;
+
+ ValidateMotorcycle(result.results[0]);
+ ValidateCar(result.results[1]);
+ ValidateSportbike(result.results[2]);
+ }
+
+ [Fact]
+ public void Action_Can_Take_Entity_In_Inheritance()
+ {
+ Stream body = GetResponseStream("http://localhost/GetMotorcycleAsVehicle", "application/atom+xml");
+
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/PostMotorcycle_When_Expecting_Motorcycle");
+ request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata=verbose"));
+ request.Content = new StreamContent(body);
+ request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/atom+xml");
+
+ HttpResponseMessage response = _client.SendAsync(request).Result;
+ response.EnsureSuccessStatusCode();
+
+ dynamic result = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ result = result.d;
+
+ ValidateMotorcycle(result);
+ }
+
+ [Fact]
+ public void Can_Post_DerivedType_To_Action_Expecting_BaseType()
+ {
+ Stream body = GetResponseStream("http://localhost/GetMotorcycleAsVehicle", "application/atom+xml");
+
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/PostMotorcycle_When_Expecting_Vehicle");
+ request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata=verbose"));
+ request.Content = new StreamContent(body);
+ request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/atom+xml");
+
+ HttpResponseMessage response = _client.SendAsync(request).Result;
+ response.EnsureSuccessStatusCode();
+
+ dynamic result = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ result = result.d;
+
+ ValidateMotorcycle(result);
+ }
+
+ [Fact]
+ public void Posting_NonDerivedType_To_Action_Expecting_BaseType_Throws()
+ {
+ Stream body = GetResponseStream("http://localhost/GetMotorcycleAsVehicle", "application/atom+xml");
+
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/PostMotorcycle_When_Expecting_Car");
+ request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata=verbose"));
+ request.Content = new StreamContent(body);
+ request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/atom+xml");
+
+ HttpResponseMessage response = _client.SendAsync(request).Result;
+ response.EnsureSuccessStatusCode();
+ Assert.Contains(
+ "An entry with type 'System.Web.Http.OData.Builder.TestModels.Motorcycle' was found, " +
+ "but it is not assignable to the expected type 'System.Web.Http.OData.Builder.TestModels.Car'. " +
+ "The type specified in the entry must be equal to either the expected type or a derived type.",
+ response.Content.ReadAsStringAsync().Result);
+ }
+
+ private Stream GetResponseStream(string uri, string contentType)
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(contentType));
+ HttpResponseMessage response = _client.SendAsync(request).Result;
+ response.EnsureSuccessStatusCode();
+ Stream stream = response.Content.ReadAsStreamAsync().Result;
+
+ return stream;
+ }
+
+ private static void ValidateMotorcycle(dynamic result)
+ {
+ Assert.Equal("System.Web.Http.OData.Builder.TestModels.Motorcycle", (string)result.__metadata.type);
+ Assert.Equal("sample motorcycle", (string)result.Name);
+ Assert.Equal("2009", (string)result.Model);
+ Assert.Equal(2, (int)result.WheelCount);
+ Assert.Equal(true, (bool)result.CanDoAWheelie);
+ }
+
+ private static void ValidateCar(dynamic result)
+ {
+ Assert.Equal("System.Web.Http.OData.Builder.TestModels.Car", (string)result.__metadata.type);
+ Assert.Equal("sample car", (string)result.Name);
+ Assert.Equal("2009", (string)result.Model);
+ Assert.Equal(4, (int)result.WheelCount);
+ Assert.Equal(5, (int)result.SeatingCapacity);
+ }
+
+ private static void ValidateSportbike(dynamic result)
+ {
+ Assert.Equal("System.Web.Http.OData.Builder.TestModels.Motorcycle", (string)result.__metadata.type);
+ Assert.Equal("sample sportsbike", (string)result.Name);
+ Assert.Equal("2009", (string)result.Model);
+ Assert.Equal(2, (int)result.WheelCount);
+ Assert.Equal(true, (bool)result.CanDoAWheelie);
+ Assert.Null(result.SportBikeProperty_NotVisible);
+ }
+
+ private static HttpRequestMessage GetODataRequest(string uri)
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata=verbose"));
+ return request;
+ }
+
+ private static IEdmModel GetEdmModel()
+ {
+ ODataModelBuilder builder = new ODataModelBuilder();
+
+ builder
+ .Entity<Vehicle>()
+ .HasKey(v => v.Name)
+ .HasKey(v => v.Model)
+ .Property(v => v.WheelCount);
+
+ builder
+ .Entity<Motorcycle>()
+ .DerivesFrom<Vehicle>()
+ .Property(m => m.CanDoAWheelie);
+
+ builder
+ .Entity<Car>()
+ .DerivesFrom<Vehicle>()
+ .Property(c => c.SeatingCapacity);
+
+ builder.EntitySet<Vehicle>("vehicles");
+ builder.EntitySet<Motorcycle>("motorcycles");
+ builder.EntitySet<Car>("cars");
+
+ return builder.GetEdmModel();
+ }
+ }
+
+ public class InheritanceController : ApiController
+ {
+ public static Motorcycle motorcycle = new Motorcycle { Model = 2009, Name = "sample motorcycle", CanDoAWheelie = true };
+ public static Car car = new Car { Model = 2009, Name = "sample car", SeatingCapacity = 5 };
+ public static SportBike sportBike = new SportBike { Model = 2009, Name = "sample sportsbike", CanDoAWheelie = true, SportBikeProperty_NotVisible = 100 };
+
+ public Vehicle GetMotorcycleAsVehicle()
+ {
+ return motorcycle;
+ }
+
+ public Vehicle GetCarAsVehicle()
+ {
+ return car;
+ }
+
+ public Vehicle GetSportBikeAsVehicle()
+ {
+ return sportBike;
+ }
+
+ public IEnumerable<Vehicle> GetVehicles()
+ {
+ return new Vehicle[] { motorcycle, car, sportBike };
+ }
+
+ public Motorcycle PostMotorcycle_When_Expecting_Motorcycle(Motorcycle motorcycle)
+ {
+ Assert.IsType<Motorcycle>(motorcycle);
+ return motorcycle;
+ }
+
+ public Vehicle PostMotorcycle_When_Expecting_Vehicle(Vehicle motorcycle)
+ {
+ Assert.IsType<Motorcycle>(motorcycle);
+ return motorcycle;
+ }
+
+ public string PostMotorcycle_When_Expecting_Car(Car car)
+ {
+ Assert.Null(car);
+ var carErrors = ModelState["car"];
+ Assert.NotNull(carErrors);
+
+ return carErrors.Errors[0].Exception.Message;
+ }
+ }
+}
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/DefaultODataSerializerProviderTests.cs b/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/DefaultODataSerializerProviderTests.cs
index e09d7296..25ed3758 100644
--- a/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/DefaultODataSerializerProviderTests.cs
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/DefaultODataSerializerProviderTests.cs
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.IO;
using System.Web.Http.OData.Formatter.Deserialization;
using Microsoft.Data.Edm;
-using Microsoft.Data.Edm.Library;
using Microsoft.Data.OData;
using Microsoft.TestCommon;
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataEntityTypeSerializerTests.cs b/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataEntityTypeSerializerTests.cs
index 9b5d85c5..57a4ac8a 100644
--- a/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataEntityTypeSerializerTests.cs
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataEntityTypeSerializerTests.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.OData.Builder;
+using System.Web.Http.OData.Formatter.Deserialization;
using System.Web.Http.Routing;
using Microsoft.Data.Edm;
using Microsoft.Data.Edm.Library;
@@ -25,6 +26,10 @@ namespace System.Web.Http.OData.Formatter.Serialization
public ODataEntityTypeSerializerTests()
{
_model = SerializationTestsHelpers.SimpleCustomerOrderModel();
+
+ _model.SetAnnotationValue<ClrTypeAnnotation>(_model.FindType("Default.Customer"), new ClrTypeAnnotation(typeof(Customer)));
+ _model.SetAnnotationValue<ClrTypeAnnotation>(_model.FindType("Default.Order"), new ClrTypeAnnotation(typeof(Order)));
+
_customerSet = _model.FindDeclaredEntityContainer("Default.Container").FindEntitySet("Customers");
_customer = new Customer()
{
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataFeedSerializerTests.cs b/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataFeedSerializerTests.cs
index 98a3f03a..0ad96dcd 100644
--- a/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataFeedSerializerTests.cs
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataFeedSerializerTests.cs
@@ -60,7 +60,7 @@ namespace System.Web.Http.OData.Formatter.Serialization
var mockWriter = new Mock<ODataWriter>();
mockSerializerProvider
- .Setup(p => p.CreateEdmTypeSerializer(_customersType.ElementType()))
+ .Setup(p => p.GetODataPayloadSerializer(typeof(Customer)))
.Returns(mockCustomerSerializer.Object);
mockCustomerSerializer
.Setup(s => s.WriteObjectInline(_customers[0], It.IsAny<ODataWriter>(), _writeContext))
@@ -104,7 +104,7 @@ namespace System.Web.Http.OData.Formatter.Serialization
expectedInlineCount
);
mockSerializerProvider
- .Setup(p => p.CreateEdmTypeSerializer(_customersType.ElementType()))
+ .Setup(p => p.GetODataPayloadSerializer(typeof(Customer)))
.Returns(mockCustomerSerializer.Object);
mockCustomerSerializer
.Setup(s => s.WriteObjectInline(_customers[0], It.IsAny<ODataWriter>(), _writeContext))
diff --git a/test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj b/test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj
index 3b4aadbb..686a85d9 100644
--- a/test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj
+++ b/test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj
@@ -107,6 +107,7 @@
<Compile Include="OData\Builder\ParameterConfigurationTest.cs" />
<Compile Include="OData\Builder\CollectionPropertyConfigurationTest.cs" />
<Compile Include="OData\Builder\TestModels\EnumModel.cs" />
+ <Compile Include="OData\Formatter\InheritanceTests.cs" />
<Compile Include="OData\Formatter\PartialTrustTest.cs" />
<Compile Include="OData\Builder\EdmTypeConfigurationExtensionsTest.cs" />
<Compile Include="OData\Builder\TestModels\InheritanceModels.cs" />