diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataEnumDeserializer.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataEnumDeserializer.cs
index c25943ade..6dc40ea5f 100644
--- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataEnumDeserializer.cs
+++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataEnumDeserializer.cs
@@ -9,10 +9,12 @@
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.OData.Common;
using Microsoft.AspNetCore.OData.Edm;
using Microsoft.AspNetCore.OData.Formatter.Value;
using Microsoft.OData;
using Microsoft.OData.Edm;
+using Microsoft.OData.ModelBuilder;
namespace Microsoft.AspNetCore.OData.Formatter.Deserialization;
@@ -83,6 +85,8 @@ public override object ReadInline(object item, IEdmTypeReference edmType, ODataD
IEdmEnumType enumType = enumTypeReference.EnumDefinition();
+ Type clrType = readContext.Model.GetClrType(edmType);
+
// Enum member supports model alias case. So, try to use the Edm member name to retrieve the Enum value.
var memberMapAnnotation = readContext.Model.GetClrEnumMemberAnnotation(enumType);
if (memberMapAnnotation != null)
@@ -98,10 +102,89 @@ public override object ReadInline(object item, IEdmTypeReference edmType, ODataD
return clrMember;
}
}
+ else if (enumType.IsFlags)
+ {
+ var result = ReadFlagsEnumValue(enumValue, enumType, clrType, memberMapAnnotation);
+ if (result != null)
+ {
+ return result;
+ }
+ }
}
}
- Type clrType = readContext.Model.GetClrType(edmType);
return EnumDeserializationHelpers.ConvertEnumValue(item, clrType);
}
+
+ ///
+ /// Reads the value of a flags enum.
+ ///
+ /// The OData enum value.
+ /// The EDM enum type.
+ /// The EDM enum CLR type.
+ /// The annotation containing the mapping of CLR enum members to EDM enum members.
+ /// The deserialized flags enum value.
+ private static object ReadFlagsEnumValue(ODataEnumValue enumValue, IEdmEnumType enumType, Type clrType, ClrEnumMemberAnnotation memberMapAnnotation)
+ {
+ long result = 0;
+ clrType = TypeHelper.GetUnderlyingTypeOrSelf(clrType);
+
+ ReadOnlySpan source = enumValue.Value.AsSpan().Trim();
+ int start = 0;
+ while (start < source.Length)
+ {
+ // Find the end of the current value.
+ int end = start;
+ while (end < source.Length && source[end] != ',')
+ {
+ end++;
+ }
+
+ // Extract the current value.
+ ReadOnlySpan currentValue = source[start..end].Trim();
+
+ bool parsed = Enum.TryParse(clrType, currentValue, true, out object enumMemberParsed);
+ if (parsed)
+ {
+ result |= Convert.ToInt64((Enum)enumMemberParsed);
+ }
+ else
+ {
+ // If the value is not a valid enum member, try to match it with the EDM enum member name.
+ // This is needed for model alias case.
+ // For example,
+ // - if the enum member is defined as "Friday" and the value is "fri", we need to match them.
+ // - if the enum member is defined as "FullTime" and the value is "Full Time", we need to match them.
+ // - if the enum member is defined as "PartTime" and the value is "part time", we need to match them.
+ foreach (IEdmEnumMember enumMember in enumType.Members)
+ {
+ // Check if the current value matches the enum member name.
+ parsed = currentValue.Equals(enumMember.Name.AsSpan(), StringComparison.InvariantCultureIgnoreCase);
+ if (parsed)
+ {
+ Enum clrEnumMember = memberMapAnnotation.GetClrEnumMember(enumMember);
+ if(clrEnumMember != null)
+ {
+ result |= Convert.ToInt64(clrEnumMember);
+ break;
+ }
+
+ // If the enum member is not found, the value is not valid.
+ parsed = false;
+ }
+ }
+ }
+
+ // If still not valid, return null.
+ if (!parsed)
+ {
+ return null;
+ }
+
+ // Move to the next value.
+ start = end + 1;
+ }
+
+ return result == 0 ? null : Enum.ToObject(clrType, result);
+ }
}
diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataEnumSerializer.cs b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataEnumSerializer.cs
index 77e949498..c90a5d92f 100644
--- a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataEnumSerializer.cs
+++ b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataEnumSerializer.cs
@@ -6,6 +6,7 @@
//------------------------------------------------------------------------------
using System;
+using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using Microsoft.AspNetCore.OData.Common;
@@ -13,6 +14,7 @@
using Microsoft.AspNetCore.OData.Formatter.Value;
using Microsoft.OData;
using Microsoft.OData.Edm;
+using Microsoft.OData.ModelBuilder;
namespace Microsoft.AspNetCore.OData.Formatter.Serialization;
@@ -102,11 +104,18 @@ public virtual ODataEnumValue CreateODataEnumValue(object graph, IEdmEnumTypeRef
var memberMapAnnotation = writeContext?.Model.GetClrEnumMemberAnnotation(enumType.EnumDefinition());
if (memberMapAnnotation != null)
{
- var edmEnumMember = memberMapAnnotation.GetEdmEnumMember((Enum)graph);
+ Enum graphEnum = (Enum)graph;
+
+ var edmEnumMember = memberMapAnnotation.GetEdmEnumMember(graphEnum);
if (edmEnumMember != null)
{
value = edmEnumMember.Name;
}
+ // If the enum is a flags enum, we need to handle the case where multiple flags are set
+ else if (enumType.EnumDefinition().IsFlags)
+ {
+ value = GetFlagsEnumValue(graphEnum, memberMapAnnotation);
+ }
}
ODataEnumValue enumValue = new ODataEnumValue(value, enumType.FullName());
@@ -171,4 +180,37 @@ private static bool ShouldSuppressTypeNameSerialization(ODataMetadataLevel metad
return false;
}
}
+
+ ///
+ /// Gets the combined names of the flags set in a Flags enum value.
+ ///
+ /// The enum value.
+ /// The annotation containing the mapping of CLR enum members to EDM enum members.
+ /// A comma-separated string of the names of the flags that are set.
+ private static string GetFlagsEnumValue(Enum graphEnum, ClrEnumMemberAnnotation memberMapAnnotation)
+ {
+ List flagsList = new List();
+
+ // Convert the enum value to a long for bitwise operations
+ long graphValue = Convert.ToInt64(graphEnum);
+
+ // Iterate through all enum values
+ foreach (Enum flag in Enum.GetValues(graphEnum.GetType()))
+ {
+ // Convert the current flag to a long
+ long flagValue = Convert.ToInt64(flag);
+
+ // Using bitwise operations to check if a flag is set, which is more efficient than Enum.HasFlag
+ if ((graphValue & flagValue) != 0 && flagValue != 0)
+ {
+ IEdmEnumMember flagMember = memberMapAnnotation.GetEdmEnumMember(flag);
+ if (flagMember != null)
+ {
+ flagsList.Add(flagMember.Name);
+ }
+ }
+ }
+
+ return string.Join(", ", flagsList);
+ }
}
diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
index 2ec713bde..dd2c60d0e 100644
--- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
+++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
@@ -3522,6 +3522,16 @@
+
+
+ Reads the value of a flags enum.
+
+ The OData enum value.
+ The EDM enum type.
+ The EDM enum CLR type.
+ The annotation containing the mapping of CLR enum members to EDM enum members.
+ The deserialized flags enum value.
+
Represents an that can read OData primitive types.
@@ -4651,6 +4661,14 @@
The serializer write context.
The created .
+
+
+ Gets the combined names of the flags set in a Flags enum value.
+
+ The enum value.
+ The annotation containing the mapping of CLR enum members to EDM enum members.
+ A comma-separated string of the names of the flags that are set.
+
Represents an to serialize s.
diff --git a/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsController.cs b/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsController.cs
index abc4f6247..6e2fbc43f 100644
--- a/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsController.cs
+++ b/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsController.cs
@@ -12,7 +12,6 @@
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Formatter;
using Microsoft.AspNetCore.OData.Query;
-using Microsoft.AspNetCore.OData.Routing.Attributes;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using Xunit;
@@ -46,6 +45,7 @@ private void InitEmployees()
SkillSet=new List { Skill.CSharp, Skill.Sql },
Gender=Gender.Female,
AccessLevel=AccessLevel.Execute,
+ EmployeeType = EmployeeType.FullTime | EmployeeType.PartTime,
FavoriteSports=new FavoriteSports()
{
LikeMost=Sport.Pingpong,
@@ -58,6 +58,7 @@ private void InitEmployees()
SkillSet=new List(),
Gender=Gender.Female,
AccessLevel=AccessLevel.Read,
+ EmployeeType = EmployeeType.Contract,
FavoriteSports=new FavoriteSports()
{
LikeMost=Sport.Pingpong,
@@ -70,6 +71,7 @@ private void InitEmployees()
SkillSet=new List { Skill.Web, Skill.Sql },
Gender=Gender.Female,
AccessLevel=AccessLevel.Read|AccessLevel.Write,
+ EmployeeType = EmployeeType.Intern | EmployeeType.FullTime | EmployeeType.PartTime,
FavoriteSports=new FavoriteSports()
{
LikeMost=Sport.Pingpong|Sport.Basketball,
@@ -121,6 +123,13 @@ public IActionResult GetFavoriteSportsFromEmployee(int key)
return Ok(employee.FavoriteSports);
}
+ [EnableQuery]
+ public IActionResult GetEmployeeTypeFromEmployee(int key)
+ {
+ var employee = Employees.SingleOrDefault(e => e.ID == key);
+ return Ok(employee.EmployeeType);
+ }
+
[HttpGet("Employees({key})/FavoriteSports/LikeMost")]
public IActionResult GetFavoriteSportLikeMost(int key)
{
diff --git a/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsDataModel.cs b/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsDataModel.cs
index 795999463..08eea53c5 100644
--- a/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsDataModel.cs
+++ b/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsDataModel.cs
@@ -23,6 +23,8 @@ public class Employee
public AccessLevel AccessLevel { get; set; }
+ public EmployeeType EmployeeType { get; set; }
+
public FavoriteSports FavoriteSports { get; set; }
}
@@ -36,6 +38,23 @@ public enum AccessLevel
Execute = 4
}
+[Flags]
+[DataContract(Name = "employeeType")]
+public enum EmployeeType
+{
+ [EnumMember(Value = "full time")]
+ FullTime = 1,
+
+ [EnumMember(Value = "Part Time")]
+ PartTime = 2,
+
+ [EnumMember(Value = "contract")]
+ Contract = 4,
+
+ [EnumMember(Value = "intern")]
+ Intern = 8
+}
+
public enum Gender
{
Male = 1,
diff --git a/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsEdmModel.cs b/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsEdmModel.cs
index 3b9fe2b24..7486ce5c2 100644
--- a/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsEdmModel.cs
+++ b/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsEdmModel.cs
@@ -21,6 +21,7 @@ public static IEdmModel GetExplicitModel()
employee.CollectionProperty(c => c.SkillSet);
employee.EnumProperty(c => c.Gender);
employee.EnumProperty(c => c.AccessLevel);
+ employee.EnumProperty(c => c.EmployeeType);
employee.ComplexProperty(c => c.FavoriteSports);
var skill = builder.EnumType();
@@ -37,6 +38,12 @@ public static IEdmModel GetExplicitModel()
accessLevel.Member(AccessLevel.Read);
accessLevel.Member(AccessLevel.Write);
+ var employeeType = builder.EnumType();
+ employeeType.Member(EmployeeType.FullTime);
+ employeeType.Member(EmployeeType.PartTime);
+ employeeType.Member(EmployeeType.Contract);
+ employeeType.Member(EmployeeType.Intern);
+
var favoriteSports = builder.ComplexType();
favoriteSports.EnumProperty(f => f.LikeMost);
favoriteSports.CollectionProperty(f => f.Like);
diff --git a/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsTest.cs b/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsTest.cs
index 9fa8baf87..82e499a1c 100644
--- a/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsTest.cs
+++ b/test/Microsoft.AspNetCore.OData.E2E.Tests/Enums/EnumsTest.cs
@@ -12,10 +12,10 @@
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.OData.TestCommon;
using Microsoft.AspNetCore.OData.E2E.Tests.Commons;
using Microsoft.AspNetCore.OData.E2E.Tests.Extensions;
using Microsoft.AspNetCore.OData.Routing.Controllers;
+using Microsoft.AspNetCore.OData.TestCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData;
using Microsoft.OData.Edm;
@@ -35,7 +35,7 @@ protected static void UpdateConfigureServices(IServiceCollection services)
IEdmModel model2 = EnumsEdmModel.GetExplicitModel();
services.AddControllers().AddOData(opt => opt.Count().Filter().Expand().Select().OrderBy().SetMaxTop(5)
- .AddRouteComponents("convention", model1)
+ .AddRouteComponents("convention", model1)
.AddRouteComponents("explicit", model2));
}
@@ -82,7 +82,7 @@ public async Task ModelBuilderTest(string modelMode)
var employee = edmModel.SchemaElements.SingleOrDefault(e => e.Name == "Employee") as IEdmEntityType;
Assert.Single(employee.Key());
Assert.Equal("ID", employee.Key().First().Name);
- Assert.Equal(6, employee.Properties().Count());
+ Assert.Equal(7, employee.Properties().Count());
//Entity Enum Collection Property
var skillSet = employee.Properties().SingleOrDefault(p => p.Name == "SkillSet");
@@ -99,6 +99,11 @@ public async Task ModelBuilderTest(string modelMode)
Assert.Equal(3, edmEnumType.Members.Count());
Assert.True(edmEnumType.IsFlags);
+ var employeeType = employee.Properties().SingleOrDefault(p => p.Name == "EmployeeType") as IEdmStructuralProperty;
+ edmEnumType = employeeType.Type.Definition as IEdmEnumType;
+ Assert.Equal(4, edmEnumType.Members.Count());
+ Assert.True(edmEnumType.IsFlags);
+
//Action AddSkill
var iEdmOperation = edmModel.FindOperations(typeof(Employee).Namespace + ".AddSkill").FirstOrDefault();
var iEdmOperationParameter = iEdmOperation.Parameters.SingleOrDefault(p => p.Name == "skill");
@@ -257,6 +262,69 @@ public async Task QueryEnumPropertyInEntityType(string format)
var context = json["@odata.context"].ToString();
Assert.True(context.IndexOf("/$metadata#Collection(Microsoft.AspNetCore.OData.E2E.Tests.Enums.Skill)") >= 0);
}
+
+ requestUri = "/convention/Employees(1)/EmployeeType?$format=" + format;
+ response = await client.GetAsync(requestUri);
+ json = await response.Content.ReadAsObject();
+ var employeeTypeValue = json.GetValue("value").ToString();
+ Assert.Equal("full time, Part Time", employeeTypeValue);
+ if (format != "application/json;odata.metadata=none")
+ {
+ var context = json.GetValue("@odata.context").ToString();
+ Assert.True(context.IndexOf("/$metadata#Employees(1)/EmployeeType") > 0);
+ }
+ }
+
+ public static TheoryDataSet QueryEnumPropertyWithFlagsInEntityTypeData
+ {
+ get
+ {
+ return new TheoryDataSet
+ {
+ { "application/json;odata.metadata=full", 1, "Execute", "full time, Part Time" },
+ { "application/json;odata.metadata=minimal", 1, "Execute", "full time, Part Time" },
+ { "application/json;odata.metadata=none", 1, "Execute", "full time, Part Time" },
+
+ { "application/json;odata.metadata=full", 2, "Read", "contract" },
+ { "application/json;odata.metadata=minimal", 2, "Read", "contract" },
+ { "application/json;odata.metadata=none", 2, "Read", "contract" },
+
+ { "application/json;odata.metadata=full", 3, "Read, Write", "full time, Part Time, intern" },
+ { "application/json;odata.metadata=minimal", 3, "Read, Write", "full time, Part Time, intern" },
+ { "application/json;odata.metadata=none", 3, "Read, Write", "full time, Part Time, intern" },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(QueryEnumPropertyWithFlagsInEntityTypeData))]
+ public async Task QueryEnumPropertyWithFlagsInEntityType(string format, int id, string expectedAccessLevelValue,string expectedEmployeeTypeValue)
+ {
+ // Arrange
+ await ResetDatasource();
+ HttpClient client = CreateClient();
+
+ string requestUri = $"/convention/Employees({id})?$format={format}";
+
+ // Act
+ var response = await client.GetAsync(requestUri);
+
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsObject();
+
+ var accessLevel = json.GetValue("AccessLevel").ToString();
+ var employeeType = json.GetValue("EmployeeType").ToString();
+
+ // Assert
+ Assert.Equal(expectedAccessLevelValue, accessLevel);
+ Assert.Equal(expectedEmployeeTypeValue, employeeType);
+
+ if (format != "application/json;odata.metadata=none")
+ {
+ var context = json.GetValue("@odata.context").ToString();
+ Assert.True(context.IndexOf("/$metadata#Employees/$entity") > 0);
+ }
}
[Theory]
@@ -417,48 +485,104 @@ public async Task EnumInOrderBy(string format)
Assert.Equal(3, secondEmployee["ID"]);
}
-#region Update
-
- //[Fact]
- //public async Task AddEntity()
- //{
- // await ResetDatasource();
- // string requestUri = "/convention/Employees?$format=application/json;odata.metadata=none";
-
- // using (HttpResponseMessage response = await this.Client.GetAsync(requestUri))
- // {
- // response.EnsureSuccessStatusCode();
-
- // var json = await response.Content.ReadAsObject();
- // var result = json.GetValue("value") as JArray;
- // Assert.Equal(3, result.Count);
- // }
-
- // var postUri = "/convention/Employees";
-
- // var postContent = JObject.Parse(@"{""ID"":1,
- // ""Name"":""Name2"",
- // ""SkillSet"":[""Sql""],
- // ""Gender"":""Female"",
- // ""AccessLevel"":""Read,Write"",
- // ""FavoriteSports"":{
- // ""LikeMost"":""Pingpong"",
- // ""Like"":[""Pingpong"",""Basketball""]
- // }}");
- // using (HttpResponseMessage response = await this.Client.PostAsJsonAsync(postUri, postContent))
- // {
- // Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- // }
-
- // using (HttpResponseMessage response = await this.Client.GetAsync(requestUri))
- // {
- // response.EnsureSuccessStatusCode();
-
- // var json = await response.Content.ReadAsObject();
- // var result = json.GetValue("value") as JArray;
- // Assert.Equal(4, result.Count);
- // }
- //}
+ #region Add
+
+ [Fact]
+ public async Task AddEntity()
+ {
+ await ResetDatasource();
+ HttpClient client = CreateClient();
+ string requestUri = "/convention/Employees?$format=application/json;odata.metadata=none";
+
+ using (HttpResponseMessage response = await client.GetAsync(requestUri))
+ {
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsObject();
+ var result = json.GetValue("value") as JArray;
+ Assert.Equal(3, result.Count);
+ }
+
+ var postUri = "/convention/Employees";
+
+ var postContent = JObject.Parse(@"{""ID"":6,
+ ""Name"":""New Name 20"",
+ ""SkillSet"":[""Sql""],
+ ""Gender"":""Female"",
+ ""AccessLevel"":""read,write"",
+ ""EmployeeType"":""intern, contract"",
+ ""FavoriteSports"":{
+ ""LikeMost"":""Pingpong"",
+ ""Like"":[""Pingpong"",""Basketball""]
+ }}");
+ using (HttpResponseMessage response = await client.PostAsJsonAsync(postUri, postContent))
+ {
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ }
+
+ using (HttpResponseMessage response = await client.GetAsync(requestUri))
+ {
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsObject();
+ var result = json.GetValue("value") as JArray;
+ Assert.Equal(4, result.Count);
+ }
+ }
+
+ [Theory]
+ [InlineData("read", "intern")]
+ [InlineData("Read", "Intern")]
+ [InlineData("read, Execute", "fullTime, Contract")]
+ [InlineData("Read, Execute", "Intern, FullTime")]
+ [InlineData("read,write", "intern, contract")]
+ public async Task AddEntityWithLowerAndUpperCamelCaseFlagEnums(string accessLevelValue, string employeeTypeValue)
+ {
+ await ResetDatasource();
+ HttpClient client = CreateClient();
+ string requestUri = "/convention/Employees?$format=application/json;odata.metadata=none";
+
+ using (HttpResponseMessage response = await client.GetAsync(requestUri))
+ {
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsObject();
+ var result = json.GetValue("value") as JArray;
+ Assert.Equal(3, result.Count);
+ }
+
+ var postUri = "/convention/Employees";
+
+ var postContent = JObject.Parse($@"{{
+ ""ID"":6,
+ ""Name"":""New Name 23"",
+ ""SkillSet"":[""Sql""],
+ ""Gender"":""Female"",
+ ""AccessLevel"":""{accessLevelValue}"",
+ ""EmployeeType"":""{employeeTypeValue}"",
+ ""FavoriteSports"":{{
+ ""LikeMost"":""Pingpong"",
+ ""Like"":[""Pingpong"",""Basketball""]
+ }}}}");
+
+ using (HttpResponseMessage response = await client.PostAsJsonAsync(postUri, postContent))
+ {
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ }
+
+ using (HttpResponseMessage response = await client.GetAsync(requestUri))
+ {
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsObject();
+ var result = json.GetValue("value") as JArray;
+ Assert.Equal(4, result.Count);
+ }
+ }
+
+ #endregion
+
+ #region Update
[Fact]
public async Task PostToEnumCollection()
@@ -536,6 +660,7 @@ public async Task UpdateEntity()
""SkillSet"":[""Sql""],
""Gender"":""Female"",
""AccessLevel"":""Execute,Write"",
+ ""EmployeeType"":""intern, contract"",
""FavoriteSports"":{
""LikeMost"":""Basketball"",
""Like"":[""Pingpong"",""Basketball""]
@@ -554,6 +679,9 @@ public async Task UpdateEntity()
var accessLevel = json.GetValue("AccessLevel");
Assert.Equal("Write, Execute", accessLevel);
+ var employeeType = json.GetValue("EmployeeType");
+ Assert.Equal("contract, intern", employeeType);
+
var skillSet = json.GetValue("SkillSet").ToString();
Assert.Equal(@"[""Sql""]", skillSet.Replace("\r\n", "").Replace(" ", ""));
@@ -579,6 +707,7 @@ public async Task UpsertEntity(string method)
""SkillSet"":[""Sql""],
""Gender"":""Female"",
""AccessLevel"":""Execute,Write"",
+ ""EmployeeType"":""parttime, Fulltime"",
""FavoriteSports"":{
""LikeMost"":""Basketball"",
""Like"":[""Pingpong"",""Basketball""]
@@ -598,9 +727,52 @@ public async Task UpsertEntity(string method)
}
}
-#endregion
+ public static TheoryDataSet UpsertEntityWithLowerAndUpperCamelCaseFlagEnumsData
+ {
+ get
+ {
+ return new TheoryDataSet
+ {
+ { "PUT", @"{""ID"":20,""AccessLevel"":""Execute,Write"",""EmployeeType"":""intern, FullTime""}" },
+ { "PATCH", @"{""ID"":20,""AccessLevel"":""Execute,Write"",""EmployeeType"":""intern, FullTime""}" },
+ { "PUT", @"{""ID"":20,""AccessLevel"":""Execute, write"",""EmployeeType"":""Intern, contract""}" },
+ { "PATCH", @"{""ID"":20,""AccessLevel"":""Execute, write"",""EmployeeType"":""Intern, contract""}" },
+ { "PUT", @"{""ID"":20,""AccessLevel"":""execute"",""EmployeeType"":""contract""}" },
+ { "PATCH", @"{""ID"":20,""AccessLevel"":""execute"",""EmployeeType"":""contract""}" },
+ { "PUT", @"{""ID"":20,""AccessLevel"":""Execute"",""EmployeeType"":""FullTime""}" },
+ { "PATCH", @"{""ID"":20,""AccessLevel"":""Execute"",""EmployeeType"":""FullTime""}" },
+ };
+ }
+ }
+
+
+ [Theory]
+ [MemberData(nameof(UpsertEntityWithLowerAndUpperCamelCaseFlagEnumsData))]
+ public async Task UpsertEntityWithLowerAndUpperCamelCaseFlagEnums(string method, string requestContent)
+ {
+ await ResetDatasource();
+ HttpClient client = CreateClient();
+
+ var requestUri = "/convention/Employees(20)";
+
+ HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(method), requestUri);
+ request.Content = new StringContent(requestContent);
+ request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
+ request.Content.Headers.ContentLength = requestContent.Length;
+ request.Headers.Add("Prefer", "return=minimal");
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ Assert.True(HttpStatusCode.NoContent == response.StatusCode,
+ string.Format("Response code is not right, expected: {0}, actual: {1}", HttpStatusCode.NoContent, response.StatusCode));
+ Assert.True(response.Headers.Contains("OData-EntityId"), "The response should contain Header 'OData-EntityId'");
+ Assert.True(response.Headers.Contains("Location"), "The response should contain Header 'Location'");
+ Assert.True(response.Headers.Contains("OData-Version"), "The response should contain Header 'OData-Version'");
+ }
+ }
+
+ #endregion
-#region Delete
+ #region Delete
[Fact]
public async Task DeleteEntity()
diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataEnumDeserializerTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataEnumDeserializerTests.cs
index 4f665f7b4..23067027a 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataEnumDeserializerTests.cs
+++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataEnumDeserializerTests.cs
@@ -5,6 +5,7 @@
//
//------------------------------------------------------------------------------
+using System;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
@@ -114,12 +115,14 @@ public async Task ReadAsync_Works_ForUnType()
Assert.Equal("Blue", color.Value);
}
- [Fact]
- public async Task ReadAsync_Works_ForModelAlias()
+ [Theory]
+ [InlineData("{\"@odata.type\":\"#NS.level\",\"value\":\"veryhigh\"}", Level.High)]
+ [InlineData("{\"@odata.type\":\"#NS.level\",\"value\":\"High\"}", Level.High)]
+ [InlineData("{\"@odata.type\":\"#NS.level\",\"value\":\"low\"}", Level.Low)]
+ [InlineData("{\"@odata.type\":\"#NS.level\",\"value\":\"Low\"}", Level.Low)]
+ public async Task ReadAsync_Works_ForModelAlias(string content, Level expectedLevel)
{
// Arrange
- string content = "{\"@odata.type\":\"#NS.level\",\"value\":\"veryhigh\"}";
-
var builder = new ODataConventionModelBuilder();
builder.EnumType().Namespace = "NS";
IEdmModel model = builder.GetEdmModel();
@@ -139,7 +142,48 @@ public async Task ReadAsync_Works_ForModelAlias()
// Assert
Level level = Assert.IsType(value);
- Assert.Equal(Level.High, level);
+ Assert.Equal(expectedLevel, level);
+ }
+
+ [Theory]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"mon\"}", Day.Monday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"monday\"}", Day.Monday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"Monday\"}", Day.Monday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\" monday, friday \"}", Day.Monday | Day.Friday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"mon, fri\"}", Day.Monday | Day.Friday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"monday, tuesday \"}", Day.Monday | Day.Tuesday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\" Monday, tuesday \"}", Day.Monday | Day.Tuesday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"Monday, Tuesday\"}", Day.Monday | Day.Tuesday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"monday, tuesday, thursday\"}", Day.Monday | Day.Tuesday | Day.Thursday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"Monday, Tuesday, thursday\"}", Day.Monday | Day.Tuesday | Day.Thursday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"Monday, tuesday, Thursday\"}", Day.Monday | Day.Tuesday | Day.Thursday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"Monday, Tuesday, Thursday\"}", Day.Monday | Day.Tuesday | Day.Thursday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"Monday, Friday\"}", Day.Monday | Day.Friday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"Monday, tuesday, fri\"}", Day.Monday | Day.Tuesday | Day.Friday)]
+ [InlineData("{\"@odata.type\":\"#NS.day\",\"value\":\"Monday, fri, Wednesday\"}", Day.Monday | Day.Friday | Day.Wednesday)]
+ public async Task ReadAsync_Works_ForModelAliasWithFlags(string content, Day expectedDay)
+ {
+ // Arrange
+ var builder = new ODataConventionModelBuilder();
+ builder.EnumType().Namespace = "NS";
+ IEdmModel model = builder.GetEdmModel();
+
+ ODataEnumDeserializer deserializer = new ODataEnumDeserializer();
+ ODataDeserializerContext readContext = new ODataDeserializerContext
+ {
+ Model = model,
+ ResourceType = typeof(Day)
+ };
+
+ HttpRequest request = RequestFactory.Create("Post", "http://localhost/", _edmModel);
+
+ // Act
+ object value = await deserializer.ReadAsync(ODataTestUtil.GetODataMessageReader(request.GetODataMessage(content), model),
+ typeof(Day), readContext);
+
+ // Assert
+ Day day = Assert.IsType(value);
+ Assert.Equal(expectedDay, day);
}
[Fact]
@@ -176,4 +220,24 @@ public enum Level
[EnumMember(Value = "veryhigh")]
High
}
+
+ [Flags]
+ [DataContract(Name = "day")]
+ public enum Day
+ {
+ [EnumMember(Value = "mon")]
+ Monday = 1,
+
+ [EnumMember(Value = "tuesday")]
+ Tuesday = 2,
+
+ [EnumMember(Value = "wednesday")]
+ Wednesday = 4,
+
+ [EnumMember(Value = "thursday")]
+ Thursday = 8,
+
+ [EnumMember(Value = "fri")]
+ Friday = 16
+ }
}