From dae0b378362e9596699ca8a36c232ffcf81b4d34 Mon Sep 17 00:00:00 2001 From: Wayne Mather Date: Mon, 22 Aug 2022 09:23:55 +0930 Subject: [PATCH 1/4] Allowed user to override conventions for primary key names rather than [Key] annotation or id as the name --- InstantAPIs/InstantAPIsConfig.cs | 22 +++++++++++++++++++++- InstantAPIs/MapApiExtensions.cs | 21 ++++++++++++++++++--- InstantAPIs/WebApplicationExtensions.cs | 2 +- WorkingApi/Program.cs | 1 + 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/InstantAPIs/InstantAPIsConfig.cs b/InstantAPIs/InstantAPIsConfig.cs index f7ce505..735fc63 100644 --- a/InstantAPIs/InstantAPIsConfig.cs +++ b/InstantAPIs/InstantAPIsConfig.cs @@ -5,6 +5,10 @@ internal class InstantAPIsConfig internal HashSet Tables { get; } = new HashSet(); + internal List PrimaryKeyMappingConventions { get; } = new List() { + "{ClassName}Id", "{ClassName}_Id", "Id", "ID" + }; + } @@ -17,7 +21,7 @@ public class InstantAPIsConfigBuilder where D : DbContext private readonly HashSet _IncludedTables = new(); private readonly List _ExcludedTables = new(); private const string DEFAULT_URI = "/api/"; - + public InstantAPIsConfigBuilder(D theContext) { this._TheContext = theContext; @@ -126,6 +130,22 @@ private void BuildTables() #endregion + #region Primary Key Mapping Conventions + + /// + /// Override the convention for determining primary keys of the entities. [Key] data annotation takes priority + /// + /// A list of conventions. You can use the string {ClassName} for the entity name. Ie: {ClassName}Id + /// Configuration builder with this configuraiton applied + public InstantAPIsConfigBuilder PrimaryKeyMappingConvention(List conventions) + { + this._Config.PrimaryKeyMappingConventions.Clear(); + this._Config.PrimaryKeyMappingConventions.AddRange(conventions); + return this; + } + + #endregion + internal InstantAPIsConfig Build() { diff --git a/InstantAPIs/MapApiExtensions.cs b/InstantAPIs/MapApiExtensions.cs index 2df9539..7a2caf2 100644 --- a/InstantAPIs/MapApiExtensions.cs +++ b/InstantAPIs/MapApiExtensions.cs @@ -24,9 +24,24 @@ internal static void Initialize(ILogger logger) Logger = logger; var theType = typeof(C); - var idProp = theType.GetProperty("id", BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) ?? theType.GetProperties().FirstOrDefault(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute))); - - if (idProp != null) + var keyName = $"{theType.Name}Id"; + + // annotations will always override conventions.... + var idProp = theType.GetProperties().FirstOrDefault(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute))); + if (idProp == null) + { + foreach (var idName in WebApplicationExtensions.Configuration.PrimaryKeyMappingConventions) + { + if (idProp == null) + { + var propertyName = idName.Replace("{ClassName}", theType.Name); + idProp = theType.GetProperty(propertyName, + BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + } + } + } + + if (idProp != null) { _IdLookup.Add(theType, idProp); } diff --git a/InstantAPIs/WebApplicationExtensions.cs b/InstantAPIs/WebApplicationExtensions.cs index f52f6b6..026bab8 100644 --- a/InstantAPIs/WebApplicationExtensions.cs +++ b/InstantAPIs/WebApplicationExtensions.cs @@ -14,7 +14,7 @@ public static class WebApplicationExtensions internal const string LOGGER_CATEGORY_NAME = "InstantAPI"; - private static InstantAPIsConfig Configuration { get; set; } = new(); + internal static InstantAPIsConfig Configuration { get; set; } = new(); public static IEndpointRouteBuilder MapInstantAPIs(this IEndpointRouteBuilder app, Action> options = null) where D : DbContext { diff --git a/WorkingApi/Program.cs b/WorkingApi/Program.cs index 9452016..7686079 100644 --- a/WorkingApi/Program.cs +++ b/WorkingApi/Program.cs @@ -16,6 +16,7 @@ app.MapInstantAPIs(config => { config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.All, "addressBook"); + config.PrimaryKeyMappingConvention(new List() { "{ClassName}Id", "{ClassName}_Id", }); }); app.Run(); From b04ae013aa1f7e58e11a8a6d9d0ca6a10947c0d6 Mon Sep 17 00:00:00 2001 From: Wayne Mather Date: Tue, 23 Aug 2022 06:19:29 +0930 Subject: [PATCH 2/4] Reflection for property is case insensitive, so don't need Id twice --- InstantAPIs/InstantAPIs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InstantAPIs/InstantAPIs.csproj b/InstantAPIs/InstantAPIs.csproj index d7ab559..fbad930 100644 --- a/InstantAPIs/InstantAPIs.csproj +++ b/InstantAPIs/InstantAPIs.csproj @@ -15,7 +15,7 @@ true true embedded - 0.2.1 + 0.2.2 From 12e0b831c01441f359a7ab899aa8bcbbbdf8afdc Mon Sep 17 00:00:00 2001 From: Wayne Mather Date: Tue, 23 Aug 2022 06:20:30 +0930 Subject: [PATCH 3/4] Allowed user to override conventions for primary key names rather than [Key] annotation or id as the name Reflection for property is case insensitive, so don't need Id twice --- InstantAPIs/InstantAPIs.csproj | 2 +- InstantAPIs/InstantAPIsConfig.cs | 22 +++++++++++++++++++++- InstantAPIs/MapApiExtensions.cs | 21 ++++++++++++++++++--- InstantAPIs/WebApplicationExtensions.cs | 2 +- WorkingApi/Program.cs | 1 + 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/InstantAPIs/InstantAPIs.csproj b/InstantAPIs/InstantAPIs.csproj index d7ab559..fbad930 100644 --- a/InstantAPIs/InstantAPIs.csproj +++ b/InstantAPIs/InstantAPIs.csproj @@ -15,7 +15,7 @@ true true embedded - 0.2.1 + 0.2.2 diff --git a/InstantAPIs/InstantAPIsConfig.cs b/InstantAPIs/InstantAPIsConfig.cs index f7ce505..735fc63 100644 --- a/InstantAPIs/InstantAPIsConfig.cs +++ b/InstantAPIs/InstantAPIsConfig.cs @@ -5,6 +5,10 @@ internal class InstantAPIsConfig internal HashSet Tables { get; } = new HashSet(); + internal List PrimaryKeyMappingConventions { get; } = new List() { + "{ClassName}Id", "{ClassName}_Id", "Id", "ID" + }; + } @@ -17,7 +21,7 @@ public class InstantAPIsConfigBuilder where D : DbContext private readonly HashSet _IncludedTables = new(); private readonly List _ExcludedTables = new(); private const string DEFAULT_URI = "/api/"; - + public InstantAPIsConfigBuilder(D theContext) { this._TheContext = theContext; @@ -126,6 +130,22 @@ private void BuildTables() #endregion + #region Primary Key Mapping Conventions + + /// + /// Override the convention for determining primary keys of the entities. [Key] data annotation takes priority + /// + /// A list of conventions. You can use the string {ClassName} for the entity name. Ie: {ClassName}Id + /// Configuration builder with this configuraiton applied + public InstantAPIsConfigBuilder PrimaryKeyMappingConvention(List conventions) + { + this._Config.PrimaryKeyMappingConventions.Clear(); + this._Config.PrimaryKeyMappingConventions.AddRange(conventions); + return this; + } + + #endregion + internal InstantAPIsConfig Build() { diff --git a/InstantAPIs/MapApiExtensions.cs b/InstantAPIs/MapApiExtensions.cs index 2df9539..7a2caf2 100644 --- a/InstantAPIs/MapApiExtensions.cs +++ b/InstantAPIs/MapApiExtensions.cs @@ -24,9 +24,24 @@ internal static void Initialize(ILogger logger) Logger = logger; var theType = typeof(C); - var idProp = theType.GetProperty("id", BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) ?? theType.GetProperties().FirstOrDefault(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute))); - - if (idProp != null) + var keyName = $"{theType.Name}Id"; + + // annotations will always override conventions.... + var idProp = theType.GetProperties().FirstOrDefault(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute))); + if (idProp == null) + { + foreach (var idName in WebApplicationExtensions.Configuration.PrimaryKeyMappingConventions) + { + if (idProp == null) + { + var propertyName = idName.Replace("{ClassName}", theType.Name); + idProp = theType.GetProperty(propertyName, + BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + } + } + } + + if (idProp != null) { _IdLookup.Add(theType, idProp); } diff --git a/InstantAPIs/WebApplicationExtensions.cs b/InstantAPIs/WebApplicationExtensions.cs index f52f6b6..026bab8 100644 --- a/InstantAPIs/WebApplicationExtensions.cs +++ b/InstantAPIs/WebApplicationExtensions.cs @@ -14,7 +14,7 @@ public static class WebApplicationExtensions internal const string LOGGER_CATEGORY_NAME = "InstantAPI"; - private static InstantAPIsConfig Configuration { get; set; } = new(); + internal static InstantAPIsConfig Configuration { get; set; } = new(); public static IEndpointRouteBuilder MapInstantAPIs(this IEndpointRouteBuilder app, Action> options = null) where D : DbContext { diff --git a/WorkingApi/Program.cs b/WorkingApi/Program.cs index 9452016..7686079 100644 --- a/WorkingApi/Program.cs +++ b/WorkingApi/Program.cs @@ -16,6 +16,7 @@ app.MapInstantAPIs(config => { config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.All, "addressBook"); + config.PrimaryKeyMappingConvention(new List() { "{ClassName}Id", "{ClassName}_Id", }); }); app.Run(); From a1992e60b2a8b5b8732e70c7d14770ba89b90cda Mon Sep 17 00:00:00 2001 From: Wayne Mather Date: Tue, 23 Aug 2022 06:24:23 +0930 Subject: [PATCH 4/4] Reflection for property is case insensitive, so don't need Id twice --- InstantAPIs/InstantAPIsConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InstantAPIs/InstantAPIsConfig.cs b/InstantAPIs/InstantAPIsConfig.cs index 735fc63..aaef7c4 100644 --- a/InstantAPIs/InstantAPIsConfig.cs +++ b/InstantAPIs/InstantAPIsConfig.cs @@ -6,7 +6,7 @@ internal class InstantAPIsConfig internal HashSet Tables { get; } = new HashSet(); internal List PrimaryKeyMappingConventions { get; } = new List() { - "{ClassName}Id", "{ClassName}_Id", "Id", "ID" + "{ClassName}Id", "{ClassName}_Id", "Id" }; }