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-06 21:36:52 +0400
committerraghuramn <ranadimi@microsoft.com>2012-10-05 05:47:14 +0400
commit504b0aa28c37ee1d7940e789d28e3020297be42a (patch)
treed76fad336b1937b65a348a02ccdb9d2059b606f9
parent5c7bb4a4fc34b67ca3648afa569b035efe46892c (diff)
Issue 325: Add parameter binder for supporting odata literal format.
Primitives in OData url's have a different representation than the normal webapi way. For example, Guid's are represented as guid'0000-00....' . default webapi model binding fails for these url's. This commit adds a ODataModelBinderProvider that can deal with such urls.
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntryDeserializer.cs82
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/EdmPrimitiveHelpers.cs90
-rw-r--r--src/System.Web.Http.OData/OData/Formatter/ODataModelBinderProvider.cs115
-rw-r--r--src/System.Web.Http.OData/Properties/SRResources.Designer.cs24
-rw-r--r--src/System.Web.Http.OData/Properties/SRResources.resx12
-rw-r--r--src/System.Web.Http.OData/System.Web.Http.OData.csproj2
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntryDeserializerTests.cs68
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/EdmPrimitiveHelpersTest.cs77
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/ODataModelBinderProviderTest.cs401
-rw-r--r--test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataPrimitiveSerializerTests.cs3
-rw-r--r--test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj2
11 files changed, 719 insertions, 157 deletions
diff --git a/src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntryDeserializer.cs b/src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntryDeserializer.cs
index 9c34ef9d..85d807de 100644
--- a/src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntryDeserializer.cs
+++ b/src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntryDeserializer.cs
@@ -2,15 +2,11 @@
using System.Collections;
using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Data.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
-using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web.Http.OData.Properties;
-using System.Xml.Linq;
using Microsoft.Data.Edm;
using Microsoft.Data.Edm.Library;
using Microsoft.Data.OData;
@@ -124,7 +120,7 @@ namespace System.Web.Http.OData.Formatter.Deserialization
if (propertyKind == EdmTypeKind.Primitive)
{
- value = ConvertPrimitiveValue(value, GetPropertyType(resource, propertyName, isDelta), propertyName, resource.GetType().FullName);
+ value = EdmPrimitiveHelpers.ConvertPrimitiveValue(value, GetPropertyType(resource, propertyName, isDelta));
}
SetProperty(resource, propertyName, isDelta, value);
@@ -191,82 +187,6 @@ namespace System.Web.Http.OData.Formatter.Deserialization
}
}
- internal static object ConvertPrimitiveValue(object value, Type type, string propertyName, string typeName)
- {
- Contract.Assert(value != null);
- Contract.Assert(type != null);
-
- // if value is of the same type nothing to do here.
- if (value.GetType() == type || value.GetType() == Nullable.GetUnderlyingType(type))
- {
- return value;
- }
-
- string str = value as string;
-
- if (type == typeof(char))
- {
- if (str == null || str.Length != 1)
- {
- throw new ValidationException(Error.Format(SRResources.PropertyMustBeStringLengthOne, propertyName, typeName));
- }
-
- return str[0];
- }
- else if (type == typeof(char?))
- {
- if (str == null || str.Length > 1)
- {
- throw new ValidationException(Error.Format(SRResources.PropertyMustBeStringMaxLengthOne, propertyName, typeName));
- }
-
- return str.Length > 0 ? str[0] : (char?)null;
- }
- else if (type == typeof(char[]))
- {
- if (str == null)
- {
- throw new ValidationException(Error.Format(SRResources.PropertyMustBeString, propertyName, typeName));
- }
-
- return str.ToCharArray();
- }
- else if (type == typeof(Binary))
- {
- return new Binary((byte[])value);
- }
- else if (type == typeof(XElement))
- {
- if (str == null)
- {
- throw new ValidationException(Error.Format(SRResources.PropertyMustBeString, propertyName, typeName));
- }
-
- return XElement.Parse(str);
- }
- else
- {
- type = Nullable.GetUnderlyingType(type) ?? type;
- if (type.IsEnum)
- {
- if (str == null)
- {
- throw new ValidationException(Error.Format(SRResources.PropertyMustBeString, propertyName, typeName));
- }
-
- return Enum.Parse(type, str);
- }
- else
- {
- Contract.Assert(type == typeof(uint) || type == typeof(ushort) || type == typeof(ulong));
-
- // Note that we are not casting the return value to nullable<T> as even if we do it
- // CLR would unbox it back to T.
- return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
- }
- }
- }
-
private static object ConvertComplexValue(ODataComplexValue complexValue, ref IEdmTypeReference propertyType, ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext)
{
IEdmComplexTypeReference edmComplexType;
diff --git a/src/System.Web.Http.OData/OData/Formatter/EdmPrimitiveHelpers.cs b/src/System.Web.Http.OData/OData/Formatter/EdmPrimitiveHelpers.cs
new file mode 100644
index 00000000..7da42631
--- /dev/null
+++ b/src/System.Web.Http.OData/OData/Formatter/EdmPrimitiveHelpers.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using System.ComponentModel.DataAnnotations;
+using System.Data.Linq;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Web.Http.OData.Properties;
+using System.Xml.Linq;
+
+namespace System.Web.Http.OData.Formatter
+{
+ internal static class EdmPrimitiveHelpers
+ {
+ public static object ConvertPrimitiveValue(object value, Type type)
+ {
+ Contract.Assert(value != null);
+ Contract.Assert(type != null);
+
+ // if value is of the same type nothing to do here.
+ if (value.GetType() == type || value.GetType() == Nullable.GetUnderlyingType(type))
+ {
+ return value;
+ }
+
+ string str = value as string;
+
+ if (type == typeof(char))
+ {
+ if (str == null || str.Length != 1)
+ {
+ throw new ValidationException(Error.Format(SRResources.PropertyMustBeStringLengthOne));
+ }
+
+ return str[0];
+ }
+ else if (type == typeof(char?))
+ {
+ if (str == null || str.Length > 1)
+ {
+ throw new ValidationException(Error.Format(SRResources.PropertyMustBeStringMaxLengthOne));
+ }
+
+ return str.Length > 0 ? str[0] : (char?)null;
+ }
+ else if (type == typeof(char[]))
+ {
+ if (str == null)
+ {
+ throw new ValidationException(Error.Format(SRResources.PropertyMustBeString));
+ }
+
+ return str.ToCharArray();
+ }
+ else if (type == typeof(Binary))
+ {
+ return new Binary((byte[])value);
+ }
+ else if (type == typeof(XElement))
+ {
+ if (str == null)
+ {
+ throw new ValidationException(Error.Format(SRResources.PropertyMustBeString));
+ }
+
+ return XElement.Parse(str);
+ }
+ else
+ {
+ type = Nullable.GetUnderlyingType(type) ?? type;
+ if (type.IsEnum)
+ {
+ if (str == null)
+ {
+ throw new ValidationException(Error.Format(SRResources.PropertyMustBeString));
+ }
+
+ return Enum.Parse(type, str);
+ }
+ else
+ {
+ Contract.Assert(type == typeof(uint) || type == typeof(ushort) || type == typeof(ulong));
+
+ // Note that we are not casting the return value to nullable<T> as even if we do it
+ // CLR would unbox it back to T.
+ return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Http.OData/OData/Formatter/ODataModelBinderProvider.cs b/src/System.Web.Http.OData/OData/Formatter/ODataModelBinderProvider.cs
new file mode 100644
index 00000000..abb6c569
--- /dev/null
+++ b/src/System.Web.Http.OData/OData/Formatter/ODataModelBinderProvider.cs
@@ -0,0 +1,115 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Http.Controllers;
+using System.Web.Http.ModelBinding;
+using System.Web.Http.OData.Properties;
+using System.Web.Http.ValueProviders;
+using Microsoft.Data.OData;
+using Microsoft.Data.OData.Query;
+
+namespace System.Web.Http.OData.Formatter
+{
+ /// <summary>
+ /// Provides a <see cref="IModelBinder"/> for EDM primitive types.
+ /// </summary>
+ public class ODataModelBinderProvider : ModelBinderProvider
+ {
+ public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
+ {
+ if (configuration == null)
+ {
+ throw Error.ArgumentNull("configuration");
+ }
+
+ if (modelType == null)
+ {
+ throw Error.ArgumentNull("modelType");
+ }
+
+ if (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(modelType) != null)
+ {
+ return new ODataModelBinder();
+ }
+
+ return null;
+ }
+
+ internal class ODataModelBinder : IModelBinder
+ {
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't want to fail in model binding.")]
+ public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
+ {
+ if (bindingContext == null)
+ {
+ throw Error.ArgumentNull("bindingContext");
+ }
+
+ if (bindingContext.ModelMetadata == null)
+ {
+ throw Error.Argument("bindingContext", SRResources.ModelBinderUtil_ModelMetadataCannotBeNull);
+ }
+
+ ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
+ if (value == null)
+ {
+ return false;
+ }
+ bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
+
+ try
+ {
+ string valueString = value.RawValue as string;
+ object model = ConvertTo(valueString, bindingContext.ModelType);
+ bindingContext.Model = model;
+ return true;
+ }
+ catch (ODataException ex)
+ {
+ bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);
+ return false;
+ }
+ catch (ValidationException ex)
+ {
+ bindingContext.ModelState.AddModelError(bindingContext.ModelName, Error.Format(SRResources.ValueIsInvalid, value.RawValue, ex.Message));
+ return false;
+ }
+ catch (FormatException ex)
+ {
+ bindingContext.ModelState.AddModelError(bindingContext.ModelName, Error.Format(SRResources.ValueIsInvalid, value.RawValue, ex.Message));
+ return false;
+ }
+ catch (Exception e)
+ {
+ bindingContext.ModelState.AddModelError(bindingContext.ModelName, e);
+ return false;
+ }
+ }
+
+ internal static object ConvertTo(string valueString, Type type)
+ {
+ if (valueString == null)
+ {
+ return null;
+ }
+
+ object value = ODataUriUtils.ConvertFromUriLiteral(valueString, ODataVersion.V3);
+
+ bool isNonStandardEdmPrimitive;
+ EdmLibHelpers.IsNonstandardEdmPrimitive(type, out isNonStandardEdmPrimitive);
+
+ if (isNonStandardEdmPrimitive)
+ {
+ return EdmPrimitiveHelpers.ConvertPrimitiveValue(value, type);
+ }
+ else
+ {
+ type = Nullable.GetUnderlyingType(type) ?? type;
+ return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Http.OData/Properties/SRResources.Designer.cs b/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
index 16269bdf..e661c268 100644
--- a/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
+++ b/src/System.Web.Http.OData/Properties/SRResources.Designer.cs
@@ -538,6 +538,15 @@ namespace System.Web.Http.OData.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to The binding context cannot have a null ModelMetadata..
+ /// </summary>
+ internal static string ModelBinderUtil_ModelMetadataCannotBeNull {
+ get {
+ return ResourceManager.GetString("ModelBinderUtil_ModelMetadataCannotBeNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to More than one Procedure called &apos;{0}&apos; was found. Try using the other RemoveProcedure override..
/// </summary>
internal static string MoreThanOneProcedureFound {
@@ -763,7 +772,7 @@ namespace System.Web.Http.OData.Properties {
}
/// <summary>
- /// Looks up a localized string similar to The property &apos;{0}&apos; on type &apos;{1}&apos; must be a string..
+ /// Looks up a localized string similar to The value must be a string..
/// </summary>
internal static string PropertyMustBeString {
get {
@@ -772,7 +781,7 @@ namespace System.Web.Http.OData.Properties {
}
/// <summary>
- /// Looks up a localized string similar to The property &apos;{0}&apos; on type &apos;{1}&apos; must be a string with a length of 1..
+ /// Looks up a localized string similar to The value must be a string with a length of 1..
/// </summary>
internal static string PropertyMustBeStringLengthOne {
get {
@@ -781,7 +790,7 @@ namespace System.Web.Http.OData.Properties {
}
/// <summary>
- /// Looks up a localized string similar to The property &apos;{0}&apos; on type &apos;{1}&apos; must be a string with a maximum length of 1..
+ /// Looks up a localized string similar to The value must be a string with a maximum length of 1..
/// </summary>
internal static string PropertyMustBeStringMaxLengthOne {
get {
@@ -952,6 +961,15 @@ namespace System.Web.Http.OData.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to The value &apos;{0}&apos; is invalid. {1}.
+ /// </summary>
+ internal static string ValueIsInvalid {
+ get {
+ return ResourceManager.GetString("ValueIsInvalid", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to {0} does not support WriteObjectInline..
/// </summary>
internal static string WriteObjectInlineNotSupported {
diff --git a/src/System.Web.Http.OData/Properties/SRResources.resx b/src/System.Web.Http.OData/Properties/SRResources.resx
index 462b533f..a501622f 100644
--- a/src/System.Web.Http.OData/Properties/SRResources.resx
+++ b/src/System.Web.Http.OData/Properties/SRResources.resx
@@ -337,13 +337,13 @@
<value>Cannot apply PATCH to navigation property '{0}' on entity type '{1}'.</value>
</data>
<data name="PropertyMustBeString" xml:space="preserve">
- <value>The property '{0}' on type '{1}' must be a string.</value>
+ <value>The value must be a string.</value>
</data>
<data name="PropertyMustBeStringLengthOne" xml:space="preserve">
- <value>The property '{0}' on type '{1}' must be a string with a length of 1.</value>
+ <value>The value must be a string with a length of 1.</value>
</data>
<data name="PropertyMustBeStringMaxLengthOne" xml:space="preserve">
- <value>The property '{0}' on type '{1}' must be a string with a maximum length of 1.</value>
+ <value>The value must be a string with a maximum length of 1.</value>
</data>
<data name="ArgumentMustBeOfType" xml:space="preserve">
<value>The argument must be of type '{0}'.</value>
@@ -423,4 +423,10 @@
<data name="ResultLimitMustBePositive" xml:space="preserve">
<value>The result limit must be a positive number.</value>
</data>
+ <data name="ValueIsInvalid" xml:space="preserve">
+ <value>The value '{0}' is invalid. {1}</value>
+ </data>
+ <data name="ModelBinderUtil_ModelMetadataCannotBeNull" xml:space="preserve">
+ <value>The binding context cannot have a null ModelMetadata.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/System.Web.Http.OData/System.Web.Http.OData.csproj b/src/System.Web.Http.OData/System.Web.Http.OData.csproj
index e61d613b..54bf0feb 100644
--- a/src/System.Web.Http.OData/System.Web.Http.OData.csproj
+++ b/src/System.Web.Http.OData/System.Web.Http.OData.csproj
@@ -174,10 +174,12 @@
<Compile Include="OData\Formatter\Deserialization\ODataNavigationLinkAnnotation.cs" />
<Compile Include="OData\Formatter\Deserialization\ODataPrimitiveDeserializer.cs" />
<Compile Include="OData\FeedContext.cs" />
+ <Compile Include="OData\Formatter\EdmPrimitiveHelpers.cs" />
<Compile Include="OData\Formatter\EdmTypeReferenceEqualityComparer.cs" />
<Compile Include="OData\Formatter\ODataFormatterParameterBinding.cs" />
<Compile Include="OData\Formatter\PatchKeyMode.cs" />
<Compile Include="OData\Formatter\PatchKeyModeHelper.cs" />
+ <Compile Include="OData\Formatter\ODataModelBinderProvider.cs" />
<Compile Include="OData\Formatter\Serialization\ODataErrorSerializer.cs" />
<Compile Include="OData\Formatter\Serialization\ODataMetadataSerializer.cs" />
<Compile Include="OData\IDeltaOfTEntityType.cs" />
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntryDeserializerTests.cs b/test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntryDeserializerTests.cs
index 01a41dc1..fe64f145 100644
--- a/test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntryDeserializerTests.cs
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/Deserialization/ODataEntryDeserializerTests.cs
@@ -1,82 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
-using System.ComponentModel.DataAnnotations;
-using System.Data.Linq;
-using System.Xml.Linq;
using Microsoft.Data.Edm.Library;
using Microsoft.Data.OData;
using Microsoft.TestCommon;
-using Microsoft.TestCommon.Types;
using Moq;
namespace System.Web.Http.OData.Formatter.Deserialization
{
public class ODataEntryDeserializerTests
{
- public static TheoryDataSet<object, object, Type> ConvertPrimitiveValue_NonStandardPrimitives_Data
- {
- get
- {
- return new TheoryDataSet<object, object, Type>
- {
- { "1", (char)'1', typeof(char) },
- { "1", (char?)'1', typeof(char?) },
- { "123", (char[]) new char[] {'1', '2', '3' }, typeof(char[]) },
- { (int)1 , (ushort)1, typeof(ushort)},
- { (int?)1, (ushort?)1, typeof(ushort?) },
- { (long)1, (uint)1, typeof(uint) },
- { (long?)1, (uint?)1, typeof(uint?) },
- { (long)1 , (ulong)1, typeof(ulong)},
- { (long?)1 ,(ulong?)1, typeof(ulong?)},
- //(Stream) new MemoryStream(new byte[] { 1 }), // TODO: Enable once we have support for streams
- { "<element xmlns=\"namespace\" />" ,(XElement) new XElement(XName.Get("element","namespace")), typeof(XElement)},
- { new byte[] {1}, new Binary(new byte[] {1}), typeof(Binary)},
-
- // Enums
- { "Second", SimpleEnum.Second, typeof(SimpleEnum) },
- { "Second", SimpleEnum.Second, typeof(SimpleEnum?) },
- { "ThirdLong" , LongEnum.ThirdLong, typeof(LongEnum) },
- { "ThirdLong" , LongEnum.ThirdLong, typeof(LongEnum?) },
- { "One, Four" , FlagsEnum.One | FlagsEnum.Four, typeof(FlagsEnum) },
- { "One, Four" , FlagsEnum.One | FlagsEnum.Four, typeof(FlagsEnum?) }
- };
- }
- }
-
- [Theory]
- [PropertyData("ConvertPrimitiveValue_NonStandardPrimitives_Data")]
- public void ConvertPrimitiveValue_NonStandardPrimitives(object valueToConvert, object result, Type conversionType)
- {
- Assert.Equal(result.GetType(), ODataEntryDeserializer.ConvertPrimitiveValue(valueToConvert, conversionType, "", "").GetType());
- Assert.Equal(result.ToString(), ODataEntryDeserializer.ConvertPrimitiveValue(valueToConvert, conversionType, "", "").ToString());
- }
-
- [Theory]
- [InlineData("123")]
- [InlineData("")]
- public void ConvertPrimitiveValueToChar_Throws(string input)
- {
- Assert.Throws<ValidationException>(
- () => ODataEntryDeserializer.ConvertPrimitiveValue(input, typeof(char), "property", "type"),
- "The property 'property' on type 'type' must be a string with a length of 1.");
- }
-
- [Fact]
- public void ConvertPrimitiveValueToNullableChar_Throws()
- {
- Assert.Throws<ValidationException>(
- () => ODataEntryDeserializer.ConvertPrimitiveValue("123", typeof(char?), "property", "type"),
- "The property 'property' on type 'type' must be a string with a maximum length of 1.");
- }
-
- [Fact]
- public void ConvertPrimitiveValueToXElement_Throws_IfInputIsNotString()
- {
- Assert.Throws<ValidationException>(
- () => ODataEntryDeserializer.ConvertPrimitiveValue(123, typeof(XElement), "property", "type"),
- "The property 'property' on type 'type' must be a string.");
- }
-
[Theory]
[InlineData("Property", true, typeof(int))]
[InlineData("Property", false, typeof(int))]
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/EdmPrimitiveHelpersTest.cs b/test/System.Web.Http.OData.Test/OData/Formatter/EdmPrimitiveHelpersTest.cs
new file mode 100644
index 00000000..e0bbfd48
--- /dev/null
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/EdmPrimitiveHelpersTest.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using System.ComponentModel.DataAnnotations;
+using System.Data.Linq;
+using System.Xml.Linq;
+using Microsoft.TestCommon;
+using Microsoft.TestCommon.Types;
+
+namespace System.Web.Http.OData.Formatter
+{
+ public class EdmPrimitiveHelpersTest
+ {
+ public static TheoryDataSet<object, object, Type> ConvertPrimitiveValue_NonStandardPrimitives_Data
+ {
+ get
+ {
+ return new TheoryDataSet<object, object, Type>
+ {
+ { "1", (char)'1', typeof(char) },
+ { "1", (char?)'1', typeof(char?) },
+ { "123", (char[]) new char[] {'1', '2', '3' }, typeof(char[]) },
+ { (int)1 , (ushort)1, typeof(ushort)},
+ { (int?)1, (ushort?)1, typeof(ushort?) },
+ { (long)1, (uint)1, typeof(uint) },
+ { (long?)1, (uint?)1, typeof(uint?) },
+ { (long)1 , (ulong)1, typeof(ulong)},
+ { (long?)1 ,(ulong?)1, typeof(ulong?)},
+ //(Stream) new MemoryStream(new byte[] { 1 }), // TODO: Enable once we have support for streams
+ { "<element xmlns=\"namespace\" />" ,(XElement) new XElement(XName.Get("element","namespace")), typeof(XElement)},
+ { new byte[] {1}, new Binary(new byte[] {1}), typeof(Binary)},
+
+ // Enums
+ { "Second", SimpleEnum.Second, typeof(SimpleEnum) },
+ { "Second", SimpleEnum.Second, typeof(SimpleEnum?) },
+ { "ThirdLong" , LongEnum.ThirdLong, typeof(LongEnum) },
+ { "ThirdLong" , LongEnum.ThirdLong, typeof(LongEnum?) },
+ { "One, Four" , FlagsEnum.One | FlagsEnum.Four, typeof(FlagsEnum) },
+ { "One, Four" , FlagsEnum.One | FlagsEnum.Four, typeof(FlagsEnum?) }
+ };
+ }
+ }
+
+ [Theory]
+ [PropertyData("ConvertPrimitiveValue_NonStandardPrimitives_Data")]
+ public void ConvertPrimitiveValue_NonStandardPrimitives(object valueToConvert, object result, Type conversionType)
+ {
+ Assert.Equal(result.GetType(), EdmPrimitiveHelpers.ConvertPrimitiveValue(valueToConvert, conversionType).GetType());
+ Assert.Equal(result.ToString(), EdmPrimitiveHelpers.ConvertPrimitiveValue(valueToConvert, conversionType).ToString());
+ }
+
+ [Theory]
+ [InlineData("123")]
+ [InlineData("")]
+ public void ConvertPrimitiveValueToChar_Throws(string input)
+ {
+ Assert.Throws<ValidationException>(
+ () => EdmPrimitiveHelpers.ConvertPrimitiveValue(input, typeof(char)),
+ "The value must be a string with a length of 1.");
+ }
+
+ [Fact]
+ public void ConvertPrimitiveValueToNullableChar_Throws()
+ {
+ Assert.Throws<ValidationException>(
+ () => EdmPrimitiveHelpers.ConvertPrimitiveValue("123", typeof(char?)),
+ "The value must be a string with a maximum length of 1.");
+ }
+
+ [Fact]
+ public void ConvertPrimitiveValueToXElement_Throws_IfInputIsNotString()
+ {
+ Assert.Throws<ValidationException>(
+ () => EdmPrimitiveHelpers.ConvertPrimitiveValue(123, typeof(XElement)),
+ "The value must be a string.");
+ }
+ }
+}
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/ODataModelBinderProviderTest.cs b/test/System.Web.Http.OData.Test/OData/Formatter/ODataModelBinderProviderTest.cs
new file mode 100644
index 00000000..ae8e235d
--- /dev/null
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/ODataModelBinderProviderTest.cs
@@ -0,0 +1,401 @@
+// 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.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.ModelBinding;
+using System.Web.Http.Routing;
+using System.Web.Http.ValueProviders;
+using Microsoft.Data.OData;
+using Microsoft.Data.OData.Query;
+using Microsoft.TestCommon;
+using Microsoft.TestCommon.Types;
+
+namespace System.Web.Http.OData.Formatter
+{
+ public class ODataModelBinderProviderTest
+ {
+ private HttpConfiguration _configuration;
+ private HttpServer _server;
+ private HttpClient _client;
+
+ public ODataModelBinderProviderTest()
+ {
+ _configuration = new HttpConfiguration();
+ _configuration.Services.Replace(typeof(ModelBinderProvider), new ODataModelBinderProvider());
+
+ _configuration.Routes.MapHttpRoute("default_multiple_keys", "{controller}/{action}({key1}={value1},{key2}={value2})");
+ _configuration.Routes.MapHttpRoute("default", "{controller}/{action}({id})");
+
+ _server = new HttpServer(_configuration);
+ _client = new HttpClient(_server);
+ }
+
+ public static TheoryDataSet<object, string> ODataModelBinderProvider_Works_TestData
+ {
+ get
+ {
+ return new TheoryDataSet<object, string>
+ {
+ { true, "GetBool" },
+ { (short)123, "GetInt16"},
+ { (short)123, "GetUInt16"},
+ { (int)123, "GetInt32" },
+ { (int)123, "GetUInt32" },
+ { (long)123, "GetInt64" },
+ { (long)123, "GetUInt64" },
+ { (byte)1, "GetByte" },
+ { "123", "GetString" },
+ { Guid.Empty, "GetGuid" },
+ { DateTime.Now, "GetDateTime" },
+ { TimeSpan.FromTicks(424242), "GetTimeSpan" },
+ { DateTimeOffset.MaxValue, "GetDateTimeOffset" },
+ { float.NaN, "GetFloat" },
+ { decimal.MaxValue, "GetDecimal" },
+ // { double.NaN, "GetDouble" } // doesn't work with uri parser.
+ { SimpleEnum.First.ToString(), "GetEnum" },
+ { (FlagsEnum.One | FlagsEnum.Two).ToString(), "GetFlagsEnum" }
+ };
+ }
+ }
+
+ public static TheoryDataSet<object, string> ODataModelBinderProvider_Throws_TestData
+ {
+ get
+ {
+ return new TheoryDataSet<object, string>
+ {
+ { "123", "GetBool" },
+ { 123, "GetDateTime" },
+ { "abc", "GetInt32" },
+ { "abc", "GetEnum" },
+ { "abc", "GetGuid" },
+ { "abc", "GetByte" },
+ { "abc", "GetFloat" },
+ { "abc", "GetDouble" },
+ { "abc", "GetDecimal" },
+ { "abc", "GetDateTime" },
+ { "abc", "GetTimeSpan" },
+ { "abc", "GetDateTimeOffset" },
+ { -1, "GetUInt16"},
+ { -1, "GetUInt32" },
+ { -1, "GetUInt64"},
+ };
+ }
+ }
+
+ public static TheoryDataSet<string, string, string> ODataModelBinderProvider_ModelStateErrors_InvalidODataRepresentations_TestData
+ {
+ get
+ {
+ return new TheoryDataSet<string, string, string>
+ {
+ { "abc", "GetNullableBool", "Expected literal type token but found token 'abc'." },
+ { "datetime'123'", "GetNullableDateTime", "Unrecognized 'Edm.DateTime' literal 'datetime'123'' at '0' in 'datetime'123''." }
+ };
+ }
+ }
+
+ public static TheoryDataSet<string, string, string> ODataModelBinderProvider_ModelStateErrors_InvalidConversions_TestData
+ {
+ get
+ {
+ return new TheoryDataSet<string, string, string>
+ {
+ { "'abc'", "GetNullableChar", "The value ''abc'' is invalid. The value must be a string with a maximum length of 1." },
+ { "'abc'", "GetDefaultChar", "The value ''abc'' is invalid. The value must be a string with a length of 1." },
+ { "-123", "GetDefaultUInt", "Value was either too large or too small for a UInt32." }
+ };
+ }
+ }
+
+ [Fact]
+ public void GetBinder_ThrowsArgumentNull_configuration()
+ {
+ ODataModelBinderProvider binderProvider = new ODataModelBinderProvider();
+
+ Assert.ThrowsArgumentNull(
+ () => binderProvider.GetBinder(configuration: null, modelType: typeof(int)),
+ "configuration");
+ }
+
+ [Fact]
+ public void GetBinder_ThrowsArgumentNull_modelType()
+ {
+ ODataModelBinderProvider binderProvider = new ODataModelBinderProvider();
+
+ Assert.ThrowsArgumentNull(
+ () => binderProvider.GetBinder(new HttpConfiguration(), modelType: null),
+ "modelType");
+ }
+
+ [Theory]
+ [PropertyData("ODataModelBinderProvider_Works_TestData")]
+ public void ODataModelBinderProvider_Works(object value, string action)
+ {
+ string url = String.Format("http://localhost/ODataModelBinderProviderTest/{0}({1})", action, Uri.EscapeDataString(ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V3)));
+ HttpResponseMessage response = _client.GetAsync(url).Result;
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(
+ value,
+ response.Content.ReadAsAsync(value.GetType(), _configuration.Formatters).Result);
+ }
+
+ [Theory]
+ [PropertyData("ODataModelBinderProvider_Throws_TestData")]
+ public void ODataModelBinderProvider_Throws(object value, string action)
+ {
+ string url = String.Format("http://localhost/ODataModelBinderProviderThrowsTest/{0}({1})", action, Uri.EscapeDataString(ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V3)));
+ HttpResponseMessage response = _client.GetAsync(url).Result;
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+
+ [Theory]
+ [PropertyData("ODataModelBinderProvider_ModelStateErrors_InvalidODataRepresentations_TestData")]
+ public void ODataModelBinderProvider_ModelStateErrors_InvalidODataRepresentations(string value, string action, string error)
+ {
+ string url = String.Format("http://localhost/ODataModelBinderProviderThrowsTest/{0}({1})", action, Uri.EscapeDataString(value));
+ HttpResponseMessage response = _client.GetAsync(url).Result;
+
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(
+ response.Content.ReadAsAsync<string[]>().Result,
+ new[] { error });
+ }
+
+ [Theory]
+ [PropertyData("ODataModelBinderProvider_ModelStateErrors_InvalidConversions_TestData")]
+ public void ODataModelBinderProvider_ModelStateErrors_InvalidConversions(string value, string action, string error)
+ {
+ string url = String.Format("http://localhost/ODataModelBinderProviderThrowsTest/{0}({1})", action, Uri.EscapeDataString(value));
+ HttpResponseMessage response = _client.GetAsync(url).Result;
+
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(
+ response.Content.ReadAsAsync<string[]>().Result,
+ new[] { error });
+ }
+
+ [Fact]
+ public void TestMultipleKeys()
+ {
+ string url = String.Format(
+ "http://localhost/ODataModeBinderMultipleKeys/GetMultipleKeys(name={0},model={1})",
+ Uri.EscapeDataString(ODataUriUtils.ConvertToUriLiteral("name", ODataVersion.V3)),
+ Uri.EscapeDataString(ODataUriUtils.ConvertToUriLiteral(2009, ODataVersion.V3)));
+
+ HttpResponseMessage response = _client.GetAsync(url).Result;
+
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(
+ "name-2009",
+ response.Content.ReadAsAsync<string>().Result);
+ }
+ }
+
+ public class ODataKeyAttribute : ModelBinderAttribute
+ {
+ public override IEnumerable<ValueProviderFactory> GetValueProviderFactories(HttpConfiguration configuration)
+ {
+ return new[] { new ODataKeysValueProviderFactory() };
+ }
+
+ internal class ODataKeysValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(HttpActionContext actionContext)
+ {
+ return new ODataKeysValueProvider(actionContext.ControllerContext.RouteData);
+ }
+
+ private class ODataKeysValueProvider : IValueProvider
+ {
+ private IHttpRouteData _routeData;
+
+ public ODataKeysValueProvider(IHttpRouteData routedata)
+ {
+ _routeData = routedata;
+ }
+
+ public bool ContainsPrefix(string prefix)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ IEnumerable<KeyValuePair<string, object>> match = _routeData.Values.Where(kvp => kvp.Value.Equals(key) && kvp.Key.StartsWith("key"));
+ if (match.Count() == 1)
+ {
+ KeyValuePair<string, object> data = match.First();
+ int index = Int32.Parse(data.Key.Replace("key", String.Empty));
+ object value = _routeData.Values[String.Format("value{0}", index)];
+ return new ValueProviderResult(value, value.ToString(), CultureInfo.InvariantCulture);
+ }
+
+ return null;
+ }
+ }
+ }
+ }
+
+ public class ODataModelBinderProviderTestController : ApiController
+ {
+ HttpResponseException _exception = new HttpResponseException(HttpStatusCode.NotImplemented);
+
+ public bool GetBool(bool id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public byte GetByte(byte id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public short GetInt16(short id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public ushort GetUInt16(ushort id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public int GetInt32(int id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public uint GetUInt32(uint id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public long GetInt64(long id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public ulong GetUInt64(ulong id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public string GetString(string id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public Guid GetGuid(Guid id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public DateTime GetDateTime(DateTime id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public TimeSpan GetTimeSpan(TimeSpan id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public DateTimeOffset GetDateTimeOffset(DateTimeOffset id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public float GetFloat(float id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public double GetDouble(double id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public decimal GetDecimal(decimal id)
+ {
+ ThrowIfInsideThrowsController();
+ return id;
+ }
+
+ public string GetEnum(SimpleEnum id)
+ {
+ ThrowIfInsideThrowsController();
+ return id.ToString();
+ }
+
+ public string GetFlagsEnum(FlagsEnum id)
+ {
+ ThrowIfInsideThrowsController();
+ return id.ToString();
+ }
+
+ private void ThrowIfInsideThrowsController()
+ {
+ if (Request.GetRouteData().Values["Controller"].Equals("ODataModelBinderProviderThrowsTest"))
+ {
+ throw new HttpResponseException(HttpStatusCode.NotImplemented);
+ }
+ }
+ }
+
+ public class ODataModelBinderProviderThrowsTestController : ODataModelBinderProviderTestController
+ {
+ public IEnumerable<string> GetNullableBool(bool? id)
+ {
+ return ModelState["id"].Errors.Select(e => e.ErrorMessage);
+ }
+
+ public IEnumerable<string> GetNullableDateTime(DateTime? id)
+ {
+ return ModelState["id"].Errors.Select(e => e.ErrorMessage);
+ }
+
+ public IEnumerable<string> GetNullableChar(char? id)
+ {
+ return ModelState["id"].Errors.Select(e => e.ErrorMessage);
+ }
+
+ public IEnumerable<string> GetDefaultChar(char id = 'a')
+ {
+ return ModelState["id"].Errors.Select(e => e.ErrorMessage);
+ }
+
+ public IEnumerable<string> GetDefaultUInt(uint id = 0)
+ {
+ return ModelState["id"].Errors.Select(e => e.Exception.Message);
+ }
+ }
+
+ public class ODataModeBinderMultipleKeysController : ApiController
+ {
+ public string GetMultipleKeys([ODataKey]string name, [ODataKey]int model)
+ {
+ return name + "-" + model;
+ }
+ }
+}
diff --git a/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataPrimitiveSerializerTests.cs b/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataPrimitiveSerializerTests.cs
index dbfcf9c0..2601a2f3 100644
--- a/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataPrimitiveSerializerTests.cs
+++ b/test/System.Web.Http.OData.Test/OData/Formatter/Serialization/ODataPrimitiveSerializerTests.cs
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Data.Linq;
using System.IO;
using System.Linq;
-using System.Web.Http.OData.Formatter.Deserialization;
using System.Xml.Linq;
using Microsoft.Data.Edm;
using Microsoft.Data.Edm.Library;
@@ -20,7 +19,7 @@ namespace System.Web.Http.OData.Formatter.Serialization
{
get
{
- return ODataEntryDeserializerTests
+ return EdmPrimitiveHelpersTest
.ConvertPrimitiveValue_NonStandardPrimitives_Data
.Select(data => new[] { data[1], data[0] });
}
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 64dc71a1..93fc186c 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
@@ -111,6 +111,8 @@
<Compile Include="OData\Formatter\ODataActionTests.cs" />
<Compile Include="OData\Formatter\InheritanceTests.cs" />
<Compile Include="OData\Formatter\Deserialization\ODataActionPayloadDeserializerTest.cs" />
+ <Compile Include="OData\Formatter\EdmPrimitiveHelpersTest.cs" />
+ <Compile Include="OData\Formatter\ODataModelBinderProviderTest.cs" />
<Compile Include="OData\Formatter\PartialTrustTest.cs" />
<Compile Include="OData\Builder\EdmTypeConfigurationExtensionsTest.cs" />
<Compile Include="OData\Builder\TestModels\InheritanceModels.cs" />