From 38338c0c38e219798446d62cb72277bca2675bb7 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Thu, 24 Feb 2022 15:50:56 -0800 Subject: [PATCH] Add an alternate key sample and its related docs --- AspNetCoreOData.sln | 7 + .../Controllers/CustomersController.cs | 71 +++++++ .../Controllers/OrdersController.cs | 70 +++++++ .../Controllers/PeopleController.cs | 56 +++++ .../Controllers/WeatherForecastController.cs | 33 +++ .../Models/AlternateKeyRepositoryInMemory.cs | 65 ++++++ .../Models/Customer.cs | 21 ++ .../Models/EdmModelBuilder.cs | 129 ++++++++++++ .../Models/IAlternateKeyRepository.cs | 18 ++ .../ODataAlternateKeySample/Models/Order.cs | 25 +++ .../ODataAlternateKeySample/Models/Person.cs | 23 ++ .../ODataAlternateKeySample.csproj | 12 ++ sample/ODataAlternateKeySample/Program.cs | 23 ++ .../Properties/launchSettings.json | 31 +++ .../WeatherForecast.cs | 13 ++ .../appsettings.Development.json | 8 + .../ODataAlternateKeySample/appsettings.json | 9 + sample/ODataAlternateKeySample/readme.md | 198 ++++++++++++++++++ 18 files changed, 812 insertions(+) create mode 100644 sample/ODataAlternateKeySample/Controllers/CustomersController.cs create mode 100644 sample/ODataAlternateKeySample/Controllers/OrdersController.cs create mode 100644 sample/ODataAlternateKeySample/Controllers/PeopleController.cs create mode 100644 sample/ODataAlternateKeySample/Controllers/WeatherForecastController.cs create mode 100644 sample/ODataAlternateKeySample/Models/AlternateKeyRepositoryInMemory.cs create mode 100644 sample/ODataAlternateKeySample/Models/Customer.cs create mode 100644 sample/ODataAlternateKeySample/Models/EdmModelBuilder.cs create mode 100644 sample/ODataAlternateKeySample/Models/IAlternateKeyRepository.cs create mode 100644 sample/ODataAlternateKeySample/Models/Order.cs create mode 100644 sample/ODataAlternateKeySample/Models/Person.cs create mode 100644 sample/ODataAlternateKeySample/ODataAlternateKeySample.csproj create mode 100644 sample/ODataAlternateKeySample/Program.cs create mode 100644 sample/ODataAlternateKeySample/Properties/launchSettings.json create mode 100644 sample/ODataAlternateKeySample/WeatherForecast.cs create mode 100644 sample/ODataAlternateKeySample/appsettings.Development.json create mode 100644 sample/ODataAlternateKeySample/appsettings.json create mode 100644 sample/ODataAlternateKeySample/readme.md diff --git a/AspNetCoreOData.sln b/AspNetCoreOData.sln index 3c956f023..64dd16091 100644 --- a/AspNetCoreOData.sln +++ b/AspNetCoreOData.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataDynamicModel", "sample EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataSampleCommon", "sample\ODataSampleCommon\ODataSampleCommon.csproj", "{647EFCFA-55A7-4F0A-AD40-4B6EB1BFCFFA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataAlternateKeySample", "sample\ODataAlternateKeySample\ODataAlternateKeySample.csproj", "{7B153669-A42F-4511-8BDB-587B3B27B2F3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +65,10 @@ Global {647EFCFA-55A7-4F0A-AD40-4B6EB1BFCFFA}.Debug|Any CPU.Build.0 = Debug|Any CPU {647EFCFA-55A7-4F0A-AD40-4B6EB1BFCFFA}.Release|Any CPU.ActiveCfg = Release|Any CPU {647EFCFA-55A7-4F0A-AD40-4B6EB1BFCFFA}.Release|Any CPU.Build.0 = Release|Any CPU + {7B153669-A42F-4511-8BDB-587B3B27B2F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B153669-A42F-4511-8BDB-587B3B27B2F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B153669-A42F-4511-8BDB-587B3B27B2F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B153669-A42F-4511-8BDB-587B3B27B2F3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -76,6 +82,7 @@ Global {BDC5474B-9511-4CDF-83FE-376C7130F7F0} = {B1F86961-6958-4617-ACA4-C231F95AE099} {CE04E38B-547F-46C0-ABE4-F981E3A1874F} = {B1F86961-6958-4617-ACA4-C231F95AE099} {647EFCFA-55A7-4F0A-AD40-4B6EB1BFCFFA} = {B1F86961-6958-4617-ACA4-C231F95AE099} + {7B153669-A42F-4511-8BDB-587B3B27B2F3} = {B1F86961-6958-4617-ACA4-C231F95AE099} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {540C9752-AAC0-49EA-BA60-78490C90FF86} diff --git a/sample/ODataAlternateKeySample/Controllers/CustomersController.cs b/sample/ODataAlternateKeySample/Controllers/CustomersController.cs new file mode 100644 index 000000000..e98d5ef22 --- /dev/null +++ b/sample/ODataAlternateKeySample/Controllers/CustomersController.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OData.Deltas; +using Microsoft.AspNetCore.OData.Query; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using ODataAlternateKeySample.Models; + +namespace ODataAlternateKeySample.Controllers +{ + public class CustomersController : ODataController + { + private readonly IAlternateKeyRepository _repository; + + public CustomersController(IAlternateKeyRepository repository) + { + _repository = repository; + } + + [HttpGet] + [EnableQuery] + public IActionResult Get() + { + return Ok(_repository.GetCustomers()); + } + + [HttpGet] + [EnableQuery] + public IActionResult Get(int key) + { + var c = _repository.GetCustomers().FirstOrDefault(c => c.Id == key); + if (c == null) + { + return NotFound(); + } + + return Ok(c); + } + + // Alternate key: SSN + [HttpGet("odata/Customers(SSN={ssn})")] + public IActionResult GetCustomerBySSN(string ssn) + { + var c = _repository.GetCustomers().FirstOrDefault(c => c.SSN == ssn); + if (c == null) + { + return NotFound(); + } + + return Ok(c); + } + + [HttpPatch("odata/Customers(SSN={ssnKey})")] + public IActionResult PatchCustomerBySSN(string ssnKey, Delta delta) + { + var originalCustomer = _repository.GetCustomers().FirstOrDefault(c => c.SSN == ssnKey); + if (originalCustomer == null) + { + return NotFound(); + } + + delta.Patch(originalCustomer); + return Updated(originalCustomer); + } + } +} diff --git a/sample/ODataAlternateKeySample/Controllers/OrdersController.cs b/sample/ODataAlternateKeySample/Controllers/OrdersController.cs new file mode 100644 index 000000000..323f62b1a --- /dev/null +++ b/sample/ODataAlternateKeySample/Controllers/OrdersController.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OData.Query; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using ODataAlternateKeySample.Models; + +namespace ODataAlternateKeySample.Controllers +{ + public class OrdersController : ODataController + { + private readonly IAlternateKeyRepository _repository; + + public OrdersController(IAlternateKeyRepository repository) + { + _repository = repository; + } + + [HttpGet] + [EnableQuery] + public IActionResult Get() + { + return Ok(_repository.GetOrders()); + } + + [HttpGet] + [EnableQuery] + public IActionResult Get(int key) + { + var c = _repository.GetOrders().FirstOrDefault(c => c.Id == key); + if (c == null) + { + return NotFound(); + } + + return Ok(c); + } + + // alternate key: Name + [HttpGet("odata/Orders(Name={orderName})")] + public IActionResult GetOrderByName(string orderName) + { + var c = _repository.GetOrders().FirstOrDefault(c => c.Name == orderName); + if (c == null) + { + return NotFound(); + } + + return Ok(c); + } + + // alternate key: Token + [HttpGet("odata/Orders(Token={token})")] + public IActionResult GetOrderByToken(Guid token) + { + var c = _repository.GetOrders().FirstOrDefault(c => c.Token == token); + if (c == null) + { + return NotFound(); + } + + return Ok(c); + } + } +} diff --git a/sample/ODataAlternateKeySample/Controllers/PeopleController.cs b/sample/ODataAlternateKeySample/Controllers/PeopleController.cs new file mode 100644 index 000000000..5786872dd --- /dev/null +++ b/sample/ODataAlternateKeySample/Controllers/PeopleController.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OData.Query; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using ODataAlternateKeySample.Models; + +namespace ODataAlternateKeySample.Controllers +{ + public class PeopleController : ODataController + { + private readonly IAlternateKeyRepository _repository; + + public PeopleController(IAlternateKeyRepository repository) + { + _repository = repository; + } + + [HttpGet] + [EnableQuery] + public IActionResult Get() + { + return Ok(_repository.GetPeople()); + } + + [HttpGet] + [EnableQuery] + public IActionResult Get(int key) + { + var c = _repository.GetPeople().FirstOrDefault(c => c.Id == key); + if (c == null) + { + return NotFound(); + } + + return Ok(c); + } + + [HttpGet("odata/People(c_or_r={cr},passport={passport})")] + public IActionResult FindPeopleByCountryAndPassport(string cr, string passport) + { + var c = _repository.GetPeople().FirstOrDefault(c => c.CountryOrRegion == cr && c.Passport == passport); + if (c == null) + { + return NotFound(); + } + + return Ok(c); + } + } +} diff --git a/sample/ODataAlternateKeySample/Controllers/WeatherForecastController.cs b/sample/ODataAlternateKeySample/Controllers/WeatherForecastController.cs new file mode 100644 index 000000000..f56955089 --- /dev/null +++ b/sample/ODataAlternateKeySample/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace ODataAlternateKeySample.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/sample/ODataAlternateKeySample/Models/AlternateKeyRepositoryInMemory.cs b/sample/ODataAlternateKeySample/Models/AlternateKeyRepositoryInMemory.cs new file mode 100644 index 000000000..fe3debd10 --- /dev/null +++ b/sample/ODataAlternateKeySample/Models/AlternateKeyRepositoryInMemory.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +namespace ODataAlternateKeySample.Models +{ + public class AlternateKeyRepositoryInMemory : IAlternateKeyRepository + { + private static IList _customers; + private static IList _orders; + private static IList _people; + + static AlternateKeyRepositoryInMemory() + { + // Customers + var names = new[] { "Tom", "Jerry", "Mike", "Ben", "Sam", "Peter" }; + _customers = Enumerable.Range(1, 5).Select(e => new Customer + { + Id = e, + Name = names[e - 1], + SSN = "SSN-" + e + "-" + (100 + e) + }).ToList(); + + // Orders + Guid[] tokes = + { + new Guid("196B3584-EF3D-41FD-90B4-76D59F9B929C"), + new Guid("6CED5600-28BA-40EE-A2DF-E80AFADBE6C7"), + new Guid("75036B94-C836-4946-8CC8-054CF54060EC"), + new Guid("B3FF5460-6E77-4678-B959-DCC1C4937FA7"), + new Guid("ED773C85-4E3C-4FC4-A3E9-9F1DA0A626DA"), + new Guid("E9CC3D9F-BC80-4D43-8C3E-ED38E8C9A8B6") + }; + + _orders = Enumerable.Range(1, 6).Select(e => new Order + { + Id = e, + Name = string.Format("Order-{0}", e), + Token = tokes[e - 1], + Amount = 10 * (e + 1) - e, + Price = 8 * e + }).ToList(); + + // People + var cs = new[] { "EN", "CN", "USA", "RU", "JP", "KO" }; + var ps = new[] { "1001", "2010", "9999", "3199992", "00001", "8110" }; + _people = Enumerable.Range(1, 6).Select(e => new Person + { + Id = e, + Name = names[e - 1], + CountryOrRegion = cs[e - 1], + Passport = ps[e - 1] + }).ToList(); + } + + public IEnumerable GetCustomers() => _customers; + + public IEnumerable GetOrders() => _orders; + + public IEnumerable GetPeople() => _people; + } +} diff --git a/sample/ODataAlternateKeySample/Models/Customer.cs b/sample/ODataAlternateKeySample/Models/Customer.cs new file mode 100644 index 000000000..53de1b1ea --- /dev/null +++ b/sample/ODataAlternateKeySample/Models/Customer.cs @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +namespace ODataAlternateKeySample.Models +{ + /// + /// Entity type with one alternate key + /// + public class Customer + { + public int Id { get; set; } + + public string Name { get; set; } + + public string SSN { get; set; } + } +} diff --git a/sample/ODataAlternateKeySample/Models/EdmModelBuilder.cs b/sample/ODataAlternateKeySample/Models/EdmModelBuilder.cs new file mode 100644 index 000000000..18de67924 --- /dev/null +++ b/sample/ODataAlternateKeySample/Models/EdmModelBuilder.cs @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; + +namespace ODataAlternateKeySample.Models +{ + public class EdmModelBuilder + { + public static IEdmModel GetEdmModel() + { + var builder = new ODataConventionModelBuilder(); + builder.EntitySet("Customers"); + builder.EntitySet("Orders"); + builder.EntitySet("People"); + + EdmModel model = builder.GetEdmModel() as EdmModel; + + SetCustomerAlternateKey(model); + SetOrderAlternateKey(model); + SetPersonAlternateKey(model); + return model; + } + + private static void SetCustomerAlternateKey(EdmModel model) + { + // Add one alternate key using the extension method. + // It's using 'OData.Community.Keys.V1.AlternateKeys' + var customer = model.SchemaElements.OfType().First(c => c.Name == "Customer"); + var ssn = customer.FindProperty("SSN"); + + model.AddAlternateKeyAnnotation(customer, new Dictionary + { + {"SSN", ssn} + }); + } + + private static void SetOrderAlternateKey(EdmModel model) + { + // Add multiple alternate keys using the Term/annotation methods. + // It's using 'Org.OData.Core.V1.AlternateKeys' + var order = model.SchemaElements.OfType().First(c => c.Name == "Order"); + + var name = order.FindProperty("Name"); + model.AddAlternateKeyAnnotation(order, new Dictionary + { + {"Name", name}, + }); + + var token = order.FindProperty("Token"); + model.AddAlternateKeyAnnotation(order, new Dictionary + { + {"Token", token}, + }); + + // ODL doesn't support Org.OData.Core.V1.AlternateKeys to do the uri parsing + /* + var alternateKeysCollection = new List(); + foreach (string item in new [] { "Name", "Token"}) + { + List propertyRefs = new List(); + + IEdmRecordExpression propertyRef = new EdmRecordExpression( + new EdmPropertyConstructor("Alias", new EdmStringConstant(item)), + new EdmPropertyConstructor("Name", new EdmPropertyPathExpression(item))); + propertyRefs.Add(propertyRef); + + EdmRecordExpression alternateKeyRecord = new EdmRecordExpression( + new EdmPropertyConstructor("Key", new EdmCollectionExpression(propertyRefs))); + + alternateKeysCollection.Add(alternateKeyRecord); + } + + var term = model.FindTerm("Org.OData.Core.V1.AlternateKeys"); + var annotation = new EdmVocabularyAnnotation(order, term, new EdmCollectionExpression(alternateKeysCollection)); + + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + */ + } + + private static void SetPersonAlternateKey(EdmModel model) + { + // Add composed alternate keys using the Term/annotation methods. + // It's using 'Org.OData.Core.V1.AlternateKeys' + var person = model.SchemaElements.OfType().First(c => c.Name == "Person"); + var cr = person.FindProperty("CountryOrRegion"); + var passport = person.FindProperty("Passport"); + + model.AddAlternateKeyAnnotation(person, new Dictionary + { + {"c_or_r", cr}, + {"passport", passport}, + }); + + // ODL doesn't support Org.OData.Core.V1.AlternateKeys to do the uri parsing + /* + List propertyRefs = new List(); + + IEdmRecordExpression propertyRef = new EdmRecordExpression( + new EdmPropertyConstructor("Alias", new EdmStringConstant("CountryOrRegion")), + new EdmPropertyConstructor("Name", new EdmPropertyPathExpression("CountryOrRegion"))); + propertyRefs.Add(propertyRef); + + propertyRef = new EdmRecordExpression( + new EdmPropertyConstructor("Alias", new EdmStringConstant("Passport")), + new EdmPropertyConstructor("Name", new EdmPropertyPathExpression("Passport"))); + propertyRefs.Add(propertyRef); + + EdmRecordExpression alternateKeyRecord = new EdmRecordExpression( + new EdmPropertyConstructor("Key", new EdmCollectionExpression(propertyRefs))); + + var alternateKeysCollection = new List(); + alternateKeysCollection.Add(alternateKeyRecord); + + var term = model.FindTerm("Org.OData.Core.V1.AlternateKeys"); + var annotation = new EdmVocabularyAnnotation(person, term, new EdmCollectionExpression(alternateKeysCollection)); + + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + */ + } + } +} diff --git a/sample/ODataAlternateKeySample/Models/IAlternateKeyRepository.cs b/sample/ODataAlternateKeySample/Models/IAlternateKeyRepository.cs new file mode 100644 index 000000000..b728f7ea6 --- /dev/null +++ b/sample/ODataAlternateKeySample/Models/IAlternateKeyRepository.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +namespace ODataAlternateKeySample.Models +{ + public interface IAlternateKeyRepository + { + IEnumerable GetCustomers(); + + IEnumerable GetOrders(); + + IEnumerable GetPeople(); + } +} diff --git a/sample/ODataAlternateKeySample/Models/Order.cs b/sample/ODataAlternateKeySample/Models/Order.cs new file mode 100644 index 000000000..9103b7f3c --- /dev/null +++ b/sample/ODataAlternateKeySample/Models/Order.cs @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +namespace ODataAlternateKeySample.Models +{ + /// + /// Entity type with multiple alternate keys + /// + public class Order + { + public int Id { get; set; } + + public string Name { get; set; } + + public Guid Token { get; set; } + + public decimal Price { get; set; } + + public int Amount { get; set; } + } +} diff --git a/sample/ODataAlternateKeySample/Models/Person.cs b/sample/ODataAlternateKeySample/Models/Person.cs new file mode 100644 index 000000000..80c12fa68 --- /dev/null +++ b/sample/ODataAlternateKeySample/Models/Person.cs @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +namespace ODataAlternateKeySample.Models +{ + /// + /// Entity type with composed alternate keys + /// + public class Person + { + public int Id { get; set; } + + public string Name { get; set; } + + public string CountryOrRegion { get; set; } + + public string Passport { get; set; } + } +} diff --git a/sample/ODataAlternateKeySample/ODataAlternateKeySample.csproj b/sample/ODataAlternateKeySample/ODataAlternateKeySample.csproj new file mode 100644 index 000000000..aa24c793b --- /dev/null +++ b/sample/ODataAlternateKeySample/ODataAlternateKeySample.csproj @@ -0,0 +1,12 @@ + + + + net6.0 + enable + + + + + + + diff --git a/sample/ODataAlternateKeySample/Program.cs b/sample/ODataAlternateKeySample/Program.cs new file mode 100644 index 000000000..6a8afdd06 --- /dev/null +++ b/sample/ODataAlternateKeySample/Program.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.OData; +using ODataAlternateKeySample.Models; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddTransient(); +builder.Services.AddControllers(). + AddOData(opt => opt.EnableQueryFeatures() + .AddRouteComponents("odata", EdmModelBuilder.GetEdmModel())); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. + +app.UseODataRouteDebug(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/sample/ODataAlternateKeySample/Properties/launchSettings.json b/sample/ODataAlternateKeySample/Properties/launchSettings.json new file mode 100644 index 000000000..ffcad3ad8 --- /dev/null +++ b/sample/ODataAlternateKeySample/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:48226", + "sslPort": 0 + } + }, + "profiles": { + "ODataAlternateKeySample": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "$odata", + "applicationUrl": "http://localhost:5219", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/sample/ODataAlternateKeySample/WeatherForecast.cs b/sample/ODataAlternateKeySample/WeatherForecast.cs new file mode 100644 index 000000000..ef600d859 --- /dev/null +++ b/sample/ODataAlternateKeySample/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace ODataAlternateKeySample +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} \ No newline at end of file diff --git a/sample/ODataAlternateKeySample/appsettings.Development.json b/sample/ODataAlternateKeySample/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/sample/ODataAlternateKeySample/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/sample/ODataAlternateKeySample/appsettings.json b/sample/ODataAlternateKeySample/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/sample/ODataAlternateKeySample/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/sample/ODataAlternateKeySample/readme.md b/sample/ODataAlternateKeySample/readme.md new file mode 100644 index 000000000..e86e4fabe --- /dev/null +++ b/sample/ODataAlternateKeySample/readme.md @@ -0,0 +1,198 @@ +# ODataAlternateKeySample +------------------------- + +This sample illustrates how to use the Alternate key in ASP.NET Core OData 8.x. + +Alternate key is an 'alternate' key compared to the declared key. + +For example, any customer can have a `Id` as his declared key, meanwhile, he can also have `SSN` as his identity. + +The sample implements three alternate key scenarios: + +1. Single alternate key + - Using declared keys : ~/odata/Customers(3) + - Using alternate keys: ~/odata/Customer(SSN='SSN-3-103') + +2. Multiple alternate keys + - Using declared keys : ~/odata/Orders(2) + - Using alternate keys: ~/odata/Orders(Name='Order-2') + - Using alternate keys: ~/odata/Orders(Token=75036B94-C836-4946-8CC8-054CF54060EC) + +3. Composition alternate keys + - Using declared keys : ~/odata/People(2) + - Using alternate keys: ~/odata/People(CountryOrRegion='USA',Passport='9999') + +You may noticed that you should use the `alternateKeyAlias=alternateKeyValue` pattern to invoke the API. + +## Verify the Model + +OData uses the vocabulary annotation to specify the alternate key. + +Send `GET http://localhost:5219/odata/$metadata` + +You can get the following metadata. + +1) Customer type has `OData.Community.Keys.V1.AlternateKeys` annotation with one alternate key +2) Order type has `OData.Community.Keys.V1.AlternateKeys` annotation with two alternate keys +3) Person type has `OData.Community.Keys.V1.AlternateKeys` annotation with one alternate key, in which has two records + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## Query using single alternate key + +Send one of the following requests + +```C# +GET http://localhost:5219/odata/Customers(2) +GET http://localhost:5219/odata/Customers(ssn='SSN-2-102') +``` + +you can get the following result: +```json +{ + "@odata.context": "http://localhost:5219/odata/$metadata#Customers/$entity", + "Id": 2, + "Name": "Jerry", + "SSN": "SSN-2-102" +} +``` + +## Query using multiple alternate keys + +Send one of the following requests + +```C# +GET http://localhost:5219/odata/orders(3) +GET http://localhost:5219/odata/orders(Name='Order-3') +GET http://localhost:5219/odata/orders(Token=75036b94-c836-4946-8cc8-054cf54060ec) +``` + +you can get the following result: +```json +{ + "@odata.context": "http://localhost:5219/odata/$metadata#Orders/$entity", + "Id": 3, + "Name": "Order-3", + "Token": "75036b94-c836-4946-8cc8-054cf54060ec", + "Price": 24, + "Amount": 37 +} +``` + +## Query using composited alternate keys + +Send one of the following requests + +```C# +GET http://localhost:5219/odata/People(3) +GET ttp://localhost:5219/odata/People(c_or_r='USA',passport='9999') +``` + +you can get the following result: +```json +{ + "@odata.context": "http://localhost:5219/odata/$metadata#People/$entity", + "Id": 3, + "Name": "Mike", + "CountryOrRegion": "USA", + "Passport": "9999" +} +```