diff --git a/.gitmodules b/.gitmodules index d8f6cc8f..6cb6181c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/AspNetCore.Hashids"] path = lib/AspNetCore.Hashids url = https://github.com/dgmjr/AspNetCore.Hashids.git +[submodule "src/Http/CorrelationId"] + path = src/Http/CorrelationId + url = https://github.com/dgmjr-io/CorrelationId.git diff --git a/Program.cs b/Program.cs new file mode 100644 index 00000000..b580f44d --- /dev/null +++ b/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddRazorPages(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthorization(); + +app.MapRazorPages(); + +app.Run(); diff --git a/src/Communication.Abstractions/Dgmjr.AspNetCore.Communication.Abstractions.csproj b/src/Communication.Abstractions/Dgmjr.AspNetCore.Communication.Abstractions.csproj index 1b8a5591..64508191 100644 --- a/src/Communication.Abstractions/Dgmjr.AspNetCore.Communication.Abstractions.csproj +++ b/src/Communication.Abstractions/Dgmjr.AspNetCore.Communication.Abstractions.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Communication.Abstractions/Dgmjr.AspNetCore.Communication.Abstractions.sln b/src/Communication.Abstractions/Dgmjr.AspNetCore.Communication.Abstractions.sln index 94482ddd..04d4675c 100644 --- a/src/Communication.Abstractions/Dgmjr.AspNetCore.Communication.Abstractions.sln +++ b/src/Communication.Abstractions/Dgmjr.AspNetCore.Communication.Abstractions.sln @@ -2,13 +2,13 @@ # Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B283EBC2-E01F-412D-9339-FD56EF114549}" ProjectSection(SolutionItems) = preProject - ..\..\..\..\Directory.Build.props = ..\..\..\..\Directory.Build.props + ..\..\Directory.Build.props = ..\..\Directory.Build.props ..\..\..\..\Directory.Build.targets = ..\..\..\..\Directory.Build.targets ..\..\..\..\global.json = ..\..\..\..\global.json ..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\Packages\Versions.Local.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Communication.Abstractions", "Dgmjr.AspNetCore.Communication.Abstractions.csproj", "{2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Communication.Abstractions", "Dgmjr.AspNetCore.Communication.Abstractions.csproj", "{3B567DE2-8008-43B6-B04C-20586C7F30CF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -20,18 +20,18 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Local|Any CPU.ActiveCfg = Local|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Local|Any CPU.Build.0 = Local|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Testing|Any CPU.Build.0 = Testing|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Staging|Any CPU.Build.0 = Staging|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Production|Any CPU.ActiveCfg = Local|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Production|Any CPU.Build.0 = Local|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2BE89DB2-FCC3-4CC5-9713-6FD2CB8B2E4B}.Release|Any CPU.Build.0 = Release|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Local|Any CPU.ActiveCfg = Local|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Local|Any CPU.Build.0 = Local|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Testing|Any CPU.Build.0 = Testing|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Staging|Any CPU.Build.0 = Staging|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Production|Any CPU.ActiveCfg = Local|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Production|Any CPU.Build.0 = Local|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B567DE2-8008-43B6-B04C-20586C7F30CF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Communication.Abstractions/LICENSE.md b/src/Communication.Abstractions/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Communication.Abstractions/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Communication.Abstractions/icon.png b/src/Communication.Abstractions/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Communication.Abstractions/icon.png differ diff --git a/src/Communication/AzureCommunicationServicesAutoConfigurator.cs b/src/Communication/AzureCommunicationServicesAutoConfigurator.cs new file mode 100644 index 00000000..3de996e6 --- /dev/null +++ b/src/Communication/AzureCommunicationServicesAutoConfigurator.cs @@ -0,0 +1,124 @@ +/* + * AutoConfigurator.cs + * + * Created: 2024-17-19T08:17:28-05:00 + * Modified: 2024-17-19T08:17:28-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Microsoft.Extensions.DependencyInjection; + +using Dgmjr.AspNetCore.Communication; +using Dgmjr.AspNetCore.Communication.Email; +using Dgmjr.AspNetCore.Communication.Sms; + +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +public class AzureCommunicationServicesAutoConfigurator( + ILogger logger +) : IConfigureIHostApplicationBuilder, IConfigureIApplicationBuilder, ILog +{ + public ConfigurationOrder Order => ConfigurationOrder.AnyTime; + + public ILogger Logger => logger; + + public void Configure(IApplicationBuilder builder) + { + var emailSenderOptions = builder.ApplicationServices.GetService< + IOptions + >(); + if (emailSenderOptions is null) + { + Logger.LogWarning( + "Azure Email Communication Services options are not configured. Please configure them in your appsettings.json file." + ); + } + var smsSenderOptions = builder.ApplicationServices.GetService>(); + if (smsSenderOptions is null) + { + Logger.LogWarning( + "Azure SMS Communication Services options are not configured. Please configure them in your appsettings.json file." + ); + } + } + + public void Configure(IHostApplicationBuilder builder) + { + var optionsSection = builder.Configuration.GetSection( + AzureCommunicationServicesOptionsBase.ConfigurationSectionName + ); + if (!optionsSection.Exists()) + { + Logger.LogWarning( + "Azure Communication Services options are not configured. Please configure them in your appsettings.json file." + ); + } + else + { + builder.Services.Configure(optionsSection); + + var emailCommunicationSection = builder.Configuration.GetSection( + EmailSenderOptions.ConfigurationSectionName + ); + if (!emailCommunicationSection.Exists()) + { + Logger.LogWarning( + "Azure Email Communication Services options are not configured. Please configure them in your appsettings.json file." + ); + builder.Services.AddSingleton( + y => + throw new KeyNotFoundException( + $"The {nameof(EmailSenderOptions)} configuration section was not found in the appsettings.json file." + ) + ); + } + else + { + builder.Services.Configure(emailCommunicationSection); + builder.Services.AddSingleton( + y => + new EmailSender( + y.GetService>() + ?? throw new KeyNotFoundException( + $"The {nameof(EmailSenderOptions)} configuration section was not found in the appsettings.json file." + ) + ) + ); + } + var smsCommunicationSection = builder.Configuration.GetSection( + SmsSenderOptions.ConfigurationSectionName + ); + if (!smsCommunicationSection.Exists()) + { + Logger.LogWarning( + "Azure SMS Communication Services options are not configured. Please configure them in your appsettings.json file." + ); + builder.Services.AddSingleton( + y => + throw new KeyNotFoundException( + $"The {nameof(SmsSenderOptions)} configuration section was not found in the appsettings.json file." + ) + ); + } + else + { + builder.Services.Configure(smsCommunicationSection); + builder.Services.AddSingleton( + y => + new SmsSender( + y.GetService>() + ?? throw new KeyNotFoundException( + $"The {nameof(SmsSenderOptions)} configuration section was not found in the appsettings.json file." + ) + ) + ); + } + } + } +} diff --git a/src/Communication/AzureCommunicationServicesOptions.cs b/src/Communication/AzureCommunicationServicesOptions.cs index f42fca2c..10cc35b5 100644 --- a/src/Communication/AzureCommunicationServicesOptions.cs +++ b/src/Communication/AzureCommunicationServicesOptions.cs @@ -6,14 +6,32 @@ namespace Dgmjr.AspNetCore.Communication; using System; +using Dgmjr.AspNetCore.Communication.Email; +using Dgmjr.AspNetCore.Communication.Sms; + +public class AzureCommunicationServicesOptions +{ + public EmailSenderOptions Email { get; set; } = new EmailSenderOptions(); + public SmsSenderOptions Sms { get; set; } = new SmsSenderOptions(); +} + [RegexDto(_RegexString)] public partial record class AzureCommunicationServicesOptionsBase { + public const string EmptyValue = "endpoint=https://empty.example;accessKey=NOTHING"; + public const string ConfigurationSectionName = "CommunicationServices"; + #if NET8_0_OR_GREATER - [StringSyntax(SS.Regex)] + [@StringSyntax(SS.Regex)] #endif public const string _RegexString = - "^endpoint=(?https://.*);accessKey=(?.*)$"; + "^endpoint=(?https://.*);accessKey=(?.*)$"; + + public uri Endpoint + { + get => EndpointString; + init => EndpointString = value; + } public AzureCommunicationServicesOptionsBase(uri endpoint, string accessKey) { @@ -47,7 +65,7 @@ protected AzureCommunicationServicesOptions(uri endpoint, string accessKey) : base(endpoint, accessKey) { } protected AzureCommunicationServicesOptions() - : this(string.Empty) { } + : this(EmptyValue) { } public abstract TAddressType DefaultFrom { get; set; } diff --git a/src/Communication/Dgmjr.AspNetCore.Communication.csproj b/src/Communication/Dgmjr.AspNetCore.Communication.csproj index eb4bea27..73cbf1a5 100644 --- a/src/Communication/Dgmjr.AspNetCore.Communication.csproj +++ b/src/Communication/Dgmjr.AspNetCore.Communication.csproj @@ -12,21 +12,28 @@ - netstandard2.0;netstandard2.1;net6.0;net8.0 This package contains the communication classes for the project. It's used for sending SMS and email messages. comms + true + $(TargetedDotNetFrameworks) + + + + - + + + diff --git a/src/Communication/Dgmjr.AspNetCore.Communication.sln b/src/Communication/Dgmjr.AspNetCore.Communication.sln index 539edc41..7bacf8b5 100644 --- a/src/Communication/Dgmjr.AspNetCore.Communication.sln +++ b/src/Communication/Dgmjr.AspNetCore.Communication.sln @@ -2,15 +2,15 @@ # Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B283EBC2-E01F-412D-9339-FD56EF114549}" ProjectSection(SolutionItems) = preProject - ..\..\..\..\Directory.Build.props = ..\..\..\..\Directory.Build.props + ..\..\Directory.Build.props = ..\..\Directory.Build.props ..\..\..\..\Directory.Build.targets = ..\..\..\..\Directory.Build.targets ..\..\..\..\global.json = ..\..\..\..\global.json ..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\Packages\Versions.Local.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Communication.Abstractions", "..\Communication.Abstractions\Dgmjr.AspNetCore.Communication.Abstractions.csproj", "{D94A100A-53A6-45F1-8378-A506BC6530F0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Communication.Abstractions", "..\Communication.Abstractions\Dgmjr.AspNetCore.Communication.Abstractions.csproj", "{A21EC7BB-E229-4D5F-98BE-194738D8505B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Communication", "Dgmjr.AspNetCore.Communication.csproj", "{9E620C7A-C71B-47B9-BD3B-4F49ED64951D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Communication", "Dgmjr.AspNetCore.Communication.csproj", "{A6082934-C897-4FE1-85CA-AFBDB3EDE272}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -22,30 +22,30 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Local|Any CPU.ActiveCfg = Local|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Local|Any CPU.Build.0 = Local|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Testing|Any CPU.Build.0 = Testing|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Staging|Any CPU.Build.0 = Staging|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Production|Any CPU.ActiveCfg = Local|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Production|Any CPU.Build.0 = Local|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D94A100A-53A6-45F1-8378-A506BC6530F0}.Release|Any CPU.Build.0 = Release|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Local|Any CPU.ActiveCfg = Local|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Local|Any CPU.Build.0 = Local|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Testing|Any CPU.Build.0 = Testing|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Staging|Any CPU.Build.0 = Staging|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Production|Any CPU.ActiveCfg = Local|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Production|Any CPU.Build.0 = Local|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E620C7A-C71B-47B9-BD3B-4F49ED64951D}.Release|Any CPU.Build.0 = Release|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Local|Any CPU.ActiveCfg = Local|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Local|Any CPU.Build.0 = Local|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Testing|Any CPU.Build.0 = Testing|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Staging|Any CPU.Build.0 = Staging|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Production|Any CPU.ActiveCfg = Local|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Production|Any CPU.Build.0 = Local|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A21EC7BB-E229-4D5F-98BE-194738D8505B}.Release|Any CPU.Build.0 = Release|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Local|Any CPU.ActiveCfg = Local|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Local|Any CPU.Build.0 = Local|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Testing|Any CPU.Build.0 = Testing|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Staging|Any CPU.Build.0 = Staging|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Production|Any CPU.ActiveCfg = Local|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Production|Any CPU.Build.0 = Local|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6082934-C897-4FE1-85CA-AFBDB3EDE272}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Communication/Email/EmailSender.cs b/src/Communication/Email/EmailSender.cs index 547b11e0..8369ba71 100644 --- a/src/Communication/Email/EmailSender.cs +++ b/src/Communication/Email/EmailSender.cs @@ -28,7 +28,7 @@ public class EmailSender : IEmailSender public EmailSender(IOptions options) { _options = options?.Value; - _client = new EmailClient(new Uri(_options.ConnectionString), new DefaultAzureCredential()); + _client = new EmailClient(_options.Endpoint, new DefaultAzureCredential()); } public async Task SendEmailAsync(string email, string subject, string htmlMessage) diff --git a/src/Communication/Email/EmailSenderOptions.cs b/src/Communication/Email/EmailSenderOptions.cs index d9d6c255..018258d7 100644 --- a/src/Communication/Email/EmailSenderOptions.cs +++ b/src/Communication/Email/EmailSenderOptions.cs @@ -12,12 +12,16 @@ namespace Dgmjr.AspNetCore.Communication.Email; +using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Net.Mail; using Dgmjr.AspNetCore.Communication; public record class EmailSenderOptions : AzureCommunicationServicesOptions { + public new const string ConfigurationSectionName = + $"{AzureCommunicationServicesOptionsBase.ConfigurationSectionName}:Email"; + public override required EmailAddress DefaultFrom { get; set; } public static new EmailSenderOptions Parse(string connectionString) @@ -42,6 +46,9 @@ public EmailSenderOptions(AzureCommunicationServicesOptions option : base(options) { DefaultFrom = options.DefaultFrom; + AdminFrom = options.AdminFrom; + MassDistributionFrom = options.MassDistributionFrom; + SecurityFrom = options.SecurityFrom; } [SetsRequiredMembers] @@ -58,5 +65,5 @@ public EmailSenderOptions(string endpoint, string accessKey, EmailAddress? defau [SetsRequiredMembers] public EmailSenderOptions() - : this(string.Empty) { } + : this(EmptyValue) { } } diff --git a/src/Communication/LICENSE.md b/src/Communication/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Communication/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Communication/README.md b/src/Communication/README.md deleted file mode 100644 index 12cdaccb..00000000 --- a/src/Communication/README.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: -lastmod: 2023-41-25T05:41:07.7246-05:00Z -date: 2023-41-25T05:41:07.7250-05:00Z -license: MIT -slug: Dgmjr.AspNetCore.Communication-readme -version: -authors: - - dgmjr; -description: Dgmjr.AspNetCore.Communication Readme #TODO: write description for Dgmjr.AspNetCore.Communication Readme -keywords: -- Dgmjr.AspNetCore.Communication - - dgmjr - - dgmjr-io -type: readme ---- -# Dgmjr.AspNetCore.Communication Readme - -## Package Description -## Getting Started -## Prerequisites -## Installation -## Usage -## Contributing -## Versioning -Built from [commit on branch at ] -(/tree/) diff --git a/src/Communication/Sms/SmsSender.cs b/src/Communication/Sms/SmsSender.cs index 8015be05..17a85d46 100644 --- a/src/Communication/Sms/SmsSender.cs +++ b/src/Communication/Sms/SmsSender.cs @@ -24,12 +24,16 @@ namespace Dgmjr.AspNetCore.Communication.Sms; /// /// An sms sender. /// -public class SmsSender +/// +/// Initializes a new instance of the class. +/// +/// The options. +public class SmsSender(SmsSenderOptions? options) : ISmsSender { /// /// The options. /// - private readonly SmsSenderOptions _options; + private readonly SmsSenderOptions _options = options; /// /// Initializes a new instance of the class. @@ -38,12 +42,6 @@ public class SmsSender public SmsSender(IOptions options) : this(options?.Value) { } - /// - /// Initializes a new instance of the class. - /// - /// The options. - public SmsSender(SmsSenderOptions? options) => _options = options; - /// /// Creates the client. /// @@ -63,10 +61,13 @@ public SmsSender(IOptions options) /// The number of the recipient. /// The message. /// A ]]> - public async Task SendSmsAsync(PhoneNumber @to, string message) + public async Task SendSmsAsync(PhoneNumber @to, string message) { - return new( + return new SmsSendResult( (await Client.SendAsync(from: _options.DefaultFrom, to: @to, message: message)).Value ); } + + public Task SendSmsAsync(string to, string message) => + SendSmsAsync(PhoneNumber.From(to), message); } diff --git a/src/Communication/Sms/SmsSenderOptions.cs b/src/Communication/Sms/SmsSenderOptions.cs index 6849d41e..43969970 100644 --- a/src/Communication/Sms/SmsSenderOptions.cs +++ b/src/Communication/Sms/SmsSenderOptions.cs @@ -17,6 +17,9 @@ namespace Dgmjr.AspNetCore.Communication.Sms; public record class SmsSenderOptions : AzureCommunicationServicesOptions { + public new const string ConfigurationSectionName = + $"{AzureCommunicationServicesOptionsBase.ConfigurationSectionName}:Sms"; + /// /// The default phone number string. /// @@ -47,7 +50,7 @@ public SmsSenderOptions(string connectionString) /// Initializes a new instance of the class. /// public SmsSenderOptions() - : this(string.Empty) { } + : this(EmptyValue) { } /// /// Initializes a new instance of the class. diff --git a/src/Communication/icon.png b/src/Communication/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Communication/icon.png differ diff --git a/src/Configuration/Dgmjr.Configuration.Extensions.sln b/src/Configuration/Dgmjr.Configuration.Extensions.sln index 7081db25..1404e75d 100644 --- a/src/Configuration/Dgmjr.Configuration.Extensions.sln +++ b/src/Configuration/Dgmjr.Configuration.Extensions.sln @@ -8,7 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\Packages\Versions.Local.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Configuration.Extensions", "Dgmjr.Configuration.Extensions.csproj", "{C10E1908-6EE4-4069-ABBB-33427AC25720}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Configuration.Extensions", "Dgmjr.Configuration.Extensions.csproj", "{7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -20,18 +20,18 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Local|Any CPU.ActiveCfg = Local|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Local|Any CPU.Build.0 = Local|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Testing|Any CPU.Build.0 = Testing|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Staging|Any CPU.Build.0 = Staging|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Production|Any CPU.ActiveCfg = Local|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Production|Any CPU.Build.0 = Local|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C10E1908-6EE4-4069-ABBB-33427AC25720}.Release|Any CPU.Build.0 = Release|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Local|Any CPU.ActiveCfg = Local|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Local|Any CPU.Build.0 = Local|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Testing|Any CPU.Build.0 = Testing|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Staging|Any CPU.Build.0 = Staging|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Production|Any CPU.ActiveCfg = Local|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Production|Any CPU.Build.0 = Local|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7731DAB8-2AD9-4208-9FAE-50FB445AF0D1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Configuration/MultiServiceCollection.cs b/src/Configuration/MultiServiceCollection.cs index 609d2ccc..7e5c9e60 100644 --- a/src/Configuration/MultiServiceCollection.cs +++ b/src/Configuration/MultiServiceCollection.cs @@ -1,20 +1,24 @@ -using System.Reflection.Metadata.Ecma335; - using Microsoft.Extensions.DependencyInjection; namespace Dgmjr.Configuration.Extensions; -public class MultiServiceCollection(params IServiceCollection[] collections) : MultiCollection(collections), IServiceCollection +public class MultiServiceCollection(params IServiceCollection[] collections) + : MultiCollection(collections), + IServiceCollection { private readonly IServiceCollection[] _collections; - public ServiceDescriptor this[int index] { get => _collections[0][index]; set => throw new NotImplementedException(); } + public ServiceDescriptor this[int index] + { + get => _collections[0][index]; + set => throw new NotImplementedException(); + } public int IndexOf(ServiceDescriptor item) => _collections[0].IndexOf(item); public void Insert(int index, ServiceDescriptor item) { - foreach(var collection in _collections) + foreach (var collection in _collections) { collection.Insert(index, item); } @@ -22,7 +26,7 @@ public void Insert(int index, ServiceDescriptor item) public void RemoveAt(int index) { - foreach(var collection in _collections) + foreach (var collection in _collections) { collection.RemoveAt(index); } diff --git a/src/Configuration/icon.png b/src/Configuration/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Configuration/icon.png differ diff --git a/src/Controllers.Abstractions/LICENSE.md b/src/Controllers.Abstractions/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Controllers.Abstractions/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Controllers.SourceGenerator/LICENSE.md b/src/Controllers.SourceGenerator/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Controllers.SourceGenerator/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Controllers/LICENSE.md b/src/Controllers/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Controllers/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Controllers/icon.png b/src/Controllers/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Controllers/icon.png differ diff --git a/src/Conventions/LICENSE.md b/src/Conventions/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Conventions/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Conventions/icon.png b/src/Conventions/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Conventions/icon.png differ diff --git a/src/DownstreamApis/Dgmjr.Web.DownstreamApis.csproj b/src/DownstreamApis/Dgmjr.Web.DownstreamApis.csproj index 5d34c182..dee055cb 100644 --- a/src/DownstreamApis/Dgmjr.Web.DownstreamApis.csproj +++ b/src/DownstreamApis/Dgmjr.Web.DownstreamApis.csproj @@ -1,7 +1,7 @@ 0eb63fc8-4d68-471c-bf25-ff7fbe57a33f - netstandard2.0;netstandard2.1;net6.0;net8.0 + net6.0;net8.0 Contains classes for simplifying working with distributed API condfiguration @@ -9,6 +9,7 @@ + diff --git a/src/DownstreamApis/DownstreamApiOptionsConfigurator.cs b/src/DownstreamApis/DownstreamApiOptionsConfigurator.cs index 328946aa..4b4d916b 100644 --- a/src/DownstreamApis/DownstreamApiOptionsConfigurator.cs +++ b/src/DownstreamApis/DownstreamApiOptionsConfigurator.cs @@ -1,5 +1,6 @@ namespace Dgmjr.Web.DownstreamApis; +using Application = Dgmjr.Mime.Application; using System.Net.Http; public class DownstreamApiOptionsConfigurator(IOptions jsonOptions) diff --git a/src/DownstreamApis/DownstreamApisAutoConfigurator.cs b/src/DownstreamApis/DownstreamApisAutoConfigurator.cs new file mode 100644 index 00000000..e52915ce --- /dev/null +++ b/src/DownstreamApis/DownstreamApisAutoConfigurator.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; + +namespace Dgmjr.Web.DownstreamApis; + +public class DownstreamApisAutoConfigurator + : IConfigureIHostApplicationBuilder, + IConfigureIApplicationBuilder +{ + public ConfigurationOrder Order => ConfigurationOrder.AnyTime; + + public void Configure(IHostApplicationBuilder builder) { } + + public void Configure(IApplicationBuilder app) { } +} diff --git a/src/DownstreamApis/icon.png b/src/DownstreamApis/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/DownstreamApis/icon.png differ diff --git a/src/ErrorHandling/LICENSE.md b/src/ErrorHandling/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/ErrorHandling/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/ErrorHandling/icon.png b/src/ErrorHandling/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/ErrorHandling/icon.png differ diff --git a/src/Formatters/icon.png b/src/Formatters/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Formatters/icon.png differ diff --git a/src/Hashids/LICENSE.md b/src/Hashids/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Hashids/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Hashids/icon.png b/src/Hashids/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Hashids/icon.png differ diff --git a/src/HealthChecks/HealthChecks/LICENSE.md b/src/HealthChecks/HealthChecks/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/HealthChecks/HealthChecks/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Http/CorrelationId b/src/Http/CorrelationId new file mode 160000 index 00000000..7b647e57 --- /dev/null +++ b/src/Http/CorrelationId @@ -0,0 +1 @@ +Subproject commit 7b647e57cf5e069290ff5b835c82b1157bbea1b3 diff --git a/src/Http/CorrelationIdAutoConfigurator/CorrelationIdAutoConfigurator.cs b/src/Http/CorrelationIdAutoConfigurator/CorrelationIdAutoConfigurator.cs new file mode 100644 index 00000000..360f39b8 --- /dev/null +++ b/src/Http/CorrelationIdAutoConfigurator/CorrelationIdAutoConfigurator.cs @@ -0,0 +1,27 @@ +namespace Microsoft.Extensions.DependencyInjection; + +using CorrelationId; +using CorrelationId.DependencyInjection; +using Dgmjr.Configuration.Extensions; + +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; + +public class CorrelationIdAutoConfigurator + : IConfigureIHostApplicationBuilder, + IConfigureIApplicationBuilder +{ + public ConfigurationOrder Order => ConfigurationOrder.VeryEarly; + + public void Configure(IHostApplicationBuilder builder) + { + builder.Services.AddDefaultCorrelationId( + options => builder.Configuration.GetSection(nameof(CorrelationId)).Bind(options) + ); + } + + public void Configure(IApplicationBuilder app) + { + app.UseCorrelationId(); + } +} diff --git a/src/Http/CorrelationIdAutoConfigurator/Dgmjr.Http.CorrelationId.csproj b/src/Http/CorrelationIdAutoConfigurator/Dgmjr.Http.CorrelationId.csproj new file mode 100644 index 00000000..1ae3fca1 --- /dev/null +++ b/src/Http/CorrelationIdAutoConfigurator/Dgmjr.Http.CorrelationId.csproj @@ -0,0 +1,12 @@ + + + $(TargetedDotNetFrameworks) + + + + + + + + + diff --git a/src/Http/CorrelationIdAutoConfigurator/Dgmjr.Http.CorrelationId.sln b/src/Http/CorrelationIdAutoConfigurator/Dgmjr.Http.CorrelationId.sln new file mode 100644 index 00000000..87da98fb --- /dev/null +++ b/src/Http/CorrelationIdAutoConfigurator/Dgmjr.Http.CorrelationId.sln @@ -0,0 +1,42 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B283EBC2-E01F-412D-9339-FD56EF114549}" + ProjectSection(SolutionItems) = preProject + ..\..\..\Directory.Build.props = ..\..\..\Directory.Build.props + ..\..\..\..\..\Directory.Build.targets = ..\..\..\..\..\Directory.Build.targets + ..\..\..\..\..\global.json = ..\..\..\..\..\global.json + ..\..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\..\Packages\Versions.Local.props + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Http.CorrelationId", "Dgmjr.Http.CorrelationId.csproj", "{31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Local|Any CPU = Local|Any CPU + Debug|Any CPU = Debug|Any CPU + Testing|Any CPU = Testing|Any CPU + Staging|Any CPU = Staging|Any CPU + Production|Any CPU = Production|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Local|Any CPU.ActiveCfg = Local|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Local|Any CPU.Build.0 = Local|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Testing|Any CPU.Build.0 = Testing|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Staging|Any CPU.Build.0 = Staging|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Production|Any CPU.ActiveCfg = Local|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Production|Any CPU.Build.0 = Local|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31A05FAC-E6CB-4140-91F0-63AD38E5E8BD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B1560816-B324-4B29-924F-825A5E40B2B0} + EndGlobalSection +EndGlobal diff --git a/src/Http/CorrelationIdAutoConfigurator/LICENSE.md b/src/Http/CorrelationIdAutoConfigurator/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Http/CorrelationIdAutoConfigurator/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Http/CorrelationIdAutoConfigurator/icon.png b/src/Http/CorrelationIdAutoConfigurator/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Http/CorrelationIdAutoConfigurator/icon.png differ diff --git a/src/Http/Extensions/icon.png b/src/Http/Extensions/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Http/Extensions/icon.png differ diff --git a/src/Http/Headers/icon.png b/src/Http/Headers/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Http/Headers/icon.png differ diff --git a/src/Http/Http/icon.png b/src/Http/Http/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Http/Http/icon.png differ diff --git a/src/Http/Mime/ApplicationMediaTypeNames.cs b/src/Http/Mime/ApplicationMediaTypeNames.cs index a13d00b3..34950989 100644 --- a/src/Http/Mime/ApplicationMediaTypeNames.cs +++ b/src/Http/Mime/ApplicationMediaTypeNames.cs @@ -41,7 +41,7 @@ public static class ApplicationMediaTypeNames public const string Json = Base + "/json"; /// /x-www-formurlencoded - public const string FormUrlEncoded = Base + "/x-www-formurlencoded"; + public const string FormUrlEncoded = Base + "/x-www-form-urlencoded"; /// /problem+json public const string ProblemJson = Base + "/problem+json"; @@ -68,10 +68,12 @@ public static class ApplicationMediaTypeNames public const string JavaScript = Base + "/javascript"; /// /openapi - public const string OpenApiJson = $"{MediaType.Application.DisplayName}/openapi{Mime.Suffixes.Json.DisplayName}"; + public const string OpenApiJson = + $"{MediaType.Application.DisplayName}/openapi{Mime.Suffixes.Json.DisplayName}"; /// /openapi - public const string OpenApiYaml = $"{MediaType.Application.DisplayName}/openapi{Mime.Suffixes.Yaml.DisplayName}"; + public const string OpenApiYaml = + $"{MediaType.Application.DisplayName}/openapi{Mime.Suffixes.Yaml.DisplayName}"; /// ; version=2.x public const string OpenApiV2Json = $"{OpenApiJson}; version=2.x"; @@ -82,6 +84,6 @@ public static class ApplicationMediaTypeNames /// ; version=3.x public const string OpenApiV3Json = $"{OpenApiJson}; version=3.x"; - /// ; version=3.x + /// ; version=3.x public const string OpenApiV3Yaml = $"{OpenApiYaml}; version=3.x"; } diff --git a/src/Http/Mime/Dgmjr.Mime.csproj b/src/Http/Mime/Dgmjr.Mime.csproj index 75ef70f5..0554c662 100644 --- a/src/Http/Mime/Dgmjr.Mime.csproj +++ b/src/Http/Mime/Dgmjr.Mime.csproj @@ -12,7 +12,7 @@ - netstandard2.0;netstandard2.1;net6.0;net8.0 + netstandard2.0 Provides a set of constants for the most common media types. Fills in the gaps left by the class. true constants @@ -36,6 +36,7 @@ + diff --git a/src/Http/Mime/Dgmjr.Mime.sln b/src/Http/Mime/Dgmjr.Mime.sln index b3925c3d..d86bb846 100644 --- a/src/Http/Mime/Dgmjr.Mime.sln +++ b/src/Http/Mime/Dgmjr.Mime.sln @@ -8,7 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\..\Packages\Versions.Local.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Mime", "Dgmjr.Mime.csproj", "{0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Mime", "Dgmjr.Mime.csproj", "{7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -20,18 +20,18 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Local|Any CPU.ActiveCfg = Local|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Local|Any CPU.Build.0 = Local|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Testing|Any CPU.Build.0 = Testing|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Staging|Any CPU.Build.0 = Staging|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Production|Any CPU.ActiveCfg = Local|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Production|Any CPU.Build.0 = Local|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C5C8570-0B57-4D82-ADE2-A4DDF4FC24F2}.Release|Any CPU.Build.0 = Release|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Local|Any CPU.ActiveCfg = Local|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Local|Any CPU.Build.0 = Local|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Testing|Any CPU.Build.0 = Testing|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Staging|Any CPU.Build.0 = Staging|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Production|Any CPU.ActiveCfg = Local|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Production|Any CPU.Build.0 = Local|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7251BA6B-4FF8-45FB-9380-9E4EBC5A867B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Http/ResponseCodes/Dgmjr.Http.StatusCodes.csproj b/src/Http/ResponseCodes/Dgmjr.Http.StatusCodes.csproj index 5e723393..b1b94744 100644 --- a/src/Http/ResponseCodes/Dgmjr.Http.StatusCodes.csproj +++ b/src/Http/ResponseCodes/Dgmjr.Http.StatusCodes.csproj @@ -1,6 +1,6 @@ - netstandard2.0;netstandard2.1;net6.0;net8.0 + netstandard2.0 diff --git a/src/Http/ResponseCodes/Dgmjr.Http.StatusCodes.sln b/src/Http/ResponseCodes/Dgmjr.Http.StatusCodes.sln index eaed81a8..b7c646fa 100644 --- a/src/Http/ResponseCodes/Dgmjr.Http.StatusCodes.sln +++ b/src/Http/ResponseCodes/Dgmjr.Http.StatusCodes.sln @@ -8,7 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\..\Packages\Versions.Local.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Http.StatusCodes", "Dgmjr.Http.StatusCodes.csproj", "{64DA7375-65E7-4D6C-BC2B-C67AED60957E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Http.StatusCodes", "Dgmjr.Http.StatusCodes.csproj", "{A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -20,18 +20,18 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Local|Any CPU.ActiveCfg = Local|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Local|Any CPU.Build.0 = Local|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Testing|Any CPU.Build.0 = Testing|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Staging|Any CPU.Build.0 = Staging|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Production|Any CPU.ActiveCfg = Local|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Production|Any CPU.Build.0 = Local|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {64DA7375-65E7-4D6C-BC2B-C67AED60957E}.Release|Any CPU.Build.0 = Release|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Local|Any CPU.ActiveCfg = Local|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Local|Any CPU.Build.0 = Local|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Testing|Any CPU.Build.0 = Testing|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Staging|Any CPU.Build.0 = Staging|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Production|Any CPU.ActiveCfg = Local|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Production|Any CPU.Build.0 = Local|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A08126BD-7F0C-49DB-9B73-9AAC242CCFEA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Http/ResponseCodes/ResponseCode.cs b/src/Http/ResponseCodes/ResponseCode.cs index 5f8480e0..084abc39 100644 --- a/src/Http/ResponseCodes/ResponseCode.cs +++ b/src/Http/ResponseCodes/ResponseCode.cs @@ -230,11 +230,11 @@ public enum StatusCode : ushort /// In this case, the request should be repeated with another URI; however, future requests should still use the original URI. In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. For example, a POST request should be repeated using another POST request. /// 307 + /// [Display( Name = "Temporary Redirect", Description = "In this case, the request should be repeated with another URI; however, future requests should still use the original URI. In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. For example, a POST request should be repeated using another POST request." )] - /// [Uri($"{UriBase}307")] TemporaryRedirect = 307, @@ -280,11 +280,11 @@ public enum StatusCode : ushort /// The request was valid, but the server is refusing action. The user might not have the necessary permissions for a resource, or may need an account of some sort. /// 403 + /// [Display( Name = "Forbidden", Description = "The request was valid, but the server is refusing action. The user might not have the necessary permissions for a resource, or may need an account of some sort." )] - /// [Uri($"{UriBase}403")] Forbidden = 403, diff --git a/src/Http/ResponseCodes/icon.png b/src/Http/ResponseCodes/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Http/ResponseCodes/icon.png differ diff --git a/src/Http/Services/icon.png b/src/Http/Services/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Http/Services/icon.png differ diff --git a/src/JsonYaml/UnRefJsonYaml/LICENSE.md b/src/JsonYaml/UnRefJsonYaml/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/JsonYaml/UnRefJsonYaml/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/JsonYaml/UnRefJsonYaml/UnRef/LICENSE.md b/src/JsonYaml/UnRefJsonYaml/UnRef/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/JsonYaml/UnRefJsonYaml/UnRef/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/MicrosoftGraph/Constants/MimeTypes.cs b/src/MicrosoftGraph/Constants/MimeTypes.cs index 34621b39..6fe5d1ca 100644 --- a/src/MicrosoftGraph/Constants/MimeTypes.cs +++ b/src/MicrosoftGraph/Constants/MimeTypes.cs @@ -1,6 +1,7 @@ namespace Dgmjr.Graph.Constants; using Dgmjr.Mime; +using Application = Dgmjr.Mime.Application; public static class MimeTypes { diff --git a/src/MicrosoftGraph/Constants/MsGraphConstants.cs b/src/MicrosoftGraph/Constants/MsGraphConstants.cs index c44167be..29979f0b 100644 --- a/src/MicrosoftGraph/Constants/MsGraphConstants.cs +++ b/src/MicrosoftGraph/Constants/MsGraphConstants.cs @@ -1,4 +1,5 @@ namespace Dgmjr.Graph; + using Dgmjr.Web.DownstreamApis; public static class MsGraphConstants @@ -10,10 +11,15 @@ public static class MsGraphConstants public const string MicrosoftGraph_ClientSecret = "ClientSecret"; - public const string DownstreamApis_MicrosoftGraph = $"{DownstreamApis.AppSettingsKey}:{MicrosoftGraph}"; - public const string DownstreamApis_MicrosoftGraph_Scopes = $"{DownstreamApis.AppSettingsKey}:{MicrosoftGraph}:{Scopes}"; + public const string DownstreamApis_MicrosoftGraph = + $"{DownstreamApisBase.AppSettingsKey}:{MicrosoftGraph}"; + public const string DownstreamApis_MicrosoftGraph_Scopes = + $"{DownstreamApisBase.AppSettingsKey}:{MicrosoftGraph}:{Scopes}"; - public const string AzureAdB2CExtensionsApplicationId = nameof(AzureAdB2CExtensionsApplicationId); + public const string AzureAdB2CExtensionsApplicationId = nameof( + AzureAdB2CExtensionsApplicationId + ); - public const string DownstreamApis_MicrosoftGraph_AzureAdB2CExtensionsApplicationId = $"{DownstreamApis_MicrosoftGraph}:{AzureAdB2CExtensionsApplicationId}"; + public const string DownstreamApis_MicrosoftGraph_AzureAdB2CExtensionsApplicationId = + $"{DownstreamApis_MicrosoftGraph}:{AzureAdB2CExtensionsApplicationId}"; } diff --git a/src/MicrosoftGraph/Dgmjr.Graph.csproj b/src/MicrosoftGraph/Dgmjr.Graph.csproj index e046adce..a7043f37 100644 --- a/src/MicrosoftGraph/Dgmjr.Graph.csproj +++ b/src/MicrosoftGraph/Dgmjr.Graph.csproj @@ -43,7 +43,4 @@ - - - diff --git a/src/MicrosoftGraph/Dgmjr.Graph.sln b/src/MicrosoftGraph/Dgmjr.Graph.sln index 0c1a1602..9caa597d 100644 --- a/src/MicrosoftGraph/Dgmjr.Graph.sln +++ b/src/MicrosoftGraph/Dgmjr.Graph.sln @@ -10,7 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Web.DownstreamApis", "..\DownstreamApis\Dgmjr.Web.DownstreamApis.csproj", "{0EB63FC8-4D68-471C-BF25-FF7FBE57A33F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Graph", "Dgmjr.Graph.csproj", "{5C8E0E57-5C56-4505-91C3-616D1860A342}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Graph", "Dgmjr.Graph.csproj", "{8F6C27A5-36AD-4A74-BF09-B0C02B346412}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -34,18 +34,18 @@ Global {0EB63FC8-4D68-471C-BF25-FF7FBE57A33F}.Production|Any CPU.Build.0 = Local|Any CPU {0EB63FC8-4D68-471C-BF25-FF7FBE57A33F}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EB63FC8-4D68-471C-BF25-FF7FBE57A33F}.Release|Any CPU.Build.0 = Release|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Local|Any CPU.ActiveCfg = Local|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Local|Any CPU.Build.0 = Local|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Testing|Any CPU.Build.0 = Testing|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Staging|Any CPU.Build.0 = Staging|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Production|Any CPU.ActiveCfg = Local|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Production|Any CPU.Build.0 = Local|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C8E0E57-5C56-4505-91C3-616D1860A342}.Release|Any CPU.Build.0 = Release|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Local|Any CPU.ActiveCfg = Local|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Local|Any CPU.Build.0 = Local|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Testing|Any CPU.Build.0 = Testing|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Staging|Any CPU.Build.0 = Staging|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Production|Any CPU.ActiveCfg = Local|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Production|Any CPU.Build.0 = Local|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F6C27A5-36AD-4A74-BF09-B0C02B346412}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/MicrosoftGraph/Extensions/MicrosoftGraphServiceCollectionExtensions.cs b/src/MicrosoftGraph/Extensions/MicrosoftGraphServiceCollectionExtensions.cs index 1ebb9da2..3fdd497b 100644 --- a/src/MicrosoftGraph/Extensions/MicrosoftGraphServiceCollectionExtensions.cs +++ b/src/MicrosoftGraph/Extensions/MicrosoftGraphServiceCollectionExtensions.cs @@ -8,6 +8,7 @@ IConfiguration config ) { var configSection = config.GetSection(DownstreamApis_MicrosoftGraph); + var options = configSection.Get(); services .AddMicrosoftGraph(options => config.Bind(options)) .AddMicrosoftIdentityConsentHandler() @@ -15,7 +16,7 @@ IConfiguration config services.AddScoped(); services.Configure(configSection); services.AddScoped(); - services.AddSingleton(); + services.AddPassphraseGenerator(config); return services; } diff --git a/src/MicrosoftGraph/Options/MicrosoftGraphOptions.cs b/src/MicrosoftGraph/Options/MicrosoftGraphOptions.cs index cb5b15f0..a555f367 100644 --- a/src/MicrosoftGraph/Options/MicrosoftGraphOptions.cs +++ b/src/MicrosoftGraph/Options/MicrosoftGraphOptions.cs @@ -7,4 +7,6 @@ public class MicrosoftB2CGraphOptions : Microsoft.Identity.Web.MicrosoftGraphOpt /// The of the Azure AD B2C extensions application public guid AzureAdB2CExtensionsApplicationId { get; set; } + + public bool AppOnly { get; set; } = false; } diff --git a/src/MicrosoftGraph/icon.png b/src/MicrosoftGraph/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/MicrosoftGraph/icon.png differ diff --git a/src/Mvc.Polyfills/Dgmjr.AspNetCore.Mvc.Polyfills.csproj b/src/Mvc.Polyfills/Dgmjr.AspNetCore.Mvc.Polyfills.csproj index 6b556de5..cd47ac04 100644 --- a/src/Mvc.Polyfills/Dgmjr.AspNetCore.Mvc.Polyfills.csproj +++ b/src/Mvc.Polyfills/Dgmjr.AspNetCore.Mvc.Polyfills.csproj @@ -1,6 +1,6 @@ - netstandard2.0;netstandard2.1; + netstandard2.0;netstandard2.1;net6.0;net8.0 diff --git a/src/Mvc.Polyfills/Dgmjr.AspNetCore.Mvc.Polyfills.sln b/src/Mvc.Polyfills/Dgmjr.AspNetCore.Mvc.Polyfills.sln index df8ce52e..fe12ec81 100644 --- a/src/Mvc.Polyfills/Dgmjr.AspNetCore.Mvc.Polyfills.sln +++ b/src/Mvc.Polyfills/Dgmjr.AspNetCore.Mvc.Polyfills.sln @@ -8,7 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\Packages\Versions.Local.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Mvc.Polyfills", "Dgmjr.AspNetCore.Mvc.Polyfills.csproj", "{E6903D07-59E8-45EB-B433-832770DB1324}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Mvc.Polyfills", "Dgmjr.AspNetCore.Mvc.Polyfills.csproj", "{14297B8A-8C44-44BD-9347-B5D45E01AA72}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -20,18 +20,18 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E6903D07-59E8-45EB-B433-832770DB1324}.Local|Any CPU.ActiveCfg = Local|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Local|Any CPU.Build.0 = Local|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Testing|Any CPU.Build.0 = Testing|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Staging|Any CPU.Build.0 = Staging|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Production|Any CPU.ActiveCfg = Local|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Production|Any CPU.Build.0 = Local|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E6903D07-59E8-45EB-B433-832770DB1324}.Release|Any CPU.Build.0 = Release|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Local|Any CPU.ActiveCfg = Local|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Local|Any CPU.Build.0 = Local|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Testing|Any CPU.Build.0 = Testing|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Staging|Any CPU.Build.0 = Staging|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Production|Any CPU.ActiveCfg = Local|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Production|Any CPU.Build.0 = Local|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14297B8A-8C44-44BD-9347-B5D45E01AA72}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Mvc.Polyfills/IStatusCodeActionResult.cs b/src/Mvc.Polyfills/IStatusCodeActionResult.cs index 66706c02..cb2e630e 100644 --- a/src/Mvc.Polyfills/IStatusCodeActionResult.cs +++ b/src/Mvc.Polyfills/IStatusCodeActionResult.cs @@ -1,4 +1,4 @@ -#if !NET5_0_OR_GREATER +#if !NET6_0_OR_GREATER namespace Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc; diff --git a/src/Mvc.Polyfills/JsonOptions.cs b/src/Mvc.Polyfills/JsonOptions.cs index dc443fdd..27a4a43d 100644 --- a/src/Mvc.Polyfills/JsonOptions.cs +++ b/src/Mvc.Polyfills/JsonOptions.cs @@ -1,4 +1,4 @@ -#if !NET5_0_OR_GREATER +#if !NET6_0_OR_GREATER namespace Microsoft.AspNetCore.Mvc; public class JsonOptions diff --git a/src/Mvc.Polyfills/LICENSE.md b/src/Mvc.Polyfills/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Mvc.Polyfills/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Mvc.Polyfills/icon.png b/src/Mvc.Polyfills/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Mvc.Polyfills/icon.png differ diff --git a/src/Mvc/ApiControllerBase.cs b/src/Mvc/ApiControllerBase.cs index 0e16889e..f8a7d04c 100644 --- a/src/Mvc/ApiControllerBase.cs +++ b/src/Mvc/ApiControllerBase.cs @@ -12,10 +12,16 @@ namespace Dgmjr.AspNetCore.Mvc; +using Dgmjr.Abstractions; + using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; -public class ApiControllerBase : ControllerBase +public class ApiControllerBase(ILogger? logger = null) : ControllerBase, ILog { + public ILogger Logger => logger ?? new NullLogger(); + public IActionResult Result(T value, string contentType) => ControllerExtensions.Result(this, value, contentType); } diff --git a/src/Mvc/Constants.cs b/src/Mvc/Constants.cs index c0a92bf9..4d33ecda 100644 --- a/src/Mvc/Constants.cs +++ b/src/Mvc/Constants.cs @@ -1,4 +1,5 @@ namespace Dgmjr.AspNetCore.Mvc; +using Dgmjr.Mime; public static class Constants { diff --git a/src/Mvc/ControllerExtensions.cs b/src/Mvc/ControllerExtensions.cs index f27d8e04..5a455506 100644 --- a/src/Mvc/ControllerExtensions.cs +++ b/src/Mvc/ControllerExtensions.cs @@ -18,5 +18,5 @@ public static IActionResult Result( this ControllerBase controller, T value, string contentType - ) => new Result(value, contentType).Convert(); + ) => new Result(value, contentType); } diff --git a/src/Mvc/Dgmjr.AspNetCore.Mvc.csproj b/src/Mvc/Dgmjr.AspNetCore.Mvc.csproj index 6003f6a5..1be89698 100644 --- a/src/Mvc/Dgmjr.AspNetCore.Mvc.csproj +++ b/src/Mvc/Dgmjr.AspNetCore.Mvc.csproj @@ -26,20 +26,19 @@ - - + diff --git a/src/Mvc/Dgmjr.AspNetCore.Mvc.props b/src/Mvc/Dgmjr.AspNetCore.Mvc.props index 76c52102..d2b3955a 100644 --- a/src/Mvc/Dgmjr.AspNetCore.Mvc.props +++ b/src/Mvc/Dgmjr.AspNetCore.Mvc.props @@ -18,6 +18,7 @@ + @@ -25,8 +26,25 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Mvc/Dgmjr.AspNetCore.Mvc.sln b/src/Mvc/Dgmjr.AspNetCore.Mvc.sln index e4a6d498..6ebbbbb5 100644 --- a/src/Mvc/Dgmjr.AspNetCore.Mvc.sln +++ b/src/Mvc/Dgmjr.AspNetCore.Mvc.sln @@ -8,13 +8,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\Packages\Versions.Local.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Mime", "..\Http\Mime\Dgmjr.Mime.csproj", "{CD887D25-8354-4BD2-AD5F-D3AF63B7237F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Mime", "..\Http\Mime\Dgmjr.Mime.csproj", "{0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Http.StatusCodes", "..\Http\ResponseCodes\Dgmjr.Http.StatusCodes.csproj", "{0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Http.StatusCodes", "..\Http\ResponseCodes\Dgmjr.Http.StatusCodes.csproj", "{255F91DE-EF50-457A-8688-C526FD2BF0F4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Mvc.Polyfills", "..\Mvc.Polyfills\Dgmjr.AspNetCore.Mvc.Polyfills.csproj", "{1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Mvc", "Dgmjr.AspNetCore.Mvc.csproj", "{463260AF-8E29-4BBB-AE02-5766966AC651}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Mvc", "Dgmjr.AspNetCore.Mvc.csproj", "{8494B973-A402-4F9B-8262-72DA92DA74D7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -26,54 +24,42 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Local|Any CPU.ActiveCfg = Local|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Local|Any CPU.Build.0 = Local|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Testing|Any CPU.Build.0 = Testing|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Staging|Any CPU.Build.0 = Staging|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Production|Any CPU.ActiveCfg = Local|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Production|Any CPU.Build.0 = Local|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD887D25-8354-4BD2-AD5F-D3AF63B7237F}.Release|Any CPU.Build.0 = Release|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Local|Any CPU.ActiveCfg = Local|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Local|Any CPU.Build.0 = Local|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Testing|Any CPU.Build.0 = Testing|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Staging|Any CPU.Build.0 = Staging|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Production|Any CPU.ActiveCfg = Local|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Production|Any CPU.Build.0 = Local|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BE328E0-0AF8-43B5-BAF0-4E1B6891DD6A}.Release|Any CPU.Build.0 = Release|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Local|Any CPU.ActiveCfg = Local|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Local|Any CPU.Build.0 = Local|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Testing|Any CPU.Build.0 = Testing|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Staging|Any CPU.Build.0 = Staging|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Production|Any CPU.ActiveCfg = Local|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Production|Any CPU.Build.0 = Local|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E6B8B2A-68C0-47B9-9D29-DD87B90D170A}.Release|Any CPU.Build.0 = Release|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Local|Any CPU.ActiveCfg = Local|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Local|Any CPU.Build.0 = Local|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Debug|Any CPU.Build.0 = Debug|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Testing|Any CPU.Build.0 = Testing|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Staging|Any CPU.Build.0 = Staging|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Production|Any CPU.ActiveCfg = Local|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Production|Any CPU.Build.0 = Local|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Release|Any CPU.ActiveCfg = Release|Any CPU - {463260AF-8E29-4BBB-AE02-5766966AC651}.Release|Any CPU.Build.0 = Release|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Local|Any CPU.ActiveCfg = Local|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Local|Any CPU.Build.0 = Local|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Testing|Any CPU.Build.0 = Testing|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Staging|Any CPU.Build.0 = Staging|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Production|Any CPU.ActiveCfg = Local|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Production|Any CPU.Build.0 = Local|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F9518CF-E1E7-4697-BF2D-1C3FE4D285ED}.Release|Any CPU.Build.0 = Release|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Local|Any CPU.ActiveCfg = Local|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Local|Any CPU.Build.0 = Local|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Testing|Any CPU.Build.0 = Testing|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Staging|Any CPU.Build.0 = Staging|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Production|Any CPU.ActiveCfg = Local|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Production|Any CPU.Build.0 = Local|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {255F91DE-EF50-457A-8688-C526FD2BF0F4}.Release|Any CPU.Build.0 = Release|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Local|Any CPU.ActiveCfg = Local|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Local|Any CPU.Build.0 = Local|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Testing|Any CPU.Build.0 = Testing|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Staging|Any CPU.Build.0 = Staging|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Production|Any CPU.ActiveCfg = Local|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Production|Any CPU.Build.0 = Local|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8494B973-A402-4F9B-8262-72DA92DA74D7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Mvc/IHostApplicationBuilderMvcExtensions.cs b/src/Mvc/IHostApplicationBuilderMvcExtensions.cs index dda38db8..30d53519 100644 --- a/src/Mvc/IHostApplicationBuilderMvcExtensions.cs +++ b/src/Mvc/IHostApplicationBuilderMvcExtensions.cs @@ -65,7 +65,7 @@ public static IHostApplicationBuilder AddMvc( mvcBuilder.AddXmlDataContractSerializerFormatters(); } - if(mvcOptions.AddMicrosoftIdentityUI) + if (mvcOptions.AddMicrosoftIdentityUI) { mvcBuilder.AddMicrosoftIdentityUI(); } @@ -74,12 +74,17 @@ public static IHostApplicationBuilder AddMvc( { // mvcBuilder.AddMvcOptions(options => builder.Configuration.Bind(configurationSectionKey, options)); } - } - builder.Services.Configure( - options => - builder.Configuration.GetSection(JsonSerializer).Bind(options.JsonSerializerOptions) - ); + if (mvcOptions.AddJsonOptions) + { + builder.Services.Configure( + options => + builder.Configuration + .GetSection(JsonSerializer) + .Bind(options.JsonSerializerOptions) + ); + } + } return builder; } diff --git a/src/Mvc/ProblemDetailsExamples/MethodNotAllowedProblemDetailsExample.cs b/src/Mvc/ProblemDetailsExamples/MethodNotAllowedProblemDetailsExample.cs new file mode 100644 index 00000000..3e90495e --- /dev/null +++ b/src/Mvc/ProblemDetailsExamples/MethodNotAllowedProblemDetailsExample.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Dgmjr.AspNetCore.Mvc; + +public class MethodNotAllowedProblemDetails : IProblemDetails +{ + /// + public string? Type => Dgmjr.Http.StatusCode.MethodNotAllowed.UriString; + + /// + public string? Title => Dgmjr.Http.StatusCode.MethodNotAllowed.Name; + + /// + public int? Status => Dgmjr.Http.StatusCode.MethodNotAllowed.Id; + + /// + public string? Detail => Dgmjr.Http.StatusCode.MethodNotAllowed.Description; + + public string? Instance => "/api/v1/endpoint"; + + public IDictionary Extensions => + new Dictionary + { + { "traceId", Guid.NewGuid().ToString() }, + { "errors", new[] { "Method not allowed" } }, + { "method", "GET" } + }; +} diff --git a/src/Mvc/ProducesErrorAttributes/Produces405ErrorAttribute.cs b/src/Mvc/ProducesErrorAttributes/Produces405ErrorAttribute.cs new file mode 100644 index 00000000..f195fae9 --- /dev/null +++ b/src/Mvc/ProducesErrorAttributes/Produces405ErrorAttribute.cs @@ -0,0 +1,29 @@ +/* + * Attributes.cs + * + * Created: 2023-01-04-04:14:24 + * Modified: 2023-01-04-04:14:24 + * + * Author: David G. Moore, Jr, + * + * Copyright © 2022-2023 David G. Moore, Jr,, All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.Mvc; + +using Swashbuckle.AspNetCore.Annotations; + +using static Microsoft.AspNetCore.Http.StatusCodes; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +public sealed class Produces405ErrorAttribute : SwaggerResponseAttribute +{ + public Produces405ErrorAttribute() + : base( + Status404NotFound, + Http.StatusCode.MethodNotAllowed.Description, + typeof(MethodNotAllowedProblemDetails), + ApplicationMediaTypeNames.ProblemJson + ) { } +} diff --git a/src/Mvc/Result.cs b/src/Mvc/Result.cs index ab017492..e71a7f68 100644 --- a/src/Mvc/Result.cs +++ b/src/Mvc/Result.cs @@ -12,12 +12,14 @@ namespace Dgmjr.AspNetCore.Mvc; +using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using OneOf; -public class Result : OneOfBase, IConvertToActionResult +public class Result : OneOfBase, IConvertToActionResult, IActionResult { public Result(T value, string contentType) : base(value is string ? new ContentResult { Content = value as string, ContentType = contentType, StatusCode = Status200OK } : @@ -26,13 +28,16 @@ public Result(T value, string contentType) protected Result(ContentResult contentResult) : base(contentResult) { } protected Result(ObjectResult contentResult) : base(contentResult) { } - public IActionResult Convert() => IsT0 ? AsT0 : AsT1; + public IActionResult Convert() => this; + + public Task ExecuteResultAsync(ActionContext context) + => IsT0 ? AsT0.ExecuteResultAsync(context) : AsT1.ExecuteResultAsync(context); public static implicit operator Result(ContentResult contentResult) => new(contentResult); public static implicit operator Result(ObjectResult objectResult) => new(objectResult); - public static implicit operator ActionResult(Result result) => result.Convert() as ActionResult; + public static implicit operator ActionResult?(Result result) => result.Convert() as ActionResult; } public class Result : Result diff --git a/src/Mvc/ViewControllerBase.cs b/src/Mvc/ViewControllerBase.cs index 4f4fac32..bb6c443b 100644 --- a/src/Mvc/ViewControllerBase.cs +++ b/src/Mvc/ViewControllerBase.cs @@ -12,10 +12,17 @@ namespace Dgmjr.AspNetCore.Mvc; +using Dgmjr.Abstractions; + using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.IdentityModel.LoggingExtensions; -public class ViewControllerBase : Controller +public class ViewControllerBase(ILogger? logger = null) : Controller, ILog { + public ILogger Logger => logger ?? new NullLogger(); + public IActionResult Result(T value, string contentType) => ControllerExtensions.Result(this, value, contentType); } diff --git a/src/Mvc/icon.png b/src/Mvc/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Mvc/icon.png differ diff --git a/src/Payloads/Dgmjr.Payloads.csproj b/src/Payloads/Dgmjr.Payloads.csproj index 9cfac569..95a76db3 100644 --- a/src/Payloads/Dgmjr.Payloads.csproj +++ b/src/Payloads/Dgmjr.Payloads.csproj @@ -32,7 +32,7 @@ - + @@ -42,10 +42,9 @@ - - - - + + + diff --git a/src/Payloads/Dgmjr.Payloads.sln b/src/Payloads/Dgmjr.Payloads.sln index 5281fb83..da6f7f23 100644 --- a/src/Payloads/Dgmjr.Payloads.sln +++ b/src/Payloads/Dgmjr.Payloads.sln @@ -8,11 +8,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\Packages\Versions.Local.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Http", "..\Http\Http\Dgmjr.Http.csproj", "{D1A009EA-0515-40A5-AA33-998BBAA834C4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Http", "..\Http\Http\Dgmjr.Http.csproj", "{678270F3-44C0-4FF8-A2B5-C7DC4601873C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Mime", "..\Http\Mime\Dgmjr.Mime.csproj", "{3CA89151-E9ED-4CDA-82BB-4220797DA789}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Mime", "..\Http\Mime\Dgmjr.Mime.csproj", "{5569BA54-7A68-4A85-BEAE-FB64F67D8D30}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Payloads", "Dgmjr.Payloads.csproj", "{F52A6B01-77E4-4118-B9C4-CC538B2CEED4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.Mvc.Polyfills", "..\Mvc.Polyfills\Dgmjr.AspNetCore.Mvc.Polyfills.csproj", "{0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Payloads", "Dgmjr.Payloads.csproj", "{B1F2E372-E233-4177-AE6D-1307929E365F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -24,42 +26,54 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Local|Any CPU.ActiveCfg = Local|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Local|Any CPU.Build.0 = Local|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Testing|Any CPU.Build.0 = Testing|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Staging|Any CPU.Build.0 = Staging|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Production|Any CPU.ActiveCfg = Local|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Production|Any CPU.Build.0 = Local|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1A009EA-0515-40A5-AA33-998BBAA834C4}.Release|Any CPU.Build.0 = Release|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Local|Any CPU.ActiveCfg = Local|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Local|Any CPU.Build.0 = Local|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Testing|Any CPU.Build.0 = Testing|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Staging|Any CPU.Build.0 = Staging|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Production|Any CPU.ActiveCfg = Local|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Production|Any CPU.Build.0 = Local|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CA89151-E9ED-4CDA-82BB-4220797DA789}.Release|Any CPU.Build.0 = Release|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Local|Any CPU.ActiveCfg = Local|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Local|Any CPU.Build.0 = Local|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Testing|Any CPU.Build.0 = Testing|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Staging|Any CPU.Build.0 = Staging|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Production|Any CPU.ActiveCfg = Local|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Production|Any CPU.Build.0 = Local|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F52A6B01-77E4-4118-B9C4-CC538B2CEED4}.Release|Any CPU.Build.0 = Release|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Local|Any CPU.ActiveCfg = Local|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Local|Any CPU.Build.0 = Local|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Testing|Any CPU.Build.0 = Testing|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Staging|Any CPU.Build.0 = Staging|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Production|Any CPU.ActiveCfg = Local|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Production|Any CPU.Build.0 = Local|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {678270F3-44C0-4FF8-A2B5-C7DC4601873C}.Release|Any CPU.Build.0 = Release|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Local|Any CPU.ActiveCfg = Local|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Local|Any CPU.Build.0 = Local|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Testing|Any CPU.Build.0 = Testing|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Staging|Any CPU.Build.0 = Staging|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Production|Any CPU.ActiveCfg = Local|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Production|Any CPU.Build.0 = Local|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5569BA54-7A68-4A85-BEAE-FB64F67D8D30}.Release|Any CPU.Build.0 = Release|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Local|Any CPU.ActiveCfg = Local|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Local|Any CPU.Build.0 = Local|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Testing|Any CPU.Build.0 = Testing|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Staging|Any CPU.Build.0 = Staging|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Production|Any CPU.ActiveCfg = Local|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Production|Any CPU.Build.0 = Local|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EDA37A7-E1A8-4B7B-86D9-DBC99E44FEC5}.Release|Any CPU.Build.0 = Release|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Local|Any CPU.ActiveCfg = Local|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Local|Any CPU.Build.0 = Local|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Testing|Any CPU.Build.0 = Testing|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Staging|Any CPU.Build.0 = Staging|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Production|Any CPU.ActiveCfg = Local|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Production|Any CPU.Build.0 = Local|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1F2E372-E233-4177-AE6D-1307929E365F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Payloads/RangeRequest.cs b/src/Payloads/RangeRequest.cs index 415ee75a..071d0604 100644 --- a/src/Payloads/RangeRequest.cs +++ b/src/Payloads/RangeRequest.cs @@ -30,7 +30,7 @@ namespace Dgmjr.Payloads /// records public const string Records = "records"; - public const string Description = "Requested range of values to return"; + public const string DescriptionString = "Requested range of values to return"; public const string ExampleString = "items 0-10"; public const string EmptyString = "items 0-0"; public const string UriPrefix = "https://dgmjr.io/range"; @@ -60,13 +60,14 @@ namespace Dgmjr.Payloads public static Regex Regex() => _regex; #endif -#if !NET6_0_OR_GREATER - Regex IRegexValueObject.Regex() => Regex(); + // #if !NET6_0_OR_GREATER + // public Regex Regex() => Regex(); - string IRegexValueObject.RegexString => RegexString; - string IRegexValueObject.Description => Description; - Range IRegexValueObject.ExampleValue => From(ExampleString); -#endif + // public string RegexString => RegexString; + public string Description => DescriptionString; + public Range ExampleValue => From(ExampleString); + + // #endif Uri IHaveAUri.Uri => Uri; /// 2147483647 @@ -159,15 +160,14 @@ public static Range From(int pageNumber, int pageSize = int.MaxValue) } public readonly string OriginalString { get; init; } = string.Empty; - readonly string IRegexValueObject.Value => $"{Units} {Start}-{End}"; - readonly bool IRegexValueObject.IsEmpty => - Value.Start.Value == 0 && Value.End.Value == 0; + public readonly string StringValue => $"{Units} {Start}-{End}"; + readonly bool IsEmpty => Value.Start.Value == 0 && Value.End.Value == 0; #if NET6_0_OR_GREATER - static string IRegexValueObject.RegexString => RegexString; - static string IRegexValueObject.Description => Description; - static Range IRegexValueObject.ExampleValue => From(ExampleString); - static Range IRegexValueObject.Empty => From(EmptyString); + // static string RegexString => RegexString; + // static string Description => Description; + // static Range ExampleValue => From(ExampleString); + // static Range Empty => From(EmptyString); #endif public int CompareTo(Range other) => Value.Start.Value.CompareTo(other.Value.Start.Value); diff --git a/src/Payloads/System.Range.cs b/src/Payloads/System.Range.cs index 690cb35d..0a104292 100644 --- a/src/Payloads/System.Range.cs +++ b/src/Payloads/System.Range.cs @@ -47,11 +47,11 @@ public override bool Equals(object? value) => /// Returns the hash code for this instance. public override int GetHashCode() { -#if !NETSTANDARD2_0 && !NETFRAMEWORK - return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); -#else + // #if !NETSTANDARD2_0 && !NETFRAMEWORK return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); -#endif + // #else + // return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); + // #endif } /// Converts the value of the current Range object to its equivalent string representation. diff --git a/src/Payloads/icon.png b/src/Payloads/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Payloads/icon.png differ diff --git a/src/Security/Authentication/LICENSE.md b/src/Security/Authentication/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Security/Authentication/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Security/Authentication/icon.png b/src/Security/Authentication/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Security/Authentication/icon.png differ diff --git a/src/Security/Authorization/icon.png b/src/Security/Authorization/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Security/Authorization/icon.png differ diff --git a/src/Startup/LICENSE.md b/src/Startup/LICENSE.md new file mode 100644 index 00000000..4f592f86 --- /dev/null +++ b/src/Startup/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00-05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2024 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/Startup/icon.png b/src/Startup/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Startup/icon.png differ diff --git a/src/Swagger/AddSwaggerGen.cs b/src/Swagger/AddSwaggerGen.cs index f59afee0..59b66c8a 100644 --- a/src/Swagger/AddSwaggerGen.cs +++ b/src/Swagger/AddSwaggerGen.cs @@ -24,14 +24,30 @@ public static partial class SwaggerExtensions const string SwaggerUI = nameof(SwaggerUI); - public static IHostApplicationBuilder AddSwaggerGen(this IHostApplicationBuilder builder, string configurationSectionKey = Swagger, Action? configure = default) + public static IHostApplicationBuilder AddSwaggerGen( + this IHostApplicationBuilder builder, + string configurationSectionKey = Swagger, + Action? configure = default + ) { builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { + var schemaNames = new HashSet(); builder.Configuration.Bind(configurationSectionKey, c); - // c.CustomSchemaIds(type => type.FullName ?? guid.NewGuid().ToString()/*type.GetDisplayName().Contains("$") ? type.FullName : $"{type.Namespace}.{type.GetDisplayName()}"*/); + c.CustomSchemaIds(type => + { + var schemaName = type.IsGenericParameter + ? guid.NewGuid().ToString() + : $"{type.Namespace}.{type.GetDisplayName()}"; + if (!schemaNames.Add(schemaName)) + { + schemaName += guid.NewGuid().ToString(); + schemaNames.Add(schemaName); + } + return schemaName; + }); c.AddAuthorizeSummary(); c.DocumentFilter(); }); @@ -51,7 +67,10 @@ public static IHostApplicationBuilder AddSwaggerGen(this IHostApplicationBuilder return builder; } - public static IApplicationBuilder UseCustomSwaggerUI(this IApplicationBuilder app, string configurationSectionKey = SwaggerUI) + public static IApplicationBuilder UseCustomSwaggerUI( + this IApplicationBuilder app, + string configurationSectionKey = SwaggerUI + ) { app.InjectUICustomizations(); app.UseSwagger(); diff --git a/src/Swagger/icon.png b/src/Swagger/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/Swagger/icon.png differ diff --git a/src/Swagger/petstore.json b/src/Swagger/petstore.json deleted file mode 100644 index 8571c0b0..00000000 --- a/src/Swagger/petstore.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "Swagger Petstore", - "license": { - "name": "MIT" - } - }, - "servers": [ - { - "url": "http://petstore.swagger.io/v1" - } - ], - "paths": { - "/pets": { - "get": { - "summary": "List all pets", - "operationId": "listPets", - "tags": [ - "pets" - ], - "parameters": [ - { - "name": "limit", - "in": "query", - "description": "How many items to return at one time (max 100)", - "required": false, - "schema": { - "type": "integer", - "maximum": 100, - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "A paged array of pets", - "headers": { - "x-next": { - "description": "A link to the next page of responses", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Pets" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - }, - "post": { - "summary": "Create a pet", - "operationId": "createPets", - "tags": [ - "pets" - ], - "responses": { - "201": { - "description": "Null response" - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/pets/{petId}": { - "get": { - "summary": "Info for a specific pet", - "operationId": "showPetById", - "tags": [ - "pets" - ], - "parameters": [ - { - "name": "petId", - "in": "path", - "required": true, - "description": "The id of the pet to retrieve", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Expected response to a valid request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Pet" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Pet": { - "type": "object", - "required": [ - "id", - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } - } - }, - "Pets": { - "type": "array", - "maxItems": 100, - "items": { - "$ref": "#/components/schemas/Pet" - } - }, - "Error": { - "type": "object", - "required": [ - "code", - "message" - ], - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } - } - } - } - } -} \ No newline at end of file diff --git a/src/TagHelpers/.vscode/settings.json b/src/TagHelpers/.vscode/settings.json new file mode 100644 index 00000000..fd02d624 --- /dev/null +++ b/src/TagHelpers/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.words": [ + "ihaaaca", + "ihagiaiis" + ] +} diff --git a/src/TagHelpers/Abstractions/HaveAnActionContextAccessor.cs b/src/TagHelpers/Abstractions/HaveAnActionContextAccessor.cs new file mode 100644 index 00000000..e2487a6a --- /dev/null +++ b/src/TagHelpers/Abstractions/HaveAnActionContextAccessor.cs @@ -0,0 +1,18 @@ +/* + * HaveAnActionContextAccessor.cs + * + * Created: 2024-59-16T00:59:45-05:00 + * Modified: 2024-59-16T00:59:45-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Abstractions; + +public interface IHaveAnActionContextAccessor +{ + IActionContextAccessor ActionContextAccessor { get; } +} diff --git a/src/TagHelpers/Abstractions/IHaveAGeneratedId.cs b/src/TagHelpers/Abstractions/IHaveAGeneratedId.cs new file mode 100644 index 00000000..54b453c4 --- /dev/null +++ b/src/TagHelpers/Abstractions/IHaveAGeneratedId.cs @@ -0,0 +1,18 @@ +/* + * IHaveAGeneratedId.cs + * + * Created: 2024-17-16T00:17:11-05:00 + * Modified: 2024-17-16T00:17:12-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Abstractions; + +public interface IHaveAGeneratedId +{ + string GeneratedId { get; set; } +} diff --git a/src/TagHelpers/Attributes/ColorInfo.cs b/src/TagHelpers/Attributes/ColorInfo.cs new file mode 100644 index 00000000..2a4eeeed --- /dev/null +++ b/src/TagHelpers/Attributes/ColorInfo.cs @@ -0,0 +1,28 @@ +/* + * ColorInfo.cs + * + * Created: 2024-59-15T17:59:25-05:00 + * Modified: 2024-59-15T17:59:26-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Attributes; + +[AttributeUsage(AttributeTargets.Field)] +public class ColorInfoAttribute : EnumInfoAttribute +{ + public string TextCssClass { get; set; } + + public string BackgroundCssClass { get; set; } + + public string BorderCssClass { get; set; } + + public ColorInfoAttribute(string name) + : base(name) + { + } +} diff --git a/src/TagHelpers/Attributes/ContextAttribute.cs b/src/TagHelpers/Attributes/ContextAttribute.cs new file mode 100644 index 00000000..a85d425c --- /dev/null +++ b/src/TagHelpers/Attributes/ContextAttribute.cs @@ -0,0 +1,30 @@ +/* + * ContextAttribute.cs + * + * Created: 2024-41-15T17:41:41-05:00 + * Modified: 2024-41-15T17:41:42-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class ContextAttribute : Attribute +{ + public bool UseInherited { get; set; } = true; + + public bool RemoveContext { get; set; } + + public string Key { get; set; } + + public ContextAttribute() { } + + public ContextAttribute(string key) + { + Key = key; + } +} diff --git a/src/TagHelpers/Attributes/ContextClassAttribute.cs b/src/TagHelpers/Attributes/ContextClassAttribute.cs new file mode 100644 index 00000000..17f30778 --- /dev/null +++ b/src/TagHelpers/Attributes/ContextClassAttribute.cs @@ -0,0 +1,35 @@ +/* + * ContextClassAttribute.cs + * + * Created: 2024-43-15T16:43:50-05:00 + * Modified: 2024-59-15T16:59:05-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Attributes; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class ContextClassAttribute : Attribute +{ + public string Key { get; set; } + + public Type Type { get; set; } + + public ContextClassAttribute(type type) + { + Type = type; + } + + public ContextClassAttribute() + { + } + + public ContextClassAttribute(string key) + { + Key = key; + } +} diff --git a/src/TagHelpers/Attributes/ConvertVirtualUrlAttribute.cs b/src/TagHelpers/Attributes/ConvertVirtualUrlAttribute.cs new file mode 100644 index 00000000..cc4e7cd7 --- /dev/null +++ b/src/TagHelpers/Attributes/ConvertVirtualUrlAttribute.cs @@ -0,0 +1,18 @@ +/* + * ConvertVirtualUrlAttribute.cs + * + * Created: 2024-28-15T17:28:59-05:00 + * Modified: 2024-28-15T17:28:59-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class ConvertVirtualUrlAttribute : Attribute +{ +} diff --git a/src/TagHelpers/Attributes/CopyToOutputAttribute.cs b/src/TagHelpers/Attributes/CopyToOutputAttribute.cs new file mode 100644 index 00000000..a8a1e6f9 --- /dev/null +++ b/src/TagHelpers/Attributes/CopyToOutputAttribute.cs @@ -0,0 +1,73 @@ +/* + * CopyToOutputAttribute.cs + * + * Created: 2024-40-15T16:40:24-05:00 + * Modified: 2024-58-15T16:58:59-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers; + +[AttributeUsage(AttributeTargets.Property)] +public class CopyToOutputAttribute : Attribute +{ + public string Prefix { get; set; } + + public string Suffix { get; set; } + + public string OutputAttributeName { get; set; } + + public bool CopyIfValueIsNull { get; set; } + + public CopyToOutputAttribute() + { + } + + public CopyToOutputAttribute(bool copyIfValueIsNull) + { + CopyIfValueIsNull = copyIfValueIsNull; + } + + public CopyToOutputAttribute(string outputAttributeName) + { + OutputAttributeName = outputAttributeName; + } + + public CopyToOutputAttribute(bool copyIfValueIsNull, string outputAttributeName) + { + OutputAttributeName = outputAttributeName; + CopyIfValueIsNull = copyIfValueIsNull; + } + + public CopyToOutputAttribute(string prefix, string suffix) + { + Prefix = prefix; + Suffix = suffix; + } + + public CopyToOutputAttribute(bool copyIfValueIsNull, string prefix, string suffix) + { + Prefix = prefix; + Suffix = suffix; + CopyIfValueIsNull = copyIfValueIsNull; + } + + public CopyToOutputAttribute(bool copyIfValueIsNull, string outputAttributeName, string prefix, string suffix) + { + Prefix = prefix; + Suffix = suffix; + OutputAttributeName = outputAttributeName; + CopyIfValueIsNull = copyIfValueIsNull; + } + + public CopyToOutputAttribute(string outputAttributeName, string prefix, string suffix) + { + Prefix = prefix; + Suffix = suffix; + OutputAttributeName = outputAttributeName; + } +} diff --git a/src/TagHelpers/Attributes/EnumInfo.cs b/src/TagHelpers/Attributes/EnumInfo.cs new file mode 100644 index 00000000..890a7509 --- /dev/null +++ b/src/TagHelpers/Attributes/EnumInfo.cs @@ -0,0 +1,24 @@ +/* + * EnumInfo.cs + * + * Created: 2024-54-15T17:54:01-05:00 + * Modified: 2024-54-15T17:54:01-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Attributes; + +[AttributeUsage(AttributeTargets.Field)] +public class EnumInfoAttribute : Attribute +{ + public string Name { get; set; } + + public EnumInfoAttribute(string name) + { + Name = name; + } +} diff --git a/src/TagHelpers/Attributes/GenerateIdAttribute.cs b/src/TagHelpers/Attributes/GenerateIdAttribute.cs new file mode 100644 index 00000000..cabc9f58 --- /dev/null +++ b/src/TagHelpers/Attributes/GenerateIdAttribute.cs @@ -0,0 +1,25 @@ +/* + * GenerateIdAttribute.cs + * + * Created: 2024-49-15T16:49:03-05:00 + * Modified: 2024-59-15T16:59:25-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class GenerateIdAttribute(string prefix, bool renderIdAttribute = true) : Attribute +{ + private string _id; + + public string Prefix { get; set; } = prefix; + + public bool RenderIdAttribute { get; set; } = renderIdAttribute; + + public string Id => _id ??= Prefix + guid.NewGuid().ToString("N"); +} diff --git a/src/TagHelpers/Attributes/HtmlAttributeMinimizable.cs b/src/TagHelpers/Attributes/HtmlAttributeMinimizable.cs new file mode 100644 index 00000000..bb7fb000 --- /dev/null +++ b/src/TagHelpers/Attributes/HtmlAttributeMinimizable.cs @@ -0,0 +1,18 @@ +/* + * HtmlAttributeMinimizable.cs + * + * Created: 2024-48-15T16:48:11-05:00 + * Modified: 2024-59-15T16:59:29-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class HtmlAttributeMinimizableAttribute : Attribute +{ +} diff --git a/src/TagHelpers/Attributes/MandatoryAttribute.cs b/src/TagHelpers/Attributes/MandatoryAttribute.cs new file mode 100644 index 00000000..2a36c8d4 --- /dev/null +++ b/src/TagHelpers/Attributes/MandatoryAttribute.cs @@ -0,0 +1,19 @@ +/* + * MandatoryAttribute.cs + * + * Created: 2024-40-15T16:40:24-05:00 + * Modified: 2024-59-15T16:59:33-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Attributes; + +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +public class MandatoryAttribute : Attribute +{ + +} diff --git a/src/TagHelpers/Authorization/AuthorizeResourceTagHelper.cs b/src/TagHelpers/Authorization/AuthorizeResourceTagHelper.cs new file mode 100644 index 00000000..0a5d4bfd --- /dev/null +++ b/src/TagHelpers/Authorization/AuthorizeResourceTagHelper.cs @@ -0,0 +1,85 @@ +/* + * AuthorizeResourceTagHelper.cs + * + * Created: 2024-40-15T16:40:24-05:00 + * Modified: 2024-59-15T16:59:51-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Authorization; + +[HtmlTargetElement(Attributes = "asp-authorize-resource,asp-policy")] +[HtmlTargetElement(Attributes = "asp-authorize-resource,asp-requirement")] +public class AuthorizeResourceTagHelper( + IHttpContextAccessor httpContextAccessor, + IAuthorizationService authorizationService +) : TagHelper +{ + /// + /// Gets or sets the policy name that determines access to the HTML block. + /// + [HtmlAttributeName("asp-policy")] + public string Policy { get; set; } + + /// + /// Gets or sets a comma delimited list of roles that are allowed to access the HTML block. + /// + [HtmlAttributeName("asp-requirement")] + public IAuthorizationRequirement Requirement { get; set; } + + /// + /// Gets or sets the resource to be authorized against a particular policy + /// + [HtmlAttributeName("asp-authorize-resource")] + public object Resource { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (Resource == null) + { + throw new ArgumentException("Resource cannot be null"); + } + if (IsNullOrWhiteSpace(Policy) && Requirement == null) + { + throw new ArgumentException("Either Policy or Requirement must be specified"); + } + if (!IsNullOrWhiteSpace(Policy) && Requirement != null) + { + throw new ArgumentException( + "Policy and Requirement cannot be specified at the same time" + ); + } + + AuthorizationResult authorizeResult; + + if (!IsNullOrWhiteSpace(Policy)) + { + authorizeResult = await authorizationService.AuthorizeAsync( + httpContextAccessor.HttpContext.User, + Resource, + Policy + ); + } + else if (Requirement != null) + { + authorizeResult = await authorizationService.AuthorizeAsync( + httpContextAccessor.HttpContext.User, + Resource, + Requirement + ); + } + else + { + throw new ArgumentException("Either Policy or Requirement must be specified"); + } + + if (!authorizeResult.Succeeded) + { + output.SuppressOutput(); + } + } +} diff --git a/src/TagHelpers/Authorization/AuthorizeTagHelper.cs b/src/TagHelpers/Authorization/AuthorizeTagHelper.cs new file mode 100644 index 00000000..989c73f2 --- /dev/null +++ b/src/TagHelpers/Authorization/AuthorizeTagHelper.cs @@ -0,0 +1,56 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Authorization; +using AuthorizationPolicy = Microsoft.AspNetCore.Authorization.AuthorizationPolicy; + +[HtmlTargetElement(Attributes = "asp-authorize")] +[HtmlTargetElement(Attributes = "asp-authorize,asp-policy")] +[HtmlTargetElement(Attributes = "asp-authorize,asp-roles")] +[HtmlTargetElement(Attributes = "asp-authorize,asp-authentication-schemes")] +public class AuthorizeTagHelper( + IHttpContextAccessor httpContextAccessor, + IAuthorizationPolicyProvider policyProvider, + IPolicyEvaluator policyEvaluator + ) : TagHelper, IAuthorizeData +{ + /// + /// Gets or sets the policy name that determines access to the HTML block. + /// + [HtmlAttributeName("asp-policy")] + public string Policy { get; set; } + + /// + /// Gets or sets a comma delimited list of roles that are allowed to access the HTML block. + /// + [HtmlAttributeName("asp-roles")] + public string Roles { get; set; } + + /// + /// Gets or sets a comma delimited list of schemes from which user information is constructed. + /// + [HtmlAttributeName("asp-authentication-schemes")] + public string AuthenticationSchemes { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var policy = await AuthorizationPolicy.CombineAsync(policyProvider, new[] { this }); + + var authenticateResult = await policyEvaluator.AuthenticateAsync( + policy, + httpContextAccessor.HttpContext + ); + + var authorizeResult = await policyEvaluator.AuthorizeAsync( + policy, + authenticateResult, + httpContextAccessor.HttpContext, + null + ); + + if (!authorizeResult.Succeeded) + { + output.SuppressOutput(); + } + + if (output.Attributes.TryGetAttribute("asp-authorize", out TagHelperAttribute attribute)) + output.Attributes.Remove(attribute); + } +} diff --git a/src/TagHelpers/Bootstrap/Accordion/Accordion.cs b/src/TagHelpers/Bootstrap/Accordion/Accordion.cs new file mode 100644 index 00000000..c45b83e2 --- /dev/null +++ b/src/TagHelpers/Bootstrap/Accordion/Accordion.cs @@ -0,0 +1,40 @@ +/* + * Accordion.cs + * + * Created: 2024-08-20T01:08:23-05:00 + * Modified: 2024-08-20T01:08:23-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Accordion; + +public class Accordion : TagHelper +{ + public override void Process(TagHelperContext context, TagHelperOutput output) + { + output.TagName = TagNames.Div; + output.AddCssClass(CssClasses.Accordion); + if (Color != Color.None) + { + output.AddCssClass($"{CssClasses.TextBackground_}{Color.ToString().ToLower()}"); + } + if (BorderColor != Color.None) + { + output.AddCssClass($"{CssClasses.Border_}{BorderColor.ToString().ToLower()}"); + } + output.Attributes.Add("id", Id); + } + + [HtmlAttributeName(AttributeNames.Color)] + public Color Color { get; set; } = Color.None; + + [HtmlAttributeName(AttributeNames.BorderColor)] + public Color BorderColor { get; set; } = Color.None; + + [HtmlAttributeName("id")] + public string Id { get; set; } = guid.NewGuid().ToString(); +} diff --git a/src/TagHelpers/Bootstrap/Accordion/AccordionItem.cs b/src/TagHelpers/Bootstrap/Accordion/AccordionItem.cs new file mode 100644 index 00000000..1e41ef0c --- /dev/null +++ b/src/TagHelpers/Bootstrap/Accordion/AccordionItem.cs @@ -0,0 +1,137 @@ +/* + * AccordionItem.cs + * + * Created: 2024-10-20T01:10:25-05:00 + * Modified: 2024-10-20T01:10:25-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +using System.Security.AccessControl; + +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Accordion; + +[HtmlTargetElement(TagNames.AccordionItem, ParentTag = TagNames.Accordion)] +public class AccordionItem : TagHelper, IIdentifiable +{ + [HtmlAttributeName(AttributeNames.Header)] + public string Header { get; set; } = string.Empty; + + [HtmlAttributeName(AttributeNames.HeadingType)] + public HeadingType HeaderHeadingType { get; set; } = HeadingType.H2; + + private string HeaderHeadingTagName => HeaderHeadingType.ToString().ToLower(); + + [HtmlAttributeName(AttributeNames.Id)] + public string Id { get; set; } = $"_{guid.NewGuid().ToString()[0..8]}"; + + [HtmlAttributeName(AttributeNames.IsExpanded)] + public bool IsExpanded { get; set; } = false; + + [HtmlAttributeName(AttributeNames.HeaderColor)] + public Color HeaderColor { get; set; } = Color.None; + + [HtmlAttributeName(AttributeNames.HeaderBorderColor)] + public Color HeaderBorderColor { get; set; } = Color.None; + + [HtmlAttributeName(AttributeNames.Color)] + public Color Color { get; set; } = Color.None; + + [HtmlAttributeName(AttributeNames.BorderColor)] + public Color BorderColor { get; set; } = Color.None; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = ""; + output.AddCssClass(CssClasses.AccordionItem); + + var accordionItem = new TagBuilder(TagNames.Div); + accordionItem.AddCssClass(CssClasses.AccordionItem); + + var accordionItemHeader = new TagBuilder(HeaderHeadingTagName); + accordionItemHeader.AddCssClass(CssClasses.AccordionHeader); + if (HeaderColor != Color.None) + { + accordionItemHeader.AddCssClass( + $"{CssClasses.TextBackground_}{HeaderColor.ToString().ToLower()}" + ); + } + if (HeaderBorderColor != Color.None) + { + accordionItemHeader.AddCssClass( + $"{CssClasses.Border_}{HeaderBorderColor.ToString().ToLower()}" + ); + } + + var accordionItemButton = new TagBuilder(TagNames.Button); + accordionItemButton.AddCssClass(CssClasses.AccordionButton); + if (!IsExpanded) + { + accordionItemButton.AddCssClass(CssClasses.Collapsed); + } + accordionItemButton.Attributes.Add("type", TagNames.Button); + accordionItemButton.Attributes.Add("data-bs-toggle", CssClasses.Collapse); + accordionItemButton.Attributes.Add("data-bs-target", $"#{Id}"); + accordionItemButton.Attributes.Add("aria-expanded", IsExpanded.ToString().ToLower()); + accordionItemButton.Attributes.Add("aria-controls", Id); + accordionItemButton.InnerHtml.Append(Header); + + accordionItemHeader.InnerHtml.AppendHtml(accordionItemButton); + + var accordionItemCollapse = new TagBuilder(TagNames.Div); + accordionItemCollapse.AddCssClass(CssClasses.AccordionCollapse); + accordionItemCollapse.AddCssClass(CssClasses.Collapse); + if (IsExpanded) + { + accordionItemCollapse.AddCssClass(CssClasses.Show); + } + if (Color != Color.None) + { + accordionItemCollapse.AddCssClass( + $"{CssClasses.TextBackground_}{HeaderColor.ToString().ToLower()}" + ); + } + if (BorderColor != Color.None) + { + accordionItemCollapse.AddCssClass( + $"{CssClasses.Border_}{HeaderBorderColor.ToString().ToLower()}" + ); + } + accordionItemCollapse.Attributes.Add("id", Id); + accordionItemCollapse.Attributes.Add("aria-labelledby", Id); + accordionItemCollapse.Attributes.Add("data-bs-parent", $"#{Id}"); + + var accordionItemBody = new TagBuilder(TagNames.Div); + accordionItemBody.AddCssClass(CssClasses.AccordionBody); + var content = await output.GetChildContentAsync(); + accordionItemBody.InnerHtml.AppendHtml(content); + + accordionItemCollapse.InnerHtml.AppendHtml(accordionItemBody); + + accordionItem.InnerHtml.AppendHtml(accordionItemHeader); + + accordionItem.InnerHtml.AppendHtml(accordionItemCollapse); + + output.Content.SetHtmlContent(accordionItem); + + // return Task.CompletedTask; + + // output.PreContent.AppendHtml( + // $""" + // <{HeaderHeadingType} class="{CssClasses.AccordionHeader}"> + // + // + //
+ //
+ // """ + // ); + // output.PostContent.AppendHtml("
"); + + // return Task.CompletedTask; + } +} diff --git a/src/TagHelpers/Bootstrap/ActiveAnchorTagHelper.cs b/src/TagHelpers/Bootstrap/ActiveAnchorTagHelper.cs new file mode 100644 index 00000000..b8b369e5 --- /dev/null +++ b/src/TagHelpers/Bootstrap/ActiveAnchorTagHelper.cs @@ -0,0 +1,78 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.Anchor)] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-action")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-controller")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-area")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-page")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-page-handler")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-fragment")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-host")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-protocol")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-route")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-all-route-data")] +[HtmlTargetElement(TagNames.Anchor, Attributes = "asp-route-*")] +public class ActiveAnchorTagHelper(IHtmlGenerator generator) + : Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper(generator) +{ + private bool? _isActive; + public bool IsActive + { + get => _isActive ?? ShouldBeActive(); + set => _isActive = value; + } + + public override int Order => int.MaxValue; + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + // base.Process(context, output); + + if (IsActive) + { + var activeClass = output.Attributes.FirstOrDefault(a => a.Name == "class"); + if (activeClass != null) + { + output.Attributes.SetAttribute( + AttributeNames.Class, + activeClass.Value + " " + CssClasses.Active + ); + } + else + { + output.Attributes.SetAttribute(AttributeNames.Class, CssClasses.Active); + } + } + } + + private bool ShouldBeActive() + { + var currentController = ViewContext.RouteData.Values["Controller"]?.ToString(); + var currentAction = ViewContext.RouteData.Values["Action"]?.ToString(); + + if ( + !IsNullOrWhiteSpace(Controller) && Controller?.ToLower() != currentController?.ToLower() + ) + { + return false; + } + + if (!IsNullOrWhiteSpace(Action) && Action?.ToLower() != currentAction?.ToLower()) + { + return false; + } + + foreach (var routeValue in RouteValues) + { + if ( + !ViewContext.RouteData.Values.ContainsKey(routeValue.Key) + || ViewContext.RouteData.Values[routeValue.Key].ToString() != routeValue.Value + ) + { + return false; + } + } + + return true; + } +} diff --git a/src/TagHelpers/Bootstrap/AlertTagHelper.cs b/src/TagHelpers/Bootstrap/AlertTagHelper.cs new file mode 100644 index 00000000..d6931963 --- /dev/null +++ b/src/TagHelpers/Bootstrap/AlertTagHelper.cs @@ -0,0 +1,176 @@ +/* +This TH was written by Rick Strahl + +Usage: + + + +Add font-awesome + + +*/ + +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +/// +/// TagHelper to display a BootStrap Alert box and FontAwesome icon. +/// +/// Message and Header values can be assigned from model values using +/// standard Razor expressions. +/// +/// +/// The Helper only displays content when message or header are set +/// otherwise the content is not displayed, so when binding to your +/// model and the model value is empty nothing displays. +/// +/// +/// +/// Requires FontAwesome in addition to bootstrap in order to display icons +/// +[HtmlTargetElement("alert")] +public class AlertTagHelper : TagHelper +{ + /// + /// the main message that gets displayed + /// + [HtmlAttributeName("message")] + public string Message { get; set; } + + /// + /// Optional header that is displayed in big text. Use for + /// 'noisy' warnings and stop errors only please :-) + /// The message is displayed below the header. + /// + [HtmlAttributeName("header")] + public string Header { get; set; } + + /// + /// Font-awesome icon name without the fa- prefix. + /// Example: info, warning, lightbulb-o, + /// If none is specified - "warning" is used + /// To force no icon use "none" + /// + [HtmlAttributeName("icon")] + public string Icon { get; set; } + + /// + /// CSS class. Handled here so we can capture the existing + /// class value and append the BootStrap alert class. + /// + [HtmlAttributeName("class")] + public string CssClass { get; set; } + + /// + /// Optional - specifies the alert class used on the top level + /// window. If not specified uses the same as the icon. + /// Override this if the icon and alert classes are different + /// (often they are not). + /// + [HtmlAttributeName("alert-class")] + public string AlertClass { get; set; } + + /// + /// If true embeds the message text as HTML. Use this + /// flag if you need to display HTML text. If false + /// the text is HtmlEncoded. + /// + [HtmlAttributeName("message-as-html")] + public bool MessageAsHtml { get; set; } + + /// + /// If true embeds the header text as HTML. Use this + /// flag if you need to display raw HTML text. If false + /// the text is HtmlEncoded. + /// + [HtmlAttributeName("header-as-html")] + public bool HeaderAsHtml { get; set; } + + /// + /// If true displays a close icon to close the alert. + /// + [HtmlAttributeName("dismissible")] + public bool IsDismissible { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (IsNullOrEmpty(Message) && IsNullOrEmpty(Header)) + return; + + if (IsNullOrEmpty(Icon)) + Icon = "warning"; + else + Icon = Icon.Trim(); + if (Icon == "none") + Icon = ""; + + // assume alertclass to match icon by default + // override it when icon and alert class are diff (ie. info, info-circle) + if (IsNullOrEmpty(AlertClass)) + AlertClass = Icon; + + if (Icon == "info") + Icon = "info-circle"; + if (Icon == "danger") + { + Icon = "warning"; + if (IsNullOrEmpty(AlertClass)) + AlertClass = "alert-danger"; + } + if (Icon == "success") + { + Icon = "check"; + if (IsNullOrEmpty(AlertClass)) + AlertClass = "success"; + } + + if (Icon == "warning" || Icon == "error" || Icon == "danger") + Icon = Icon + " text-danger"; // force to error color + + if (IsDismissible && !AlertClass.Contains("alert-dismissible")) + AlertClass += " alert-dismissible"; + + string messageText = !MessageAsHtml ? System.Net.WebUtility.HtmlEncode(Message) : Message; + string headerText = !HeaderAsHtml ? System.Net.WebUtility.HtmlEncode(Header) : Header; + + output.TagName = "div"; + + // fix up CSS class + if (CssClass != null) + CssClass = CssClass + " alert alert-" + AlertClass; + else + CssClass = "alert alert-" + AlertClass; + output.Attributes.Add("class", CssClass); + output.Attributes.Add("role", "alert"); + + StringBuilder sb = new StringBuilder(); + + if (IsDismissible) + sb.Append( + "\r\n" + ); + + if (IsNullOrEmpty(Header)) + { + if (!IsNullOrEmpty(Icon)) + { + sb.Append($" "); + } + sb.Append($"{messageText}"); + } + else + { + sb.Append($"

"); + if (!IsNullOrEmpty(Icon)) + { + sb.Append($" "); + } + sb.Append($"{headerText}

\r\n" + "
\r\n" + $"{messageText}\r\n"); + } + output.Content.SetHtmlContent(sb.ToString()); + } +} diff --git a/src/TagHelpers/Bootstrap/BootstrapClientSideValidationScript.cs b/src/TagHelpers/Bootstrap/BootstrapClientSideValidationScript.cs new file mode 100644 index 00000000..f7fe28f8 --- /dev/null +++ b/src/TagHelpers/Bootstrap/BootstrapClientSideValidationScript.cs @@ -0,0 +1,4 @@ +namespace Telegram; + +[HtmlTargetElement(TagNames.ClientSideValidationScript, ParentTag = TagNames.Form)] +public class BootstrapClientSideValidationScript { } diff --git a/src/TagHelpers/Bootstrap/BootstrapVariantTagHelper.cs b/src/TagHelpers/Bootstrap/BootstrapVariantTagHelper.cs new file mode 100644 index 00000000..3c1cedff --- /dev/null +++ b/src/TagHelpers/Bootstrap/BootstrapVariantTagHelper.cs @@ -0,0 +1,28 @@ +/* + * BootstrapVariantTagHelper.cs + * + * Created: 2024-58-15T22:58:22-05:00 + * Modified: 2024-58-15T22:58:22-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement("*", Attributes = AttributeNames.Variant)] +public class BootstrapVariantTagHelper : TagHelper +{ + [HtmlAttributeName(AttributeNames.Variant)] + public virtual Color Variant { get; set; } = Color.Primary; + + [HtmlAttributeName(AttributeNames.Class)] + public virtual string CssClass { get; set; } = string.Empty; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.SetAttribute("class", $"{CssClass} {Variant.ToString().ToLower()}"); + } +} diff --git a/src/TagHelpers/Bootstrap/ButtonTagHelper.cs b/src/TagHelpers/Bootstrap/ButtonTagHelper.cs new file mode 100644 index 00000000..c33297fc --- /dev/null +++ b/src/TagHelpers/Bootstrap/ButtonTagHelper.cs @@ -0,0 +1,17 @@ +// namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +// [HtmlTargetElement(TagNames.Button)] +// public class ButtonTagHelper(IHtmlGenerator generator) : InputTagHelper(generator) +// { +// // public sealed override InputType InputType +// // { +// // get => InputType.Button; +// // set => base.InputType = InputType.Button; +// // } + +// // public virtual ButtonType ButtonType +// // { +// // get => (ButtonType)InputType; +// // set => InputType = (InputType)value; +// // } +// } diff --git a/src/TagHelpers/Bootstrap/ContactInfoTagHelper.cs b/src/TagHelpers/Bootstrap/ContactInfoTagHelper.cs new file mode 100644 index 00000000..7294a66b --- /dev/null +++ b/src/TagHelpers/Bootstrap/ContactInfoTagHelper.cs @@ -0,0 +1,62 @@ +namespace AuthoringTagHelpers.TagHelpers +{ + [HtmlTargetElement(TagNames.Contact, TagStructure = TagStructure.NormalOrSelfClosing)] + public class ContactInfoTagHelper : TagHelper + { + [HtmlAttributeName("name")] + public string Name { get; set; } + + [HtmlAttributeName("address")] + public string Address { get; set; } + + [HtmlAttributeName("city")] + public string City { get; set; } + + [HtmlAttributeName("state")] + public string State { get; set; } + + [HtmlAttributeName("zip")] + public string Zip { get; set; } + + [HtmlAttributeName("phone")] + public string Phone { get; set; } + + [HtmlAttributeName("email")] + public string Email { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "address"; + var sb = new StringBuilder(); + if (!IsNullOrEmpty(Name)) + sb.Append("").Append(Name).AppendLine("
"); + if (!IsNullOrEmpty(Address)) + sb.Append(Address).AppendLine("
"); + if (!IsNullOrEmpty(City)) + sb.Append(City).Append(", "); + if (!IsNullOrEmpty(State)) + sb.Append(State).Append(' '); + if (!IsNullOrEmpty(Zip)) + sb.Append(Zip).Append("
"); + if (!IsNullOrEmpty(Phone)) + { + sb.Append("P: ") + .Append(Phone) + .AppendLine("
"); + } + + if (!IsNullOrEmpty(Email)) + { + sb.Append("") + .Append(Email) + .AppendLine(""); + } + + output.Content.SetHtmlContent(sb.ToString()); + } + } +} diff --git a/src/TagHelpers/Bootstrap/ContainerTagHelper.cs b/src/TagHelpers/Bootstrap/ContainerTagHelper.cs new file mode 100644 index 00000000..8d4e4134 --- /dev/null +++ b/src/TagHelpers/Bootstrap/ContainerTagHelper.cs @@ -0,0 +1,25 @@ +/* + * ContainerTagHelper.cs + * + * Created: 2024-43-16T16:43:35-05:00 + * Modified: 2024-43-16T16:43:35-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.Container)] +public class ContainerTagHelper : TagHelper +{ + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = TagNames.Div; + var childContent = await output.GetChildContentAsync(); + output.Content.AppendHtml(childContent); + output.AddCssClass(TagNames.Container); + } +} diff --git a/src/TagHelpers/Bootstrap/FileInput.cs b/src/TagHelpers/Bootstrap/FileInput.cs new file mode 100644 index 00000000..ccc2b346 --- /dev/null +++ b/src/TagHelpers/Bootstrap/FileInput.cs @@ -0,0 +1,6 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +public class FileInput +{ + +} diff --git a/src/TagHelpers/Bootstrap/FooterNavlinkTagHelper.cs b/src/TagHelpers/Bootstrap/FooterNavlinkTagHelper.cs new file mode 100644 index 00000000..2d4d2023 --- /dev/null +++ b/src/TagHelpers/Bootstrap/FooterNavlinkTagHelper.cs @@ -0,0 +1,72 @@ +/* + * FooterNavlinkTagHelper.cs + * + * Created: 2024-16-17T22:16:59-05:00 + * Modified: 2024-17-17T22:17:00-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +using Microsoft.Extensions.DependencyInjection; + +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.NavLink)] +public class NavLinkTagHelper(IHtmlGenerator generator) : AnchorTagHelper(generator) +{ + protected IServiceProvider Services => ViewContext.HttpContext.RequestServices; + + private IUrlHelper _urlHelper; + + [HtmlAttributeNotBound] + public IUrlHelper UrlHelper + { + get => + _urlHelper ??= Services + .GetRequiredService() + .GetUrlHelper(ViewContext); + set => _urlHelper = value; + } + + private bool ShouldBeActive() + { + var currentController = ViewContext.RouteData.Values["Controller"]?.ToString(); + var currentAction = ViewContext.RouteData.Values["Action"]?.ToString(); + + if ( + !IsNullOrWhiteSpace(Controller) && Controller?.ToLower() != currentController?.ToLower() + ) + { + return false; + } + + if (!IsNullOrWhiteSpace(Action) && Action?.ToLower() != currentAction?.ToLower()) + { + return false; + } + + foreach (var routeValue in RouteValues) + { + if ( + !ViewContext.RouteData.Values.ContainsKey(routeValue.Key) + || ViewContext.RouteData.Values[routeValue.Key].ToString() != routeValue.Value + ) + { + return false; + } + } + + return true; + } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (ShouldBeActive()) + { + output.AddCssClass(CssClasses.Active); + } + } +} diff --git a/src/TagHelpers/Bootstrap/InputTagHelper.cs b/src/TagHelpers/Bootstrap/InputTagHelper.cs new file mode 100644 index 00000000..8534662a --- /dev/null +++ b/src/TagHelpers/Bootstrap/InputTagHelper.cs @@ -0,0 +1,99 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +using System.Threading.Tasks; + +using Microsoft.AspNetCore.Razor.TagHelpers; + +using InputType = Dgmjr.AspNetCore.TagHelpers.Enumerations.InputType; +using LabelPosition = Dgmjr.AspNetCore.TagHelpers.Enumerations.LabelPosition; + +[HtmlTargetElement(TagNames.Input)] +public abstract class InputTagHelper(IHtmlGenerator generator) + : Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper(generator) +{ + [HtmlAttributeName(AttributeNames.Type)] + public virtual InputType InputType + { + get => Enum.TryParse(InputTypeName, out var value) ? value : InputType.Text; + set => InputTypeName = value.ToString().ToLower(); + } + + [HtmlAttributeName(AttributeNames.Placeholder)] + public string? Placeholder { get; set; } + + [HtmlAttributeName(AttributeNames.MarginBottom)] + public int? MarginBottom { get; set; } = 3; + + protected string MarginBottomCssClass => + MarginBottom.HasValue ? $"mb-{MarginBottom}" : string.Empty; + + [HtmlAttributeName(AttributeNames.Label)] + public string? Label { get; set; } + + [HtmlAttributeName(AttributeNames.TextBefore)] + public string? TextBefore { get; set; } + + [HtmlAttributeName(AttributeNames.TextAfter)] + public string? TextAfter { get; set; } + + [HtmlAttributeName(AttributeNames.BsLabelPosition)] + public LabelPosition LabelPosition { get; set; } = + Dgmjr.AspNetCore.TagHelpers.Enumerations.LabelPosition.Default; + + [HtmlAttributeName(AttributeNames.Id)] + public string Id + { + get => _id ??= guid.NewGuid().ToString(); + set => _id = value; + } + private string? _id; + + public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.AddCssClass("form-control"); + + var divTagBuilder = new TagBuilder(TagNames.Div); + + divTagBuilder.AddCssClass("form-group"); + divTagBuilder.AddCssClass(MarginBottomCssClass); + + if (Placeholder is not null) + { + output.Attributes.SetAttribute(AttributeNames.Placeholder, Placeholder); + } + + if (Label is not null) + { + output.PreElement.AppendHtmlLine($"
"); + switch (LabelPosition) + { + case LabelPosition.Default: + output.PreElement.AppendHtmlLine($"{Label}"); + break; + case LabelPosition.Left: + output.PreElement.AppendHtmlLine( + $"{Label}" + ); + break; + case LabelPosition.Right: + output.PostElement.AppendHtmlLine( + $"{Label}" + ); + break; + case LabelPosition.Floating: + output.PostElement.AppendHtmlLine( + $"" + ); + break; + } + output.PreElement.AppendHtml( + $"" + ); + } + + output.PreElement.SetHtmlContent($"
"); + output.PostElement.SetHtmlContent("
"); + + return base.ProcessAsync(context, output); + } +} diff --git a/src/TagHelpers/Bootstrap/ModalBodyTagHelper.cs b/src/TagHelpers/Bootstrap/ModalBodyTagHelper.cs new file mode 100644 index 00000000..c502b49f --- /dev/null +++ b/src/TagHelpers/Bootstrap/ModalBodyTagHelper.cs @@ -0,0 +1,17 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap +{ + /// + /// The modal-body portion of a Bootstrap modal dialog + /// + [HtmlTargetElement("modal-body", ParentTag = "modal")] + public class ModalBodyTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var childContent = await output.GetChildContentAsync(); + var modalContext = (ModalContext)context.Items[typeof(ModalTagHelper)]; + modalContext.Body = childContent; + output.SuppressOutput(); + } + } +} diff --git a/src/TagHelpers/Bootstrap/ModalFooterTagHelper.cs b/src/TagHelpers/Bootstrap/ModalFooterTagHelper.cs new file mode 100644 index 00000000..952994e7 --- /dev/null +++ b/src/TagHelpers/Bootstrap/ModalFooterTagHelper.cs @@ -0,0 +1,40 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap +{ + /// + /// The modal-footer portion of Bootstrap modal dialog + /// + [HtmlTargetElement("modal-footer", ParentTag = "modal")] + public class ModalFooterTagHelper : TagHelper + { + /// + /// Whether or not to show a button to dismiss the dialog. + /// Default: true + /// + public bool ShowDismiss { get; set; } = true; + + /// + /// The text to show on the Dismiss button + /// Default: Cancel + /// + public string DismissText { get; set; } = "Cancel"; + + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (ShowDismiss) + { + output.PreContent.AppendFormat(@"", DismissText); + } + var childContent = await output.GetChildContentAsync(); + var footerContent = new DefaultTagHelperContent(); + if (ShowDismiss) + { + footerContent.AppendFormat(@"", DismissText); + } + footerContent.AppendHtml(childContent); + var modalContext = (ModalContext)context.Items[typeof(ModalTagHelper)]; + modalContext.Footer = footerContent; + output.SuppressOutput(); + } + } +} diff --git a/src/TagHelpers/Bootstrap/ModalTagHelper.cs b/src/TagHelpers/Bootstrap/ModalTagHelper.cs new file mode 100644 index 00000000..e41f97ff --- /dev/null +++ b/src/TagHelpers/Bootstrap/ModalTagHelper.cs @@ -0,0 +1,68 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap +{ + public class ModalContext + { + public IHtmlContent Body { get; set; } + public IHtmlContent Footer { get; set; } + } + + /// + /// A Bootstrap modal dialog + /// + [RestrictChildren("modal-body", "modal-footer")] + public class ModalTagHelper : TagHelper + { + /// + /// The title of the modal + /// + public string Title { get; set; } + + /// + /// The Id of the modal + /// + public string Id { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var modalContext = new ModalContext(); + context.Items.Add(typeof(ModalTagHelper), modalContext); + + await output.GetChildContentAsync(); + + var template = +$@""); + } + } +} diff --git a/src/TagHelpers/Bootstrap/ModalToggleTagHelper.cs b/src/TagHelpers/Bootstrap/ModalToggleTagHelper.cs new file mode 100644 index 00000000..53608392 --- /dev/null +++ b/src/TagHelpers/Bootstrap/ModalToggleTagHelper.cs @@ -0,0 +1,23 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap +{ + /// + /// Sets the element as the item that will toggle the specified Bootstrap modal dialog + /// + [HtmlTargetElement(Attributes = ModalTargetAttributeName)] + public class ModalToggleTagHelper : TagHelper + { + public const string ModalTargetAttributeName = "bs-toggle-modal"; + + /// + /// The id of the modal that will be toggled by this element + /// + [HtmlAttributeName(ModalTargetAttributeName)] + public string ToggleModal { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.SetAttribute("data-toggle", "modal"); + output.Attributes.SetAttribute("data-target", $"#{ToggleModal}"); + } + } +} diff --git a/src/TagHelpers/Bootstrap/NavTagHelper.cs b/src/TagHelpers/Bootstrap/NavTagHelper.cs new file mode 100644 index 00000000..b3b88799 --- /dev/null +++ b/src/TagHelpers/Bootstrap/NavTagHelper.cs @@ -0,0 +1,4 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.NavbarNav, TagStructure = TagStructure.NormalOrSelfClosing)] +public class NavTagHelper : TagHelper { } diff --git a/src/TagHelpers/Bootstrap/NavbarTagHelper.cs b/src/TagHelpers/Bootstrap/NavbarTagHelper.cs new file mode 100644 index 00000000..03bcb5e2 --- /dev/null +++ b/src/TagHelpers/Bootstrap/NavbarTagHelper.cs @@ -0,0 +1,14 @@ +// namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +// [HtmlTargetElement(TagNames.Navbar, TagStructure = TagStructure.NormalOrSelfClosing)] +// public class NavbarTagHelper : TagHelper +// { +// [HtmlAttributeName(AttributeNames.Class)] +// public string Class { get; set; } = "navbar navbar-expand-lg navbar-light bg-light"; + +// public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) +// { +// output.TagName = "nav"; +// output.Attributes.SetAttribute("class", "navbar navbar-expand-lg navbar-light bg-light"); +// } +// } diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavDropdownLink.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavDropdownLink.cs new file mode 100644 index 00000000..14dd1c2c --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavDropdownLink.cs @@ -0,0 +1,38 @@ +/* + * DropdownLink.cs + * + * Created: 2024-09-17T12:09:28-05:00 + * Modified: 2024-09-17T12:09:28-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.NavDropdownLink)] +public class NavDropdownLink(IHtmlGenerator generator) : AnchorTagHelper(generator) +{ + #region --- Attribute Names --- + + private const string TitleAttributeName = "title"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(TitleAttributeName)] + public string Title { get; set; } + + #endregion + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var childContent = await output.GetChildContentAsync(); + + output.TagName = TagNames.Anchor; + output.AddCssClass("dropdown-item"); + } +} diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavbarDropdownDivider.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavbarDropdownDivider.cs new file mode 100644 index 00000000..4cb5fffa --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavbarDropdownDivider.cs @@ -0,0 +1,24 @@ +/* + * NavbarDropdownDivider.cs + * + * Created: 2024-08-17T16:08:29-05:00 + * Modified: 2024-08-17T16:08:29-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +public class NavbarDropdownDividerTagHelper() : DgmjrTagHelperBase(TagNames.Divider) +{ + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await base.ProcessAsync(context, output); + output.TagName = TagNames.Divider; + output.AddCssClass(CssClasses.DropdownDivider); + output.WrapOutside($"<{TagNames.Li}>", $""); + } +}; diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavbarDropdownTagHelper.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavbarDropdownTagHelper.cs new file mode 100644 index 00000000..becc024b --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavbarDropdownTagHelper.cs @@ -0,0 +1,53 @@ +/* + * NavbarDropdownTagHelper.cs + * + * Created: 2024-45-15T17:45:43-05:00 + * Modified: 2024-45-15T17:45:45-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.NavDropDown, ParentTag = TagNames.Navbar)] +[HtmlTargetElement(TagNames.NavDropDown, ParentTag = TagNames.NavbarNav)] +// [RestrictChildren(TagNames.NavLink)] +[GenerateId("navbar-dropdown-", false)] +[ContextClass] +public class NavbarDropdownTagHelper() : DgmjrTagHelperBase(TagNames.Li) +{ + #region --- Attribute Names --- + + private const string TitleAttributeName = "title"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(TitleAttributeName)] + public string Title { get; set; } + + #endregion + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await output.GetChildContentAsync(); + + output.TagName = "li"; + output.AddCssClass("nav-item dropdown"); + + // Dropdown Button + output.PreContent.AppendHtml( + $"" + ); + + // Dropdown Items + output.PreContent.AppendHtml( + $"
" + ); + output.PostContent.AppendHtml("
"); + } +} diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavbarFormTagHelper.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavbarFormTagHelper.cs new file mode 100644 index 00000000..103fd2e2 --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavbarFormTagHelper.cs @@ -0,0 +1,12 @@ +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[OutputElementHint(TagNames.Form)] +[HtmlTargetElement(TagNames.NavForm, ParentTag = TagNames.Navbar)] +public class NavbarFormTagHelper() : DgmjrTagHelperBase(TagNames.Form) +{ + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "form"; + output.AddCssClass("form-inline"); + } +} diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavbarLinkTagHelper.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavbarLinkTagHelper.cs new file mode 100644 index 00000000..e0a2b22a --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavbarLinkTagHelper.cs @@ -0,0 +1,151 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[OutputElementHint("a")] +[HtmlTargetElement( + TagNames.NavbarLink /*, ParentTag = TagNames.NavbarNav)] +[HtmlTargetElement(TagNames.NavLink, ParentTag = TagNames.NavDropDown)] +[HtmlTargetElement(TagNames.NavLink, ParentTag = TagNames.NavForm*/ +)] +public class NavbarLinkTagHelper(IHtmlGenerator generator) : AnchorTagHelper(generator) +{ + #region --- Attribute Names --- + + private const string ActiveAttributeName = "active"; + private const string DisableAttributeName = "disable"; + + #endregion + + #region --- Properties --- + + private bool? _active; + + [HtmlAttributeName(ActiveAttributeName)] + public bool Active + { + get => _active ?? ShouldBeActive(); + set => _active = value; + } + + [HtmlAttributeName(DisableAttributeName)] + public bool IsDisabled { get; set; } + + // [HtmlAttributeName("href")] + // public string Href { get; set; } = string.Empty; + + // [HtmlAttributeName("asp-action")] + // public string Action { get; set; } = string.Empty; + + // [HtmlAttributeName("asp-controller")] + // public string Controller { get; set; } = string.Empty; + + // [HtmlAttributeName("asp-area")] + // public string Area { get; set; } = string.Empty; + + // [HtmlAttributeName("asp-route-")] + // public IStringDictionary RouteValues { get; set; } = new StringDictionary(); + protected IServiceProvider Services => ViewContext.HttpContext.RequestServices; + + private IUrlHelper _urlHelper; + + [HtmlAttributeNotBound] + public IUrlHelper UrlHelper + { + get => + _urlHelper ??= Services + .GetRequiredService() + .GetUrlHelper(ViewContext); + set => _urlHelper = value; + } + #endregion + + private bool ShouldBeActive() + { + var currentController = ViewContext.RouteData.Values["Controller"]?.ToString(); + var currentAction = ViewContext.RouteData.Values["Action"]?.ToString(); + + if ( + !IsNullOrWhiteSpace(Controller) && Controller?.ToLower() != currentController?.ToLower() + ) + { + return false; + } + + if (!IsNullOrWhiteSpace(Action) && Action?.ToLower() != currentAction?.ToLower()) + { + return false; + } + + foreach (var routeValue in RouteValues) + { + if ( + !ViewContext.RouteData.Values.ContainsKey(routeValue.Key) + || ViewContext.RouteData.Values[routeValue.Key].ToString() != routeValue.Value + ) + { + return false; + } + } + + return true; + } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "a"; + output.AddCssClass( + !context.HasContextItem() ? "nav-link" : "dropdown-item" + ); + + // Href + if (!IsNullOrEmpty(Route)) + { + output.Attributes.Add(AttributeNames.Href, Route); + } + else + { + var url = UrlHelper.ActionLink(Action, Controller, new { area = Area }); + output.Attributes.Add(AttributeNames.Href, url); + } + + // Disabled + if (IsDisabled) + { + output.AddCssClass("disabled"); + } + + // Active + if (Active && context.HasContextItem()) + { + output.AddCssClass("active"); + output.PostContent.AppendHtml( + $"(current)" + ); + } + else if (Active && !context.HasContextItem()) + { + output.PostContent.AppendHtml( + $"(current)" + ); + output.WrapHtmlOutside($"
  • ", "
  • "); + } + else if (!context.HasContextItem()) + { + output.WrapHtmlOutside($"
  • ", "
  • "); + } + } + + // private bool IsActive() + // { + // var path = ViewContext.HttpContext.Request.Path; + // var href = Href; + + // if (href == "/") + // { + // return path == href; + // } + + // return path.StartsWithSegments(href); + // } +} diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavbarNavRight.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavbarNavRight.cs new file mode 100644 index 00000000..320459e1 --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavbarNavRight.cs @@ -0,0 +1,28 @@ +/* + * NavbarNavRight.cs + * + * Created: 2024-22-16T14:22:32-05:00 + * Modified: 2024-22-16T14:22:32-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.NavbarNav)] +// [RestrictChildren(TagNames.NavLink, TagNames.NavText, TagNames.NavDropDown, TagNames.NavItem)] +[ContextClass] +public class NavbarNavRightTagHelper(IActionContextAccessor actionContextAccessor) + : NavbarNavTagHelper(actionContextAccessor) +{ + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await base.ProcessAsync(context, output); + output.AddCssClass(CssClasses.Nav); + output.AddCssClass(CssClasses.NavbarNav); + output.AddCssClass(CssClasses.NavRight); + } +} diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavbarNavTagHelper.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavbarNavTagHelper.cs new file mode 100644 index 00000000..2780dc87 --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavbarNavTagHelper.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.NavbarNav)] +// [RestrictChildren(TagNames.NavLink, TagNames.NavText, TagNames.NavDropDown, TagNames.NavItem)] +[ContextClass] +public class NavbarNavTagHelper(IActionContextAccessor actionContextAccessor) + : TagHelper, + IHaveAnActionContextAccessor, + IIdentifiable +{ + public IActionContextAccessor ActionContextAccessor => actionContextAccessor; + + [CopyToOutput] + public string Id { get; set; } = guid.NewGuid().ToString()[..8]; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await base.ProcessAsync(context, output); + output.TagName = "ul"; + output.AddCssClass(CssClasses.Nav); + output.AddCssClass(CssClasses.NavbarNav); + } +} diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavbarTagHelper.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavbarTagHelper.cs new file mode 100644 index 00000000..be2a3c06 --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavbarTagHelper.cs @@ -0,0 +1,172 @@ +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +using Dgmjr.AspNetCore.TagHelpers.Enumerations; +using Dgmjr.AspNetCore.TagHelpers.Extensions; + +[OutputElementHint(TagNames.Nav)] +// [RestrictChildren(TagNames.NavbarNav, TagNames.NavForm, TagNames.NavText, TagNames.NavItem)] +// // [GenerateId("navbar-", false)] +// [HtmlTargetElement(TagNames.Navbar, Attributes = BrandTextAttributeName)] +// [HtmlTargetElement(TagNames.Navbar, Attributes = BrandImageAttributeName)] +// [HtmlTargetElement(TagNames.Navbar, Attributes = BrandHrefAttributeName)] +// [HtmlTargetElement(TagNames.Navbar, Attributes = ThemeAttributeName)] +// [HtmlTargetElement(TagNames.Navbar, Attributes = BackgroundAttributeName)] +// [HtmlTargetElement(TagNames.Navbar, Attributes = AttributesNames)] +public class NavbarTagHelper(IActionContextAccessor actionContextAccessor) + : TagHelper, + IHaveAnActionContextAccessor, + IIdentifiable +{ + #region --- Attribute Names --- + private const string AttributesNames = + BrandTextAttributeName + + "," + + BrandImageAttributeName + + "," + + BrandHrefAttributeName + + "," + + ThemeAttributeName + + "," + + BackgroundAttributeName; + private const string BrandTextAttributeName = "brand-text"; + private const string BrandImageAttributeName = "brand-image"; + private const string BrandHrefAttributeName = "brand-href"; + private const string BrandAreaAttributeName = "brand-area"; + private const string BrandControllerAttributeName = "brand-controller"; + private const string BrandActionAttributeName = "brand-action"; + private const string ThemeAttributeName = "theme"; + private const string BackgroundAttributeName = "background"; + #endregion + + #region --- Properties --- + + public IActionContextAccessor ActionContextAccessor => actionContextAccessor; + + [HtmlAttributeName(BrandHrefAttributeName)] + public string BrandHref { get; set; } = "#"; + + [HtmlAttributeName(BrandAreaAttributeName)] + public string BrandArea { get; set; } = string.Empty; + + [HtmlAttributeName(BrandControllerAttributeName)] + public string BrandController { get; set; } = string.Empty; + + [HtmlAttributeName(BrandActionAttributeName)] + public string BrandAction { get; set; } = string.Empty; + + [HtmlAttributeName(BrandTextAttributeName)] + public string BrandText { get; set; } + + [HtmlAttributeName(BrandImageAttributeName)] + [ConvertVirtualUrl] + public string BrandImage { get; set; } + + [HtmlAttributeName(ThemeAttributeName)] + public Theme Theme { get; set; } = Theme.Light; + + [HtmlAttributeName(BackgroundAttributeName)] + public Color Background { get; set; } = Color.Light; + + [ViewContext] + [HtmlAttributeNotBound] + public ViewContext ViewContext { get; set; } + + [CopyToOutput] + public string Id { get; set; } = guid.NewGuid().ToString()[..8]; + + #endregion + + // public override void Init(TagHelperContext context) + // { + // this.Init(context, ActionContextAccessor); + // base.Init(context); + // } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await output.GetChildContentAsync(); + + output.TagName = TagNames.Nav; + output.AddCssClass( + $"{CssClasses.Navbar} {CssClasses.NavbarExpandLg} {CssClasses.BgPrimary}" + ); + + // Theme + if (Theme != Theme.Default) + { + output.AddCssClass( + $"navbar-{Theme.GetEnumInfo().Name} data-bs-theme-{Theme.GetEnumInfo().Name}" + ); + + // Set background to valid color + Background = Theme == Theme.Dark && Background == Color.Light ? Color.Dark : Background; + } + else + { + output.AddCssClass(CssClasses.NavBarLight); + } + + // Background + output.AddCssClass(Background.GetColorInfo().BackgroundCssClass); + + // Brand + if (!IsNullOrEmpty(BrandText) || !IsNullOrEmpty(BrandImage)) + { + output.PreContent.AppendHtml(GenerateBrand()); + } + + // Toggler + output.PreContent.AppendHtml( + $"" + ); + + // Wrapper + output.WrapHtmlContentInside( + $"
    ", + "
    " + ); + } + + private string GetBrandHref() + { + // Href + if (!IsNullOrEmpty(BrandHref)) + { + return BrandHref; + } + else + { + var urlHelper = new UrlHelper(ViewContext); + return urlHelper.Action(BrandAction, BrandController, new { area = BrandArea }); + } + } + + private IHtmlContent GenerateBrand() + { + // Anchor + var brand = new TagBuilder(TagNames.Anchor); + brand.AddCssClass(CssClasses.NavBarBrand); + brand.Attributes.Add("href", GetBrandHref()); + + // Image + if (!IsNullOrEmpty(BrandImage)) + { + var img = new TagBuilder(TagNames.Img) { TagRenderMode = TagRenderMode.SelfClosing }; + img.Attributes.Add(AttributeNames.Src, BrandImage); + img.Attributes.Add("height", "30"); + img.Attributes.Add("alt", BrandText); + + if (!IsNullOrEmpty(BrandText)) + { + img.AddCssClass($"{CssClasses.DInlineBlock} {CssClasses.AlignTop}"); + } + + brand.InnerHtml.AppendHtml(img); + } + + // Text + brand.InnerHtml.Append($" {BrandText}"); + + return brand; + } +} diff --git a/src/TagHelpers/Bootstrap/Navigation.No/NavbarTextTagHelper.cs b/src/TagHelpers/Bootstrap/Navigation.No/NavbarTextTagHelper.cs new file mode 100644 index 00000000..0f53e349 --- /dev/null +++ b/src/TagHelpers/Bootstrap/Navigation.No/NavbarTextTagHelper.cs @@ -0,0 +1,17 @@ +namespace No.Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.NavText, ParentTag = TagNames.Navbar)] +[HtmlTargetElement(TagNames.NavText, ParentTag = TagNames.NavbarNav)] +public class NavbarTextTagHelper() : DgmjrTagHelperBase(TagNames.Li) +{ + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await base.ProcessAsync(context, output); + output.TagName = TagNames.Li; + output.AddCssClass(CssClasses.NavItem); + output.AddCssClass(CssClasses.NavLink); + output.AddCssClass(CssClasses.NavText); + output.AddCssClass(CssClasses.Disabled); + output.AddCssClass(TagNames.NavText); + } +} diff --git a/src/TagHelpers/Bootstrap/PageElements/Footer.cs b/src/TagHelpers/Bootstrap/PageElements/Footer.cs new file mode 100644 index 00000000..151b9611 --- /dev/null +++ b/src/TagHelpers/Bootstrap/PageElements/Footer.cs @@ -0,0 +1,112 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.Footer, Attributes = CopyrightHolderAttributeName)] +[HtmlTargetElement(TagNames.Footer, Attributes = CopyrightStartYearAttributeName)] +[HtmlTargetElement(TagNames.Footer, Attributes = CopyrightEndYearAttributeName)] +public class PageFooter : TagHelper +{ + public const string CopyrightStartYearAttributeName = "copyright-start-year"; + public const string CopyrightHolderEmailAttributeName = "copyright-holder-email"; + + public const string CopyrightEndYearAttributeName = "copyright-end-year"; + public const string BackgroundAttributeName = "bs-background"; + public const string PositionAttributeName = "position"; + public const string CopyrightHolderAttributeName = "copyright-holder"; + public const string TextColorAttributeName = "bs-text-color"; + + /// The text color of the footer (defaults to ). + /// + [HtmlAttributeName(TextColorAttributeName)] + public Color TextColor { get; set; } = Color.light; + + /// The start year of the copyright + [HtmlAttributeName(CopyrightStartYearAttributeName)] + public int? CopyrightStartYear { get; set; } = datetime.Now.Year; + + /// The end year of the copyright (usually the current year) + /// + [HtmlAttributeName(CopyrightEndYearAttributeName)] + public int? CopyrightEndYear { get; set; } = datetime.Now.Year; + + /// The name of the person or organization that owns the copyright for the content + [HtmlAttributeName(CopyrightHolderAttributeName)] + public string CopyrightHolder { get; set; } + + /// The email address of the copyright holder + [HtmlAttributeName(CopyrightHolderEmailAttributeName)] + public string CopyrightHolderEmail { get; set; } + + /// The background color of the footer (defaults to ). + [HtmlAttributeName(BackgroundAttributeName)] + public Color BackgroundColor { get; set; } = Color.primary; + + /// Where the footer should show up (defaults to ). + [HtmlAttributeName(PositionAttributeName)] + public Position Position { get; set; } = Position.fixedbottom; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = TagNames.Footer; + var childContent = await output.GetChildContentAsync(); + output.Content.AppendHtml(childContent); + output.AddCssClass( + $"footer border-top bg-{BackgroundColor.GetName()} text-{TextColor.GetName()} {Position.GetName()}" + ); + output.PreContent.AppendHtml( + $$$""" +
    + """ + ); + if (!IsNullOrEmpty(CopyrightHolder)) + { + output.PreContent.AppendHtml( + """ + © + + """ + ); + if (CopyrightStartYear.HasValue) + { + output.PreContent.AppendHtml( + $$$""" + + {{{datetime.Now.Year}}} + + """ + ); + } + if (CopyrightEndYear.HasValue && CopyrightEndYear != CopyrightStartYear) + { + output.PreContent.AppendHtml( + $$$""" + + - {{{datetime.Now.Year}}} + """ + ); + } + if (!IsNullOrEmpty(CopyrightHolderEmail)) + { + output.PreContent.AppendHtml( + $$$""" + + + {{{CopyrightHolder}}} + + | + """ + ); + } + else + { + output.PreContent.AppendHtml( + $$$""" + {{{CopyrightHolder}}} + + | + + """ + ); + } + } + } +} diff --git a/src/TagHelpers/Bootstrap/PageElements/FooterNavLinkHelper.cs b/src/TagHelpers/Bootstrap/PageElements/FooterNavLinkHelper.cs new file mode 100644 index 00000000..95f43e18 --- /dev/null +++ b/src/TagHelpers/Bootstrap/PageElements/FooterNavLinkHelper.cs @@ -0,0 +1,68 @@ +/* + * FooterNavLinkHelper.cs + * + * Created: 2024-46-16T16:46:00-05:00 + * Modified: 2024-46-16T16:46:22-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +using System.Threading.Tasks; + +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +[HtmlTargetElement(TagNames.NavLink, ParentTag = TagNames.Footer)] +// [HtmlTargetElement(TagNames.NavLink, ParentTag = TagNames.Container)] +public class FooterNavLinkTagHelper(IHtmlGenerator generator) : AnchorTagHelper(generator) +{ + public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + base.ProcessAsync(context, output); + output.TagName = "a"; + + if (ShouldBeActive()) + { + MakeActive(output); + } + return Task.CompletedTask; + } + + private bool ShouldBeActive() + { + var currentController = ViewContext.RouteData.Values["Controller"]?.ToString(); + var currentAction = ViewContext.RouteData.Values["Action"]?.ToString(); + + if ( + !IsNullOrWhiteSpace(Controller) && Controller?.ToLower() != currentController?.ToLower() + ) + { + return false; + } + + if (!IsNullOrWhiteSpace(Action) && Action?.ToLower() != currentAction?.ToLower()) + { + return false; + } + + foreach (var routeValue in RouteValues) + { + if ( + !ViewContext.RouteData.Values.ContainsKey(routeValue.Key) + || ViewContext.RouteData.Values[routeValue.Key].ToString() != routeValue.Value + ) + { + return false; + } + } + + return true; + } + + private static void MakeActive(TagHelperOutput output) + { + output.AddCssClass("active"); + } +} diff --git a/src/TagHelpers/Bootstrap/PageElements/PageHeader.cs b/src/TagHelpers/Bootstrap/PageElements/PageHeader.cs new file mode 100644 index 00000000..04fdc2ff --- /dev/null +++ b/src/TagHelpers/Bootstrap/PageElements/PageHeader.cs @@ -0,0 +1,42 @@ +/* + * PageHeader.cs + * + * Created: 2024-02-16T02:02:36-05:00 + * Modified: 2024-02-16T02:02:37-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +using Dgmjr.AspNetCore.TagHelpers.Enumerations; + +[HtmlTargetElement(TagNames.PageHeader)] +public class PageHeader(IHtmlGenerator generator) : TagHelper(), IHaveAWritableId +{ + [HtmlAttributeName(AttributeNames.HeadingType)] + public HeadingType HeadingType { get; set; } = HeadingType.h1; + + [HtmlAttributeName(AttributeNames.Id)] + public string Id { get; set; } + + [HtmlAttributeName(AttributeNames.Title)] + public string Title { get; set; } = string.Empty; + + [ViewContext] + public ViewContext ViewContext { get; set; } + + protected IHtmlGenerator Generator => generator; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddClass("page-header", HtmlEncoder.Default); + output.Content.AppendHtml( + $"<{HeadingType.ToString().ToLower()}>{Title}" + ); + } +} diff --git a/src/TagHelpers/Bootstrap/PanelBodyTagHelper.cs b/src/TagHelpers/Bootstrap/PanelBodyTagHelper.cs new file mode 100644 index 00000000..cc26dfe6 --- /dev/null +++ b/src/TagHelpers/Bootstrap/PanelBodyTagHelper.cs @@ -0,0 +1,17 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap +{ + /// + /// Panel Body Tag Helper + /// + [HtmlTargetElement("panel-body", ParentTag = "panel")] + public class PanelBodyTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var childContent = await output.GetChildContentAsync(); + var modalContext = (PanelContext)context.Items[typeof(PanelTagHelper)]; + modalContext.Body = childContent; + output.SuppressOutput(); + } + } +} diff --git a/src/TagHelpers/Bootstrap/PanelContext.cs b/src/TagHelpers/Bootstrap/PanelContext.cs new file mode 100644 index 00000000..a9d14f97 --- /dev/null +++ b/src/TagHelpers/Bootstrap/PanelContext.cs @@ -0,0 +1,9 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap +{ + public class PanelContext + { + public IHtmlContent Title { get; set; } + public IHtmlContent Body { get; set; } + public IHtmlContent Footer { get; set; } + } +} diff --git a/src/TagHelpers/Bootstrap/PanelFooterTagHelper.cs b/src/TagHelpers/Bootstrap/PanelFooterTagHelper.cs new file mode 100644 index 00000000..77060e46 --- /dev/null +++ b/src/TagHelpers/Bootstrap/PanelFooterTagHelper.cs @@ -0,0 +1,16 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +/// +/// Panel Footer Tag Helper +/// +[HtmlTargetElement("panel-footer", ParentTag = "panel")] +public class PanelFooterTagHelper : TagHelper +{ + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var childContent = await output.GetChildContentAsync(); + var modalContext = (PanelContext)context.Items[typeof(PanelTagHelper)]; + modalContext.Footer = childContent; + output.SuppressOutput(); + } +} diff --git a/src/TagHelpers/Bootstrap/PanelTagHelper.cs b/src/TagHelpers/Bootstrap/PanelTagHelper.cs new file mode 100644 index 00000000..e680f4aa --- /dev/null +++ b/src/TagHelpers/Bootstrap/PanelTagHelper.cs @@ -0,0 +1,58 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap +{ + /// + /// Panel Tag Helper + /// + [RestrictChildren("panel-title", "panel-body", "panel-footer")] + public class PanelTagHelper : TagHelper + { + public PanelType Type { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var panelContext = new PanelContext(); + context.Items.Add(typeof(PanelTagHelper), panelContext); + + await output.GetChildContentAsync(); + + output.TagName = "div"; + + // Default panel type will be panel-default + output.Attributes.Add("class", $"panel panel-{Type.ToString().ToLower()}"); + + // panel title + if (panelContext.Title != null) + { + var h3 = new TagBuilder("h3"); + h3.AddCssClass("panel-title"); + h3.InnerHtml.AppendHtml(panelContext.Title); + + var panelTitle = new TagBuilder("div"); + panelTitle.AddCssClass("panel-heading"); + panelTitle.InnerHtml.AppendHtml(h3); + + output.Content.AppendHtml(panelTitle); + } + + // panel body + if (panelContext.Body != null) + { + var panelBody = new TagBuilder("div"); + panelBody.AddCssClass("panel-body"); + panelBody.InnerHtml.AppendHtml(panelContext.Body); + + output.Content.AppendHtml(panelBody); + } + + // panel footer + if (panelContext.Footer != null) + { + var panelFooter = new TagBuilder("div"); + panelFooter.AddCssClass("panel-footer"); + panelFooter.InnerHtml.AppendHtml(panelContext.Footer); + + output.Content.AppendHtml(panelFooter); + } + } + } +} diff --git a/src/TagHelpers/Bootstrap/PanelTitleTagHelper.cs b/src/TagHelpers/Bootstrap/PanelTitleTagHelper.cs new file mode 100644 index 00000000..ec612ca2 --- /dev/null +++ b/src/TagHelpers/Bootstrap/PanelTitleTagHelper.cs @@ -0,0 +1,16 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap; + +/// +/// Panel Title Tag Helper +/// +[HtmlTargetElement("panel-title", ParentTag = "panel")] +public class PanelTitleTagHelper : TagHelper +{ + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var childContent = await output.GetChildContentAsync(); + var modalContext = (PanelContext)context.Items[typeof(PanelTagHelper)]; + modalContext.Title = childContent; + output.SuppressOutput(); + } +} diff --git a/src/TagHelpers/Bootstrap/ProgressBarTagHelper.cs b/src/TagHelpers/Bootstrap/ProgressBarTagHelper.cs new file mode 100644 index 00000000..63df2b4c --- /dev/null +++ b/src/TagHelpers/Bootstrap/ProgressBarTagHelper.cs @@ -0,0 +1,71 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap +{ + [HtmlTargetElement("div", Attributes = ProgressValueAttributeName)] + public class ProgressBarTagHelper : TagHelper + { + private const string ProgressValueAttributeName = "bs-progress-value"; + private const string ProgressMinAttributeName = "bs-progress-min"; + private const string ProgressMaxAttributeName = "bs-progress-max"; + + /// + /// An expression to be evaluated against the current model. + /// + [HtmlAttributeName(ProgressValueAttributeName)] + public int ProgressValue { get; set; } + + [HtmlAttributeName(ProgressMinAttributeName)] + public int ProgressMin { get; set; } = 0; + + [HtmlAttributeName(ProgressMaxAttributeName)] + public int ProgressMax { get; set; } = 100; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (ProgressMin >= ProgressMax) + { + throw new ArgumentException( + $"{ProgressMinAttributeName} must be less than {ProgressMaxAttributeName}" + ); + } + + if (ProgressValue > ProgressMax || ProgressValue < ProgressMin) + { + throw new ArgumentOutOfRangeException( + string.Format( + "{0} must be within the range of {1} and {2}", + ProgressValueAttributeName, + ProgressMinAttributeName, + ProgressMaxAttributeName + ) + ); + } + var progressTotal = ProgressMax - ProgressMin; + + var progressPercentage = Math.Round( + ((decimal)(ProgressValue - ProgressMin) / (decimal)progressTotal) * 100, + 4 + ); + + string progressBarContent = + $@"
    + {progressPercentage}% Complete +
    "; + + output.Content.AppendHtml(progressBarContent); + + string classValue; + if (output.Attributes.ContainsName("class")) + { + classValue = string.Format("{0} {1}", output.Attributes["class"].Value, "progress"); + } + else + { + classValue = "progress"; + } + + output.Attributes.SetAttribute("class", classValue); + + base.Process(context, output); + } + } +} diff --git a/src/TagHelpers/Bootstrap/TextEntry.cs b/src/TagHelpers/Bootstrap/TextEntry.cs new file mode 100644 index 00000000..9078be10 --- /dev/null +++ b/src/TagHelpers/Bootstrap/TextEntry.cs @@ -0,0 +1,100 @@ +/* +This TH was written by Rick Strahl + +Usage: + + + + /// Simple tag helper that creates a label and textbox combination in + /// easier to read HTML than the full bootstrap form-group format. + /// + [HtmlTargetElement("text-entry")] + public class TextEntryTagHelper : TagHelper + { + /// + /// the main message that gets displayed + /// + [HtmlAttributeName("label-text")] + public string LabelText { get; set; } + + /// + /// Optional header that is displayed in big text. Use for + /// 'noisy' warnings and stop errors only please :-) + /// The message is displayed below the header. + /// + [HtmlAttributeName("value")] + public string Value { get; set; } + + [HtmlAttributeName("placeholder")] + public string PlaceHolder { get; set; } + + /// + /// CSS class. Handled here so we can capture the existing + /// class value and append the BootStrap alert class. + /// + [HtmlAttributeName("textbox-class")] + public string TextBoxClass { get; set; } = "form-control"; + + [HtmlAttributeName("for")] + public ModelExpression For { get; set; } + + [HtmlAttributeName("id")] + public string Id { get; set; } + + [HtmlAttributeName("type")] + public string Type { get; set; } = "text"; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (IsNullOrEmpty(Value)) + { + if (For != null) + { + Value = For.Model.ToString(); + } + } + + output.TagName = "div"; + output.Attributes.Add("class", "form-group"); + + var writer = new System.IO.StringWriter(); + CreateLabelTagBuilder().WriteTo(writer, HtmlEncoder.Default); + CreateInputTagBuilder().WriteTo(writer, HtmlEncoder.Default); + // TODO: Add ValidationMessage + + output.Content.SetHtmlContent(writer.ToString()); + } + + private TagBuilder CreateLabelTagBuilder() + { + var labelBuilder = new TagBuilder("label"); + + labelBuilder.Attributes.Add("for", Id); + labelBuilder.InnerHtml.Append(LabelText); + + return labelBuilder; + } + + private TagBuilder CreateInputTagBuilder() + { + var inputBuilder = new TagBuilder("input"); + + inputBuilder.Attributes.Add("id", Id); + inputBuilder.Attributes.Add("name", Id); + inputBuilder.AddCssClass(TextBoxClass); + inputBuilder.Attributes.Add("value", Value); + if (!IsNullOrEmpty(PlaceHolder)) + { + inputBuilder.Attributes.Add("placeholder", PlaceHolder); + } + + return inputBuilder; + } + } +} diff --git a/src/TagHelpers/Breadcrumb/BreadcrumbItemTagHelper.cs b/src/TagHelpers/Breadcrumb/BreadcrumbItemTagHelper.cs new file mode 100644 index 00000000..bd45c1e4 --- /dev/null +++ b/src/TagHelpers/Breadcrumb/BreadcrumbItemTagHelper.cs @@ -0,0 +1,42 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Breadcrumb +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("breadcrumb-item", ParentTag = "breadcrumb")] + [OutputElementHint("li")] + public class BreadcrumbItemTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string HrefAttributeName = "href"; + private const string ActiveAttributeName = "active"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(HrefAttributeName)] + public string Href { get; set; } + + [HtmlAttributeName(ActiveAttributeName)] + public bool Active { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "li"; + output.AddCssClass("breadcrumb-item"); + + if (Active) + { + output.AddCssClass("active"); + } + else if (!IsNullOrEmpty(Href)) + { + output.PreContent.SetHtmlContent($""); + output.PostContent.SetHtmlContent(""); + } + } + } +} diff --git a/src/TagHelpers/Breadcrumb/BreadcrumbTagHelper.cs b/src/TagHelpers/Breadcrumb/BreadcrumbTagHelper.cs new file mode 100644 index 00000000..079f0b54 --- /dev/null +++ b/src/TagHelpers/Breadcrumb/BreadcrumbTagHelper.cs @@ -0,0 +1,15 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Breadcrumb +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("ol")] + [RestrictChildren("breadcrumb-item")] + public class BreadcrumbTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "ol"; + output.AddCssClass("breadcrumb"); + } + } +} diff --git a/src/TagHelpers/Card/CardBodyTagHelper.cs b/src/TagHelpers/Card/CardBodyTagHelper.cs new file mode 100644 index 00000000..2cd586d5 --- /dev/null +++ b/src/TagHelpers/Card/CardBodyTagHelper.cs @@ -0,0 +1,56 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Card +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("card-body", ParentTag = "card")] + [OutputElementHint("div")] + public class CardBodyTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string TitleAttributeName = "title"; + private const string SubtitleAttributeName = "subtitle"; + private const string ColorAttributeName = "color"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(TitleAttributeName)] + public string Title { get; set; } + + [HtmlAttributeName(SubtitleAttributeName)] + public string Subtitle { get; set; } + + [HtmlAttributeName(ColorAttributeName)] + public Color Color { get; set; } = Color.None; + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("card-body"); + + // Title + if (!IsNullOrEmpty(this.Title)) + { + output.PreContent.AppendHtml($"

    {this.Title}

    "); + } + + // Subtitle + if (!IsNullOrEmpty(this.Subtitle)) + { + output.PreContent.AppendHtml( + $"
    {this.Subtitle}
    " + ); + } + + // Color + if (this.Color != Color.None) + { + output.AddCssClass(this.Color.GetColorInfo().TextCssClass); + } + } + } +} diff --git a/src/TagHelpers/Card/CardColumnTagHelper.cs b/src/TagHelpers/Card/CardColumnTagHelper.cs new file mode 100644 index 00000000..f9ea8a83 --- /dev/null +++ b/src/TagHelpers/Card/CardColumnTagHelper.cs @@ -0,0 +1,15 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Card +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("div")] + [RestrictChildren("card")] + public class CardColumnTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("card-columns"); + } + } +} diff --git a/src/TagHelpers/Card/CardDeckTagHelper.cs b/src/TagHelpers/Card/CardDeckTagHelper.cs new file mode 100644 index 00000000..430eff73 --- /dev/null +++ b/src/TagHelpers/Card/CardDeckTagHelper.cs @@ -0,0 +1,15 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Card +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("div")] + [RestrictChildren("card")] + public class CardDeckTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("card-deck"); + } + } +} diff --git a/src/TagHelpers/Card/CardFooterTagHelper.cs b/src/TagHelpers/Card/CardFooterTagHelper.cs new file mode 100644 index 00000000..84080ecd --- /dev/null +++ b/src/TagHelpers/Card/CardFooterTagHelper.cs @@ -0,0 +1,15 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Card +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("card-footer", ParentTag = "card")] + [OutputElementHint("div")] + public class CardFooterTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("card-footer"); + } + } +} diff --git a/src/TagHelpers/Card/CardGroupTagHelper.cs b/src/TagHelpers/Card/CardGroupTagHelper.cs new file mode 100644 index 00000000..a2de4bdd --- /dev/null +++ b/src/TagHelpers/Card/CardGroupTagHelper.cs @@ -0,0 +1,15 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Card +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("div")] + [RestrictChildren("card")] + public class CardGroupTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("card-group"); + } + } +} diff --git a/src/TagHelpers/Card/CardHeaderTagHelper.cs b/src/TagHelpers/Card/CardHeaderTagHelper.cs new file mode 100644 index 00000000..aabdbdb3 --- /dev/null +++ b/src/TagHelpers/Card/CardHeaderTagHelper.cs @@ -0,0 +1,15 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Card +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("card-header", ParentTag = "card")] + [OutputElementHint("div")] + public class CardHeaderTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("card-header"); + } + } +} diff --git a/src/TagHelpers/Card/CardImageTagHelper.cs b/src/TagHelpers/Card/CardImageTagHelper.cs new file mode 100644 index 00000000..96f80170 --- /dev/null +++ b/src/TagHelpers/Card/CardImageTagHelper.cs @@ -0,0 +1,40 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Card +{ + using Microsoft.AspNetCore.Mvc.Infrastructure; + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("card-image", ParentTag = "card")] + [OutputElementHint("img")] + public class CardImageTagHelper(IActionContextAccessor actionContextAccessor) + : TagHelper, + IHaveAnActionContextAccessor + { + public IActionContextAccessor ActionContextAccessor => actionContextAccessor; + + #region --- Attribute Names --- + + private const string PositionAttributeName = "position"; + + #endregion + + #region --- Properties --- + + // [HtmlAttributeName(PositionAttributeName)] + public CardImagePosition Position { get; set; } = CardImagePosition.Top; + + [CopyToOutput] + [ConvertVirtualUrl] + public string Src { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "img"; + output.TagMode = TagMode.SelfClosing; + + // Position + output.AddCssClass($"card-img-{this.Position.GetEnumInfo().Name}"); + } + } +} diff --git a/src/TagHelpers/Card/CardTagHelper.cs b/src/TagHelpers/Card/CardTagHelper.cs new file mode 100644 index 00000000..086f5d5f --- /dev/null +++ b/src/TagHelpers/Card/CardTagHelper.cs @@ -0,0 +1,58 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Card +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("div")] + [RestrictChildren("card-header", "card-body", "card-footer", "card-image")] + public class CardTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string AlignmentAttributeName = "alignment"; + private const string ColorAttributeName = "color"; + private const string BorderAttributeName = "border"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(AlignmentAttributeName)] + public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Default; + + [HtmlAttributeName(ColorAttributeName)] + public Color Color { get; set; } = Color.None; + + [HtmlAttributeName(BorderAttributeName)] + public Color Border { get; set; } = Color.None; + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("card"); + + // Alignment + if (this.Alignment != HorizontalAlignment.Default) + { + output.AddCssClass($"text-{this.Alignment.GetEnumInfo().Name}"); + } + + // Color + if (this.Color != Color.None) + { + output.AddCssClass( + this.Color == Color.Light + ? this.Color.GetColorInfo().BackgroundCssClass + : $"text-white {this.Color.GetColorInfo().BackgroundCssClass}" + ); + } + + // Border + if (this.Border != Color.None) + { + output.AddCssClass(this.Border.GetColorInfo().BorderCssClass); + } + } + } +} diff --git a/src/TagHelpers/Carousel/CarouselItemTagHelper.cs b/src/TagHelpers/Carousel/CarouselItemTagHelper.cs new file mode 100644 index 00000000..9e045fc6 --- /dev/null +++ b/src/TagHelpers/Carousel/CarouselItemTagHelper.cs @@ -0,0 +1,70 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Carousel +{ + using Microsoft.AspNetCore.Mvc.Infrastructure; + using Microsoft.AspNetCore.Razor.TagHelpers; + using System.Threading.Tasks; + + [HtmlTargetElement("carousel-item", ParentTag = "carousel")] + [OutputElementHint("div")] + public class CarouselItemTagHelper(IActionContextAccessor actionContextAccessor) + : TagHelper, + IHaveAnActionContextAccessor + { + public IActionContextAccessor ActionContextAccessor => actionContextAccessor; + + #region --- Attribute Names --- + + private const string ActiveAttributeName = "active"; + private const string SrcAttributeName = "src"; + private const string AltAttributeName = "alt"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(SrcAttributeName)] + [ConvertVirtualUrl] + public string Src { get; set; } + + [HtmlAttributeName(AltAttributeName)] + public string Alt { get; set; } + + [HtmlAttributeName(ActiveAttributeName)] + public bool Active { get; set; } + + [Context] + protected CarouselTagHelper CarouselContext { get; set; } + + #endregion + + public override void Init(TagHelperContext context) + { + base.Init(context); + CarouselContext.Items.Add(this); + } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("carousel-item"); + + // Active + if (this.Active) + { + output.AddCssClass("active"); + } + + output.PreContent.PrependHtml( + $"\"{this.Alt}\"" + ); + output.Content.SetHtmlContent(await output.GetChildContentAsync()); + + // Caption + if (!output.Content.IsEmptyOrWhiteSpace) + { + output.PreContent.AppendHtml("
    "); + output.PostContent.PrependHtml("
    "); + } + } + } +} diff --git a/src/TagHelpers/Carousel/CarouselTagHelper.cs b/src/TagHelpers/Carousel/CarouselTagHelper.cs new file mode 100644 index 00000000..46e31d8b --- /dev/null +++ b/src/TagHelpers/Carousel/CarouselTagHelper.cs @@ -0,0 +1,85 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Carousel +{ + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Razor.TagHelpers; + using System.Collections.Generic; + using System.Threading.Tasks; + + [OutputElementHint("div")] + [RestrictChildren("carousel-item")] + [ContextClass] + [GenerateId("carousel-")] + public class CarouselTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string ControlsAttributeName = "controls"; + private const string IndicatorsAttributeName = "indicators"; + private const string FadeAttributeName = "fade"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ControlsAttributeName)] + public bool HasControls { get; set; } + + [HtmlAttributeName(IndicatorsAttributeName)] + public bool HasIndicators { get; set; } + + [HtmlAttributeName(FadeAttributeName)] + public bool IsFade { get; set; } + + [HtmlAttributeNotBound] + public List Items { get; } = new List(); + + [HtmlAttributeNotBound] + public string Id { get; set; } = guid.NewGuid().ToString("N")[..8]; + + #endregion + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("carousel slide"); + output.AddDataAttribute("ride", "carousel"); + + // Fade + if (this.IsFade) + { + output.AddCssClass("carousel-fade"); + } + + // Items + output.Content.SetHtmlContent(await output.GetChildContentAsync()); + + // Indicators + if (this.HasIndicators) + { + output.PreContent.AppendHtml("
      "); + for (int i = 0; i < this.Items.Count; i++) + { + output.PreContent.AppendHtml( + this.Items[i].Active + ? $"
    1. " + : $"
    2. " + ); + } + output.PreContent.AppendHtml("
    "); + } + + // Item wrapper + output.Content.Wrap( + new TagBuilder("div") { Attributes = { { "class", "carousel-inner" } } } + ); + + // Controls + if (this.HasControls) + { + output.PostContent.AppendHtml( + $"PreviousNext" + ); + } + } + } +} diff --git a/src/TagHelpers/Components/AlertTagHelper.cs b/src/TagHelpers/Components/AlertTagHelper.cs new file mode 100644 index 00000000..aeeb7a48 --- /dev/null +++ b/src/TagHelpers/Components/AlertTagHelper.cs @@ -0,0 +1,82 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Components +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + using System.Text.RegularExpressions; + using System.Threading.Tasks; + + [OutputElementHint("div")] + public class AlertTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string ColorAttributeName = "color"; + private const string DismissibleAttributeName = "dismissible"; + private const string DisableLinkStylingAttributeName = "disable-link-styling"; + private const string TitleAttributeName = "title"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ColorAttributeName)] + public Color Color { get; set; } = Color.Primary; + + [HtmlAttributeName(DismissibleAttributeName)] + public bool Dismissible { get; set; } + + [HtmlAttributeName(DisableLinkStylingAttributeName)] + public bool DisableLinkStyling { get; set; } + + [HtmlAttributeName(TitleAttributeName)] + public string Title { get; set; } + + #endregion + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("alert"); + output.MergeAttribute("role", "alert"); + + // Color + if (this.Color != Color.None) + { + output.AddCssClass($"alert-{this.Color.GetColorInfo().Name}"); + } + + // Title + if (!IsNullOrEmpty(this.Title)) + { + output.PreContent.AppendHtml($"

    {this.Title}

    "); + } + + // Dismissible + if (this.Dismissible) + { + output.AddCssClass("alert-dismissible"); + output.PreContent.SetHtmlContent( + $"" + ); + } + + // Disable Link Styling + if (!DisableLinkStyling) + { + var content = await output.GetChildContentAsync(true); + output.Content.SetHtmlContent( + Regex.Replace(content.GetContent(), "]+)?>", this.AddLinkStyle) + ); + } + } + + private string AddLinkStyle(Match match) + { + if (match.ToString().Contains("class=\"")) + { + return match.ToString().Replace("class=\"", "class=\"alert-link "); + } + + return " actionContextAccessor; + + #region --- Attribute Names --- + + private const string ImageAttributeName = "image"; + private const string WidthAttributeName = "width"; + private const string HeightAttributeName = "height"; + private const string CaptionAttributeName = "caption"; + private const string HorizontalAligmentAttributeName = "alignment"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ImageAttributeName)] + [ConvertVirtualUrl] + public string Image { get; set; } + + [HtmlAttributeName(CaptionAttributeName)] + public string Caption { get; set; } + + [HtmlAttributeName(HorizontalAligmentAttributeName)] + public HorizontalAlignment HorizontalAlignment { get; set; } + + [HtmlAttributeName(WidthAttributeName)] + public int Width { get; set; } + + [HtmlAttributeName(HeightAttributeName)] + public int Height { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "figure"; + output.TagMode = TagMode.StartTagAndEndTag; + output.AddCssClass("figure"); + + // Image + TagBuilder img = new TagBuilder("img") { TagRenderMode = TagRenderMode.SelfClosing }; + img.AddCssClass("figure-img img-fluid rounded"); + img.Attributes.Add("src", this.Image); + if (this.Width > 0) + img.Attributes.Add("width", this.Width.ToString()); + if (this.Height > 0) + img.Attributes.Add("height", this.Height.ToString()); + output.PreContent.AppendHtml(img); + + // Caption + TagBuilder figcaption = new TagBuilder("figcaption"); + figcaption.AddCssClass("figure-caption"); + figcaption.InnerHtml.Append(this.Caption); + + // Alignment + if (this.HorizontalAlignment != HorizontalAlignment.Default) + { + figcaption.AddCssClass($"text-{this.HorizontalAlignment.GetEnumInfo().Name}"); + } + + output.PostContent.AppendHtml(figcaption); + } + } +} diff --git a/src/TagHelpers/Components/ImageTagHelper.cs b/src/TagHelpers/Components/ImageTagHelper.cs new file mode 100644 index 00000000..cf4ec3f6 --- /dev/null +++ b/src/TagHelpers/Components/ImageTagHelper.cs @@ -0,0 +1,61 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Components +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("img")] + [OutputElementHint("img")] + public class ImageTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string ThumbnailAttributeName = "thumbnail"; + private const string ResponsiveAttributeName = "responsive"; + private const string RoundAttributeName = "round"; + private const string CircleAttributeName = "circle"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ThumbnailAttributeName)] + public bool Thumbnail { get; set; } + + [HtmlAttributeName(ResponsiveAttributeName)] + public bool Responsive { get; set; } + + [HtmlAttributeName(RoundAttributeName)] + public bool Round { get; set; } + + [HtmlAttributeName(CircleAttributeName)] + public bool Circle { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + // Thumbnail + if (this.Thumbnail) + { + output.AddCssClass("img-thumbnail"); + } + + // Round + if (this.Round) + { + output.AddCssClass("rounded"); + } + + // Circle + if (this.Circle) + { + output.AddCssClass("rounded-circle"); + } + + // Responsive + if (this.Responsive) + { + output.AddCssClass("img-fluid"); + } + } + } +} diff --git a/src/TagHelpers/Components/JumbotronTagHelper.cs b/src/TagHelpers/Components/JumbotronTagHelper.cs new file mode 100644 index 00000000..12142750 --- /dev/null +++ b/src/TagHelpers/Components/JumbotronTagHelper.cs @@ -0,0 +1,36 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Components +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("div")] + public class JumbotronTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string FluidAttributeName = "fluid"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(FluidAttributeName)] + public bool IsFluid { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("jumbotron"); + + // Full Width + if (this.IsFluid) + { + output.AddCssClass("jumbotron-fluid"); + + output.PreContent.SetHtmlContent(@"
    "); + output.PostContent.SetHtmlContent(@"
    "); + } + } + } +} diff --git a/src/TagHelpers/Components/PopoverTagHelper.cs b/src/TagHelpers/Components/PopoverTagHelper.cs new file mode 100644 index 00000000..cc10f602 --- /dev/null +++ b/src/TagHelpers/Components/PopoverTagHelper.cs @@ -0,0 +1,58 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Components +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("*", Attributes = PopoverAttributeName)] + public class PopoverTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string PopoverAttributeName = "popover"; + private const string TitleAttributeName = PopoverAttributeName + "-title"; + private const string DismissibleAttributeName = PopoverAttributeName + "-dismissible"; + private const string DelayAttributeName = PopoverAttributeName + "-delay"; + private const string PlacementAttributeName = PopoverAttributeName + "-placement"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(TitleAttributeName)] + public string Title { get; set; } + + [HtmlAttributeName(PopoverAttributeName)] + [CopyToOutput("data-content")] + public string Content { get; set; } + + [HtmlAttributeName(PlacementAttributeName)] + public Placement Placement { get; set; } = Placement.Top; + + [HtmlAttributeName(DismissibleAttributeName)] + public bool Dismissible { get; set; } + + [HtmlAttributeName(DelayAttributeName)] + [CopyToOutput("data-delay")] + public int Delay { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.AddDataAttribute("toggle", "popover"); + output.AddDataAttribute("container", "body"); + output.AddDataAttribute("placement", this.Placement.GetEnumInfo().Name); + + // Title + if (!IsNullOrEmpty(this.Title)) + { + output.MergeAttribute("title", this.Title); + } + + // Dismissible + if (this.Dismissible) + { + output.AddDataAttribute("trigger", "focus"); + } + } + } +} diff --git a/src/TagHelpers/Components/ProgressTagHelper.cs b/src/TagHelpers/Components/ProgressTagHelper.cs new file mode 100644 index 00000000..3100191a --- /dev/null +++ b/src/TagHelpers/Components/ProgressTagHelper.cs @@ -0,0 +1,86 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Components +{ + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("progress", TagStructure = TagStructure.WithoutEndTag)] + [OutputElementHint("div")] + public class ProgressTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string ValueAttributeName = "value"; + private const string LabelAttributeName = "label"; + private const string HeightAttributeName = "height"; + private const string ColorAttributeName = "color"; + private const string StripedAttributeName = "striped"; + private const string AnimatedAttributeName = "animated"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ValueAttributeName)] + public int Value { get; set; } + + [HtmlAttributeName(LabelAttributeName)] + public bool HasLabel { get; set; } + + [HtmlAttributeName(HeightAttributeName)] + public int Height { get; set; } = 16; + + [HtmlAttributeName(ColorAttributeName)] + public Color Color { get; set; } = Color.Primary; + + [HtmlAttributeName(StripedAttributeName)] + public bool IsStriped { get; set; } + + [HtmlAttributeName(AnimatedAttributeName)] + public bool IsAnimated { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.TagMode = TagMode.StartTagAndEndTag; + output.AddCssClass("progress"); + + // Height + output.AddCssStyle("height", $"{this.Height}px"); + + output.Content.SetHtmlContent(this.BuildProgressbar()); + } + + private TagBuilder BuildProgressbar() + { + TagBuilder progressbar = new TagBuilder("div"); + progressbar.AddCssClass($"progress-bar"); + progressbar.AddCssClass(this.Color.GetColorInfo().BackgroundCssClass); + + progressbar.MergeAttribute("aria-valuemin", "0"); + progressbar.MergeAttribute("aria-valuemax", "100"); + progressbar.MergeAttribute("aria-valuenow", this.Value.ToString()); + progressbar.MergeAttribute("role", "progressbar"); + progressbar.MergeAttribute("style", $"width: {this.Value}%;"); + + // Label + if (this.HasLabel) + { + progressbar.InnerHtml.Append($"{this.Value}%"); + } + + // Animated and Striped + if (this.IsAnimated) + { + progressbar.AddCssClass("progress-bar-striped progress-bar-animated"); + } + else if (this.IsStriped) + { + progressbar.AddCssClass("progress-bar-striped"); + } + + return progressbar; + } + } +} diff --git a/src/TagHelpers/Components/TooltipTagHelper.cs b/src/TagHelpers/Components/TooltipTagHelper.cs new file mode 100644 index 00000000..b6b10c45 --- /dev/null +++ b/src/TagHelpers/Components/TooltipTagHelper.cs @@ -0,0 +1,56 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Components +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + /// + /// A tag helper for tooltips. + /// + /// + /// + /// For performance reasons, the Tooltip and Popover data-apis are opt-in, + /// meaning you must initialize them yourself: + /// $(function () { $('[data-toggle="tooltip"]').tooltip() }) + /// + [HtmlTargetElement("*", Attributes = TooltipAttributeName)] + public class TooltipTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string TooltipAttributeName = "tooltip"; + private const string HtmlAttributeName = TooltipAttributeName + "-html"; + private const string PlacementAttributeName = TooltipAttributeName + "-placement"; + private const string AnimationAttributeName = TooltipAttributeName + "-animation"; + private const string DelayAttributeName = TooltipAttributeName + "-delay"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(TooltipAttributeName)] + [CopyToOutput("title")] + public string Tooltip { get; set; } + + [HtmlAttributeName(HtmlAttributeName)] + public bool IsHtml { get; set; } + + [HtmlAttributeName(PlacementAttributeName)] + public Placement Placement { get; set; } = Placement.Top; + + [HtmlAttributeName(AnimationAttributeName)] + public bool Animation { get; set; } = true; + + [HtmlAttributeName(DelayAttributeName)] + public int Delay { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.AddDataAttribute("toggle", "tooltip"); + output.AddDataAttribute("placement", this.Placement.GetEnumInfo().Name); + output.AddDataAttribute("animation", this.Animation ? "true" : "false"); + output.AddDataAttribute("delay", this.Delay.ToString()); + output.AddDataAttribute("html", this.IsHtml ? "true" : "false"); + } + } +} diff --git a/src/TagHelpers/Dgmjr.AspNetCore.TagHelpers.csproj b/src/TagHelpers/Dgmjr.AspNetCore.TagHelpers.csproj new file mode 100644 index 00000000..e9fc6a75 --- /dev/null +++ b/src/TagHelpers/Dgmjr.AspNetCore.TagHelpers.csproj @@ -0,0 +1,70 @@ + + + + net6.0;net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/TagHelpers/Dgmjr.AspNetCore.TagHelpers.sln b/src/TagHelpers/Dgmjr.AspNetCore.TagHelpers.sln new file mode 100644 index 00000000..98527124 --- /dev/null +++ b/src/TagHelpers/Dgmjr.AspNetCore.TagHelpers.sln @@ -0,0 +1,56 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B283EBC2-E01F-412D-9339-FD56EF114549}" + ProjectSection(SolutionItems) = preProject + ..\..\Directory.Build.props = ..\..\Directory.Build.props + ..\..\..\..\Directory.Build.targets = ..\..\..\..\Directory.Build.targets + ..\..\..\..\global.json = ..\..\..\..\global.json + ..\..\..\..\Packages\Versions.Local.props = ..\..\..\..\Packages\Versions.Local.props + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Abstractions", "..\..\..\Types\Abstractions\Dgmjr.Abstractions.csproj", "{A2F9676F-D2B3-436D-99A7-68FF47AC8741}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.AspNetCore.TagHelpers", "Dgmjr.AspNetCore.TagHelpers.csproj", "{C5A6C7E0-B36A-472D-865A-BD32170F17CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Local|Any CPU = Local|Any CPU + Debug|Any CPU = Debug|Any CPU + Testing|Any CPU = Testing|Any CPU + Staging|Any CPU = Staging|Any CPU + Production|Any CPU = Production|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Local|Any CPU.ActiveCfg = Local|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Local|Any CPU.Build.0 = Local|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Testing|Any CPU.Build.0 = Testing|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Staging|Any CPU.Build.0 = Staging|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Production|Any CPU.ActiveCfg = Local|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Production|Any CPU.Build.0 = Local|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2F9676F-D2B3-436D-99A7-68FF47AC8741}.Release|Any CPU.Build.0 = Release|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Local|Any CPU.ActiveCfg = Local|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Local|Any CPU.Build.0 = Local|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Testing|Any CPU.Build.0 = Testing|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Staging|Any CPU.Build.0 = Staging|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Production|Any CPU.ActiveCfg = Local|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Production|Any CPU.Build.0 = Local|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5A6C7E0-B36A-472D-865A-BD32170F17CE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {54A3E706-7474-47BC-AF1B-8616B6D274BB} + EndGlobalSection +EndGlobal diff --git a/src/TagHelpers/DgmjrTagHelperBase.cs b/src/TagHelpers/DgmjrTagHelperBase.cs new file mode 100644 index 00000000..af5292f2 --- /dev/null +++ b/src/TagHelpers/DgmjrTagHelperBase.cs @@ -0,0 +1,117 @@ +/* + * DgmjrTagHelperBase.cs + * + * Created: 2024-40-15T16:40:24-05:00 + * Modified: 2024-36-15T17:36:43-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +using Microsoft.Extensions.DependencyInjection; + +namespace Dgmjr.AspNetCore.TagHelpers; + +public class DgmjrTagHelperBase( + string tagName, + IActionContextAccessor actionContextAccessor = default +) : TagHelper, IHaveAnActionContextAccessor +{ + protected const string AttributePrefix = "dgmjr-"; + + [HtmlAttributeNotBound] + public string TagName { get; set; } = tagName; + + private const string DisableBootstrapAttributeName = "disable-bootstrap"; + + [HtmlAttributeName(DisableBootstrapAttributeName)] + [HtmlAttributeNotBound] + [HtmlAttributeMinimizable] + public bool DisableBootstrap { get; set; } + + [HtmlAttributeNotBound] + public string GeneratedId { get; set; } + + [CopyToOutput] + public string Id { get; set; } + + [CopyToOutput] + public string Name { get; set; } + + [HtmlAttributeNotBound] + public TagHelperOutput Output { get; set; } + + [HtmlAttributeNotBound] + public IActionContextAccessor ActionContextAccessor + { + get => _actionContextAccessor ??= Services.GetRequiredService(); + set => _actionContextAccessor = value; + } + private IActionContextAccessor _actionContextAccessor; + + protected IServiceProvider Services => ViewContext.HttpContext.RequestServices; + + [ViewContext] + [HtmlAttributeNotBound] + public ViewContext ViewContext { get; set; } + + private IUrlHelper _urlHelper; + + [HtmlAttributeNotBound] + public IUrlHelper UrlHelper + { + get => + _urlHelper ??= Services + .GetRequiredService() + .GetUrlHelper(ViewContext); + set => _urlHelper = value; + } + + // public override void Init(TagHelperContext context) + // { + // this.SetContexts(context); + // this.SetContext(context); + // this.FillMinimizableAttributes(context); + // this.ConvertUrls(ActionContextAccessor); + // } + + // public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + // { + // Output = output; + // if (!DisableBootstrap) + // { + // this.CopyPropertiesToOutput(output); + // this.CheckMandatoryProperties(); + // this.CopyIdentifier(); + // RenderProcess(context, output); + // this.RenderIdentifier(this, output); + // RemoveMinimizableAttributes(output); + // } + // } + + // public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + // { + // Output = output; + // if (!DisableBootstrap) + // { + // CopyToOutputAttribute.CopyPropertiesToOutput(this, output); + // MandatoryAttribute.CheckProperties(this); + // GenerateIdAttribute.CopyIdentifier(this); + // await RenderProcessAsync(context, output); + // GenerateIdAttribute.RenderIdentifier(this, output); + // RemoveMinimizableAttributes(output); + // } + // } + + protected virtual void RenderProcess(TagHelperContext context, TagHelperOutput output) { } + + protected virtual async Task RenderProcessAsync( + TagHelperContext context, + TagHelperOutput output + ) + { + RenderProcess(context, output); + } +} diff --git a/src/TagHelpers/Enumerations/ButtonType.cs b/src/TagHelpers/Enumerations/ButtonType.cs new file mode 100644 index 00000000..c0be3f3b --- /dev/null +++ b/src/TagHelpers/Enumerations/ButtonType.cs @@ -0,0 +1,13 @@ +using InputType = Dgmjr.AspNetCore.TagHelpers.Enumerations.InputType; + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum ButtonType +{ + Submit = InputType.Submit, + submit = InputType.Submit, + Reset = InputType.Reset, + reset = InputType.Reset, + Button = InputType.Button, + button = InputType.Button, +} diff --git a/src/TagHelpers/Enumerations/CardImagePosition.cs b/src/TagHelpers/Enumerations/CardImagePosition.cs new file mode 100644 index 00000000..12d5f9e5 --- /dev/null +++ b/src/TagHelpers/Enumerations/CardImagePosition.cs @@ -0,0 +1,24 @@ +/* + * CardImagePosition.cs + * + * Created: 2024-39-17T10:39:12-05:00 + * Modified: 2024-39-17T10:39:12-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum CardImagePosition +{ + None = 0, + Top, + top = Top, + Bottom, + bottom = Bottom, + Overlay, + overlay = Overlay, +} diff --git a/src/TagHelpers/Enumerations/Color.cs b/src/TagHelpers/Enumerations/Color.cs new file mode 100644 index 00000000..70d1c294 --- /dev/null +++ b/src/TagHelpers/Enumerations/Color.cs @@ -0,0 +1,91 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum Color +{ + None = 0, + none = None, + Unspecified = None, + unspecified = None, + + [ColorInfo( + "primary", + TextCssClass = "text-primary", + BackgroundCssClass = "bg-primary", + BorderCssClass = "border-primary" + )] + Primary, + primary = Primary, + + [ColorInfo( + "secondary", + TextCssClass = "text-secondary", + BackgroundCssClass = "bg-secondary", + BorderCssClass = "border-secondary" + )] + Secondary, + secondary = Secondary, + + [ColorInfo( + "success", + TextCssClass = "text-success", + BackgroundCssClass = "bg-success", + BorderCssClass = "border-success" + )] + Success, + success = Success, + + [ColorInfo( + "danger", + TextCssClass = "text-danger", + BackgroundCssClass = "bg-danger", + BorderCssClass = "border-danger" + )] + Danger, + danger = Danger, + + [ColorInfo( + "warning", + TextCssClass = "text-warning", + BackgroundCssClass = "bg-warning", + BorderCssClass = "border-warning" + )] + Warning, + warning = Warning, + + [ColorInfo( + "info", + TextCssClass = "text-info", + BackgroundCssClass = "bg-info", + BorderCssClass = "border-info" + )] + Info, + info = Info, + + [ColorInfo( + "light", + TextCssClass = "text-light", + BackgroundCssClass = "bg-light", + BorderCssClass = "border-light" + )] + Light, + light = Light, + + [ColorInfo( + "dark", + TextCssClass = "text-dark", + BackgroundCssClass = "bg-dark", + BorderCssClass = "border-dark" + )] + Dark, + dark = Dark, + + [ColorInfo( + "white", + TextCssClass = "text-white", + BackgroundCssClass = "bg-white", + BorderCssClass = "border-white" + )] + White, + Link, + link = Link +} diff --git a/src/TagHelpers/Enumerations/HeadingType.cs b/src/TagHelpers/Enumerations/HeadingType.cs new file mode 100644 index 00000000..313e82c3 --- /dev/null +++ b/src/TagHelpers/Enumerations/HeadingType.cs @@ -0,0 +1,30 @@ +/* + * HeadingType.cs + * + * Created: 2024-04-16T02:04:27-05:00 + * Modified: 2024-04-16T02:04:27-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum HeadingType +{ + None = 0, + h1 = 1, + H1 = h1, + h2 = 2, + H2 = h2, + h3 = 3, + H3 = h3, + h4 = 4, + H4 = h4, + h5 = 5, + H5 = h5, + h6 = 6, + H6 = h6 +} diff --git a/src/TagHelpers/Enumerations/HorizontalAlignment.cs b/src/TagHelpers/Enumerations/HorizontalAlignment.cs new file mode 100644 index 00000000..fbf0be16 --- /dev/null +++ b/src/TagHelpers/Enumerations/HorizontalAlignment.cs @@ -0,0 +1,27 @@ +/* + * HorizontalAlignment.cs + * + * Created: 2024-36-17T10:36:00-05:00 + * Modified: 2024-36-17T10:36:00-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum HorizontalAlignment +{ + Default, + + [EnumInfo("left")] + Left, + + [EnumInfo("center")] + Center, + + [EnumInfo("right")] + Right +} diff --git a/src/TagHelpers/Enumerations/InputType.cs b/src/TagHelpers/Enumerations/InputType.cs new file mode 100644 index 00000000..54216c64 --- /dev/null +++ b/src/TagHelpers/Enumerations/InputType.cs @@ -0,0 +1,47 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum InputType +{ + Text, + text = Text, + Password, + password = Password, + Email, + email = Email, + Number, + number = Number, + Url, + url = Url, + Search, + search = Search, + Tel, + tel = Tel, + Date, + date = Date, + Time, + time = Time, + DateTime, + datetime = DateTime, + DateTimeLocal, + datetimelocal = DateTimeLocal, + Month, + month = Month, + Week, + week = Week, + Color, + color = Color, + File, + file = File, + Range, + range = Range, + Hidden, + hidden = Hidden, + Image, + image = Image, + Submit, + submit = Submit, + Reset, + reset = Reset, + Button, + button = Button +} diff --git a/src/TagHelpers/Enumerations/LabelPosition.cs b/src/TagHelpers/Enumerations/LabelPosition.cs new file mode 100644 index 00000000..85e9542e --- /dev/null +++ b/src/TagHelpers/Enumerations/LabelPosition.cs @@ -0,0 +1,12 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum LabelPosition +{ + Default, + Floating, + floating = Floating, + Left, + left = Left, + Right, + right = Right, +} diff --git a/src/TagHelpers/Enumerations/ModalPosition.cs b/src/TagHelpers/Enumerations/ModalPosition.cs new file mode 100644 index 00000000..04365582 --- /dev/null +++ b/src/TagHelpers/Enumerations/ModalPosition.cs @@ -0,0 +1,19 @@ +/* + * ModalPosition.cs + * + * Created: 2024-31-17T10:31:52-05:00 + * Modified: 2024-31-17T10:31:52-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum ModalPosition +{ + Top, + Center +} diff --git a/src/TagHelpers/Enumerations/NavbarPosition.cs b/src/TagHelpers/Enumerations/NavbarPosition.cs new file mode 100644 index 00000000..a4fbe126 --- /dev/null +++ b/src/TagHelpers/Enumerations/NavbarPosition.cs @@ -0,0 +1,30 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum Position +{ + None = 0, + + [EnumInfo("fixed-top")] + FixedTop, + + [EnumInfo("fixed-top")] + fixedtop = FixedTop, + + [EnumInfo("fixed-bottom")] + FixedBottom, + + [EnumInfo("fixed-bottom")] + fixedbottom = FixedBottom, + + [EnumInfo("sticky-top")] + StickyTop, + + [EnumInfo("sticky-top")] + stickytop = StickyTop, + + [EnumInfo("sticky-bottom")] + StickyBottom, + + [EnumInfo("sticky-bottom")] + stickybottom = StickyBottom +} diff --git a/src/TagHelpers/Enumerations/PanelType.cs b/src/TagHelpers/Enumerations/PanelType.cs new file mode 100644 index 00000000..5f463a06 --- /dev/null +++ b/src/TagHelpers/Enumerations/PanelType.cs @@ -0,0 +1,12 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations +{ + public enum PanelType + { + Default, + Primary, + Success, + Info, + Warning, + Danger + } +} diff --git a/src/TagHelpers/Enumerations/Placement.cs b/src/TagHelpers/Enumerations/Placement.cs new file mode 100644 index 00000000..be70aaf5 --- /dev/null +++ b/src/TagHelpers/Enumerations/Placement.cs @@ -0,0 +1,28 @@ +/* + * Placement.cs + * + * Created: 2024-36-17T10:36:46-05:00 + * Modified: 2024-36-17T10:36:47-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum Placement +{ + [EnumInfo("top")] + Top, + + [EnumInfo("bottom")] + Bottom, + + [EnumInfo("left")] + Left, + + [EnumInfo("right")] + Right +} diff --git a/src/TagHelpers/Enumerations/Size.cs b/src/TagHelpers/Enumerations/Size.cs new file mode 100644 index 00000000..00d3c912 --- /dev/null +++ b/src/TagHelpers/Enumerations/Size.cs @@ -0,0 +1,24 @@ +/* + * Size.cs + * + * Created: 2024-30-17T10:30:45-05:00 + * Modified: 2024-30-17T10:30:45-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum Size +{ + Default, + + [EnumInfo("lg")] + Large, + + [EnumInfo("sm")] + Small +} diff --git a/src/TagHelpers/Enumerations/Theme.cs b/src/TagHelpers/Enumerations/Theme.cs new file mode 100644 index 00000000..dad72fab --- /dev/null +++ b/src/TagHelpers/Enumerations/Theme.cs @@ -0,0 +1,31 @@ +/* + * Theme.cs + * + * Created: 2024-52-15T17:52:35-05:00 + * Modified: 2024-52-15T17:52:35-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum Theme +{ + Default, + @default = Default, + + [EnumInfo("dark")] + Dark, + + [EnumInfo("dark")] + dark = Dark, + + [EnumInfo("light")] + Light, + + [EnumInfo("light")] + light = Light +} diff --git a/src/TagHelpers/Enumerations/VerticalAlignment.cs b/src/TagHelpers/Enumerations/VerticalAlignment.cs new file mode 100644 index 00000000..424b8a1e --- /dev/null +++ b/src/TagHelpers/Enumerations/VerticalAlignment.cs @@ -0,0 +1,27 @@ +/* + * VerticalAlignment.cs + * + * Created: 2024-23-17T10:23:10-05:00 + * Modified: 2024-23-17T10:23:10-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Enumerations; + +public enum VerticalAlignment +{ + Default = 0, + @default = Default, + Top = 1, + top = Top, + Middle = 2, + middle = Middle, + Bottom = 3, + bottom = Bottom, + Baseline = 4, + baseline = Baseline +} diff --git a/src/TagHelpers/Exceptions/MandatoryAttributeException.cs b/src/TagHelpers/Exceptions/MandatoryAttributeException.cs new file mode 100644 index 00000000..335c95a5 --- /dev/null +++ b/src/TagHelpers/Exceptions/MandatoryAttributeException.cs @@ -0,0 +1,33 @@ +/* + * MandatoryAttributeException.cs + * + * Created: 2024-35-15T17:35:45-05:00 + * Modified: 2024-35-15T17:35:45-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Exceptions; + +public class MandatoryAttributeException : Exception +{ + public string Attribute { get; set; } + + public Type TagHelper { get; set; } + + public MandatoryAttributeException(string attribute) + : base("The '" + attribute + "' attribute is mandatory and must be set.") + { + Attribute = attribute; + } + + public MandatoryAttributeException(string attribute, type tagHelper) + : base("The '" + attribute + "' attribute of the '" + tagHelper.Name + "' is mandatory and must be set.") + { + Attribute = attribute; + TagHelper = tagHelper; + } +} diff --git a/src/TagHelpers/Extensions/ColorInfoExtensions.cs b/src/TagHelpers/Extensions/ColorInfoExtensions.cs new file mode 100644 index 00000000..aa058466 --- /dev/null +++ b/src/TagHelpers/Extensions/ColorInfoExtensions.cs @@ -0,0 +1,52 @@ +/* + * ColorInfoExtensions.cs + * + * Created: 2024-00-15T18:00:46-05:00 + * Modified: 2024-00-15T18:00:46-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +using Dgmjr.AspNetCore.TagHelpers.Enumerations; + +internal static class ColorInfoExtensions +{ + public static ColorInfo GetColorInfo(this Color color) + { + var memberInfo = color.GetType().GetMember(color.ToString()).FirstOrDefault(); + + return memberInfo != null + ? new(memberInfo.GetCustomAttribute()) + : throw new InvalidOperationException( + "It is not possible to read ColorInfoAttribute from enumeration." + ); + } + + public static string? GetName(this Color color) + { + try + { + return color.GetColorInfo().Name ?? color.ToString(); + } + catch + { + return color.ToString(); + } + } +} + +public readonly record struct ColorInfo(ColorInfoAttribute? ColorInfoAttribute) +{ + public string Name => ColorInfoAttribute?.Name ?? string.Empty; + + public string TextCssClass => ColorInfoAttribute?.TextCssClass ?? string.Empty; + + public string BackgroundCssClass => ColorInfoAttribute?.BackgroundCssClass ?? string.Empty; + + public string BorderCssClass => ColorInfoAttribute?.BorderCssClass ?? string.Empty; +} diff --git a/src/TagHelpers/Extensions/ContextAttributeExtensions.cs b/src/TagHelpers/Extensions/ContextAttributeExtensions.cs new file mode 100644 index 00000000..2b5d229a --- /dev/null +++ b/src/TagHelpers/Extensions/ContextAttributeExtensions.cs @@ -0,0 +1,70 @@ +/* + * ContextAttributeExtensions.cs + * + * Created: 2024-06-16T00:06:13-05:00 + * Modified: 2024-06-16T00:06:13-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class ContextAttributeExtensions +{ + public static void SetContexts(this T target, TagHelperContext context) + where T : TagHelper + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + foreach ( + var item in from pi in typeof(T) + .GetTypeInfo() + .GetProperties( + BindingFlags.Instance + | BindingFlags.Static + | BindingFlags.Public + | BindingFlags.NonPublic + ) + where pi.HasCustomAttribute() + select pi + ) + { + var customAttribute = item.GetCustomAttribute(); + if (IsNullOrEmpty(customAttribute.Key)) + { + var contextItem = context.GetContextItem( + item.PropertyType, + customAttribute.UseInherited + ); + if (contextItem != null) + { + item.SetValue(target, contextItem); + } + if (customAttribute.RemoveContext) + { + context.RemoveContextItem(item.PropertyType, customAttribute.UseInherited); + } + } + else + { + item.SetValue( + target, + context.GetContextItem(item.PropertyType, customAttribute.Key) + ); + if (customAttribute.RemoveContext) + { + context.RemoveContextItem(customAttribute.Key); + } + } + } + } +} diff --git a/src/TagHelpers/Extensions/ContextClassAttributeExtensions.cs b/src/TagHelpers/Extensions/ContextClassAttributeExtensions.cs new file mode 100644 index 00000000..b003c5d5 --- /dev/null +++ b/src/TagHelpers/Extensions/ContextClassAttributeExtensions.cs @@ -0,0 +1,42 @@ +/* + * ContextClassAttributeExtensions.cs + * + * Created: 2024-04-16T00:04:30-05:00 + * Modified: 2024-04-16T00:04:30-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class ContextClassAttributeExtensions +{ + public static void SetContext(this T target, TagHelperContext context) + where T : TagHelper + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + var type = typeof(T); + var customAttribute = type.GetTypeInfo().GetCustomAttribute(); + if (customAttribute != null) + { + if (IsNullOrEmpty(customAttribute.Key)) + { + context.SetContextItem(customAttribute.Type ?? type, target); + } + else + { + context.SetContextItem(customAttribute.Key, target); + } + } + } +} diff --git a/src/TagHelpers/Extensions/ConvertUrlAttributeExtensions.cs b/src/TagHelpers/Extensions/ConvertUrlAttributeExtensions.cs new file mode 100644 index 00000000..cb70ae97 --- /dev/null +++ b/src/TagHelpers/Extensions/ConvertUrlAttributeExtensions.cs @@ -0,0 +1,45 @@ +/* + * ConvertUrlAttributeExtensions.cs + * + * Created: 2024-03-16T00:03:29-05:00 + * Modified: 2024-03-16T00:03:29-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class ConvertUrlAttributeExtensions +{ + public static void ConvertUrls(this T target, IActionContextAccessor accessor) + where T : TagHelper + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + var convertVirtualUrlAttributeProperties = (from pi in target.GetType().GetProperties() + where pi.HasCustomAttribute() + select pi).ToList(); + if (!convertVirtualUrlAttributeProperties.Any()) + { + return; + } + if (accessor == null) + { + throw new ArgumentNullException(nameof(accessor)); + } + var urlHelper = new UrlHelper(accessor.ActionContext); + foreach (var item in convertVirtualUrlAttributeProperties) + { + if (item.PropertyType != typeof(string)) + { + throw new ArgumentException("Decorated property must be a string"); + } + item.SetValue(target, urlHelper.Content((string)item.GetValue(target))); + } + } +} diff --git a/src/TagHelpers/Extensions/CopyToOutputExtensions.cs b/src/TagHelpers/Extensions/CopyToOutputExtensions.cs new file mode 100644 index 00000000..98d48893 --- /dev/null +++ b/src/TagHelpers/Extensions/CopyToOutputExtensions.cs @@ -0,0 +1,53 @@ +/* + * CopyToOutputExtensions.cs + * + * Created: 2024-00-16T00:00:28-05:00 + * Modified: 2024-00-16T00:00:28-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class CopyToOutputExtensions +{ + public static void CopyPropertiesToOutput(this T target, TagHelperOutput output) + where T : TagHelper + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + foreach ( + var item in from pI in target.GetType().GetProperties() + where pI.HasCustomAttribute() + select pI + ) + { + var obj = item.GetValue(target); + if (item.PropertyType.IsAssignableFrom(typeof(bool))) + { + obj = obj?.ToString().ToLower(); + } + foreach (var customAttribute in item.GetCustomAttributes()) + { + if (obj != null || customAttribute.CopyIfValueIsNull) + { + output.Attributes.Add( + customAttribute.Prefix + + (customAttribute.OutputAttributeName ?? item.GetHtmlAttributeName()) + + customAttribute.Suffix, + obj + ); + } + } + } + } +} diff --git a/src/TagHelpers/Extensions/EnumInfoExtensions.cs b/src/TagHelpers/Extensions/EnumInfoExtensions.cs new file mode 100644 index 00000000..477b10de --- /dev/null +++ b/src/TagHelpers/Extensions/EnumInfoExtensions.cs @@ -0,0 +1,40 @@ +/* + * EnumInfoExtensions.cs + * + * Created: 2024-54-15T17:54:58-05:00 + * Modified: 2024-54-15T17:54:58-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class EnumExtensions +{ + public static EnumInfoAttribute? GetEnumInfo(this Enum e) + { + var memberInfo = e.GetType().GetMember(e.ToString()).FirstOrDefault(); + if (memberInfo != null) + { + return memberInfo.GetCustomAttribute(); + } + throw new InvalidOperationException( + "It is not possible to read EnumInfoAttribute from enumeration." + ); + } + + public static string? GetName(this Enum e) + { + try + { + return e.GetEnumInfo()?.Name ?? e.ToString(); + } + catch + { + return e.ToString(); + } + } +} diff --git a/src/TagHelpers/Extensions/FluentTagBuilderExtensions.cs b/src/TagHelpers/Extensions/FluentTagBuilderExtensions.cs new file mode 100644 index 00000000..88393868 --- /dev/null +++ b/src/TagHelpers/Extensions/FluentTagBuilderExtensions.cs @@ -0,0 +1,109 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class FluentTagBuilderExtensions +{ + public static TagBuilder WithCssClass(this TagBuilder @this, string cssClass) + { + @this.AddCssClass(cssClass); + return @this; + } + + public static TagBuilder WithAttribute( + this TagBuilder @this, + string attributeName, + string attributeValue + ) + { + @this.Attributes[attributeName] = attributeValue; + return @this; + } + + public static TagBuilder WithAttributes(this TagBuilder @this, params string[] attributes) + { + foreach (var attribute in attributes) + { + var parts = attribute.Split('='); + if (parts.Length != 2) + { + throw new ArgumentException( + $"Attribute '{attribute}' is not in the expected format 'name=value'.", + nameof(attributes) + ); + } + + @this.WithAttribute(parts[0], parts[1]); + } + + return @this; + } + + public static TagBuilder AppendInnerHtml(this TagBuilder @this, string innerHtml) + { + @this.InnerHtml.Append(innerHtml); + return @this; + } + + public static TagBuilder AppendInnerHtml(this TagBuilder @this, TagBuilder innerHtml) + { + @this.InnerHtml.AppendHtml(innerHtml); + return @this; + } + + public static TagBuilder AppendInnerHtml(this TagBuilder @this, IHtmlContent innerHtml) + { + @this.InnerHtml.AppendHtml(innerHtml); + return @this; + } + + public static TagBuilder AppendInnerHtml( + this TagBuilder @this, + IEnumerable innerHtml + ) + { + foreach (var inner in innerHtml) + { + @this.InnerHtml.AppendHtml(inner); + } + + return @this; + } + + public static TagBuilder AppendInnerHtml(this TagBuilder @this, params TagBuilder[] innerHtml) + { + foreach (var inner in innerHtml) + { + @this.InnerHtml.AppendHtml(inner); + } + + return @this; + } + + public static TagBuilder AppendInnerHtml( + this TagBuilder @this, + IEnumerable innerHtml + ) + { + foreach (var inner in innerHtml) + { + @this.InnerHtml.AppendHtml(inner); + } + + return @this; + } + + public static TagBuilder AppendInnerHtml(this TagBuilder @this, params IHtmlContent[] innerHtml) + { + foreach (var inner in innerHtml) + { + @this.InnerHtml.AppendHtml(inner); + } + + return @this; + } + + public static TagBuilder WithInnerText(this TagBuilder @this, string innerText) + { + @this.InnerHtml.Append(innerText); + return @this; + } +} diff --git a/src/TagHelpers/Extensions/GeneratedIdExtensions.cs b/src/TagHelpers/Extensions/GeneratedIdExtensions.cs new file mode 100644 index 00000000..d25ffbbd --- /dev/null +++ b/src/TagHelpers/Extensions/GeneratedIdExtensions.cs @@ -0,0 +1,47 @@ +/* + * GeneratedIdExtensions.cs + * + * Created: 2024-16-16T00:16:23-05:00 + * Modified: 2024-16-16T00:16:23-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +using Dgmjr.AspNetCore.TagHelpers.Abstractions; + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class GeneratedIdExtensions +{ + public static void CopyIdentifier(this T tagHelper) + where T : TagHelper + { + var ihagi = tagHelper as IHaveAGeneratedId; + var iis = tagHelper as IHaveAWritableId; + if (ihagi is not null && iis is not null) + { + var generateIdAttribute = tagHelper + .GetType() + .GetTypeInfo() + .GetCustomAttributes(inherit: true) + .FirstOrDefault(); + if (IsNullOrEmpty(iis.Id) && generateIdAttribute?.RenderIdAttribute == true) + { + ihagi.GeneratedId = iis.Id; + iis.Id = ihagi.GeneratedId; + } + } + } + + public static void RenderIdentifier(this T tagHelper, TagHelperOutput output) + where T : IIdentifiable + { + if (!IsNullOrEmpty(tagHelper.Id)) + { + output.MergeAttribute("id", tagHelper.Id); + } + } +} diff --git a/src/TagHelpers/Extensions/InitExtensions.cs b/src/TagHelpers/Extensions/InitExtensions.cs new file mode 100644 index 00000000..2c32e4d4 --- /dev/null +++ b/src/TagHelpers/Extensions/InitExtensions.cs @@ -0,0 +1,39 @@ +/* + * InitExtensions.cs + * + * Created: 2024-57-16T00:57:54-05:00 + * Modified: 2024-58-16T00:58:20-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class InitExtensions +{ + public static T Init(this T @this, TagHelperContext context) + where T : TagHelper + { + @this.SetContexts(context); + @this.SetContext(context); + @this.FillMinimizableAttributes(context); + if(@this is IHaveAnActionContextAccessor ihaaaca) + { + @this.ConvertUrls(ihaaaca.ActionContextAccessor); + } + return @this; + } + + public static T Init(this T @this, TagHelperContext context, IActionContextAccessor actionContextAccessor) + where T : TagHelper + { + @this.SetContexts(context); + @this.SetContext(context); + @this.FillMinimizableAttributes(context); + @this.ConvertUrls(actionContextAccessor); + return @this; + } +} diff --git a/src/TagHelpers/Extensions/MandatoryPropertiesExtensions.cs b/src/TagHelpers/Extensions/MandatoryPropertiesExtensions.cs new file mode 100644 index 00000000..5168b86e --- /dev/null +++ b/src/TagHelpers/Extensions/MandatoryPropertiesExtensions.cs @@ -0,0 +1,40 @@ +/* + * MandatoryPropertiesExtensions.cs + * + * Created: 2024-13-16T00:13:49-05:00 + * Modified: 2024-13-16T00:13:49-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class MandatoryPropertiesExtensions +{ + public static void CheckMandatoryProperties(this T tagHelper) + where T : TagHelper + { + foreach (var item in from pi in tagHelper.GetType().GetProperties() + where pi.HasCustomAttribute() + select pi) + { + var value = item.GetValue(tagHelper); + var htmlAttributeName = item.GetHtmlAttributeName(); + if (item.PropertyType == typeof(string) && IsNullOrEmpty((string)value)) + { + throw new MandatoryAttributeException(htmlAttributeName, tagHelper.GetType()); + } + if (typeof(IEnumerable).IsAssignableFrom(item.PropertyType) && !((IEnumerable)value).GetEnumerator().MoveNext()) + { + throw new MandatoryAttributeException(htmlAttributeName, tagHelper.GetType()); + } + if (value == null) + { + throw new MandatoryAttributeException(htmlAttributeName, tagHelper.GetType()); + } + } + } +} diff --git a/src/TagHelpers/Extensions/MemberInfoExtensions.cs b/src/TagHelpers/Extensions/MemberInfoExtensions.cs new file mode 100644 index 00000000..ebf0d5a8 --- /dev/null +++ b/src/TagHelpers/Extensions/MemberInfoExtensions.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class MemberInfoExtensions +{ + public static bool HasCustomAttribute(this MemberInfo memberInfo) + { + return memberInfo.GetCustomAttributes(typeof(T), inherit: true).Any(); + } + + public static bool HasCustomAttribute(this MemberInfo memberInfo, Type attributeType) + { + return memberInfo.GetCustomAttributes(attributeType, inherit: true).Any(); + } + + public static string GetHtmlAttributeName(this MemberInfo property) + { + var customAttribute = property.GetCustomAttribute(); + if (customAttribute != null) + { + return customAttribute.Name; + } + return Regex.Replace(property.Name, "([A-Z])", "-$1").ToLower().Trim('-'); + } +} diff --git a/src/TagHelpers/Extensions/MinimizableAttributeExtensions.cs b/src/TagHelpers/Extensions/MinimizableAttributeExtensions.cs new file mode 100644 index 00000000..d1156fc0 --- /dev/null +++ b/src/TagHelpers/Extensions/MinimizableAttributeExtensions.cs @@ -0,0 +1,63 @@ +/* + * MinimizableAttributeExtensions.cs + * + * Created: 2024-07-16T00:07:41-05:00 + * Modified: 2024-07-16T00:07:41-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class MinimizableAttributeExtensions +{ + public static void FillMinimizableAttributes(this T target, TagHelperContext context) + where T : TagHelper + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + foreach ( + var item in from pI in target.GetType().GetProperties() + where pI.GetCustomAttribute() != null + select pI + ) + { + var htmlAttributeName = item.GetHtmlAttributeName(); + if (context.AllAttributes.ContainsName(htmlAttributeName)) + { + var tagHelperAttribute = context.AllAttributes[htmlAttributeName]; + if (tagHelperAttribute.Value is bool) + { + item.SetValue(target, tagHelperAttribute.Value); + } + else + { + item.SetValue( + target, + !(tagHelperAttribute.Value ?? "").ToString().Equals("false") + ); + } + } + } + } + + public static void RemoveMinimizableAttributes(this T @this, TagHelperOutput output) + { + output.Attributes.RemoveAll( + ( + from pI in typeof(T).GetTypeInfo().GetProperties() + where pI.GetCustomAttribute() != null + select pI.GetHtmlAttributeName() + ).ToArray() + ); + } +} diff --git a/src/TagHelpers/Extensions/ProcessTagHelperExtensions.cs b/src/TagHelpers/Extensions/ProcessTagHelperExtensions.cs new file mode 100644 index 00000000..de3e3331 --- /dev/null +++ b/src/TagHelpers/Extensions/ProcessTagHelperExtensions.cs @@ -0,0 +1,31 @@ +/* + * ProcessTagHelperExtensions.cs + * + * Created: 2024-30-16T00:30:37-05:00 + * Modified: 2024-30-16T00:30:37-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class ProcessTagHelperExtensions +{ + public static T Process(this T @this, Action process, TagHelperContext context, TagHelperOutput output) + where T : TagHelper + { + @this.CopyPropertiesToOutput(output); + @this.CheckMandatoryProperties(); + @this.CopyIdentifier(); + process(context, output); + if(@this is IIdentifiable iis) + { + iis.RenderIdentifier(output); + } + @this.RemoveMinimizableAttributes(output); + return @this; + } +} diff --git a/src/TagHelpers/Extensions/TagHelperAttributeListExtensions.cs b/src/TagHelpers/Extensions/TagHelperAttributeListExtensions.cs new file mode 100644 index 00000000..a8ec5dd7 --- /dev/null +++ b/src/TagHelpers/Extensions/TagHelperAttributeListExtensions.cs @@ -0,0 +1,31 @@ +/* + * TagHelperAttributeListExtensions.cs + * + * Created: 2024-43-15T17:43:40-05:00 + * Modified: 2024-43-15T17:43:40-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class TagHelperAttributeListExtensions +{ + public static bool RemoveAll(this TagHelperAttributeList attributeList, params string[] attributeNames) + { + return attributeNames.Aggregate(seed: false, (bool current, string name) => attributeList.RemoveAll(name) || current); + } + + public static void AddAriaAttribute(this TagHelperAttributeList attributeList, string attributeName, object value) + { + attributeList.Add("aria-" + attributeName, value); + } + + public static void AddDataAttribute(this TagHelperAttributeList attributeList, string attributeName, object value) + { + attributeList.Add("data-" + attributeName, value); + } +} diff --git a/src/TagHelpers/Extensions/TagHelperContentExtensions.cs b/src/TagHelpers/Extensions/TagHelperContentExtensions.cs new file mode 100644 index 00000000..dc03603e --- /dev/null +++ b/src/TagHelpers/Extensions/TagHelperContentExtensions.cs @@ -0,0 +1,88 @@ +/* + * TagHelperContentExtensions.cs + * + * Created: 2024-26-15T17:26:55-05:00 + * Modified: 2024-26-15T17:26:55-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + + +public static class TagHelperContentExtensions +{ + public static void Prepend(this TagHelperContent content, string value) + { + if (content.IsEmptyOrWhiteSpace) + { + content.SetContent(value); + } + else + { + content.SetContent(value + content.GetContent()); + } + } + + public static void PrependHtml(this TagHelperContent content, string value) + { + if (content.IsEmptyOrWhiteSpace) + { + content.SetHtmlContent(value); + } + else + { + content.SetHtmlContent(value + content.GetContent()); + } + } + + public static void Prepend(this TagHelperContent content, IHtmlContent value) + { + if (content.IsEmptyOrWhiteSpace) + { + content.SetHtmlContent(value); + content.AppendLine(); + } + else + { + string content2 = content.GetContent(); + content.SetHtmlContent(value); + content.AppendHtml(content2); + } + } + + public static void Prepend(this TagHelperContent content, TagHelperOutput output) + { + content.Prepend(output.ToTagHelperContent()); + } + + public static void Wrap(this TagHelperContent content, TagBuilder builder) + { + builder.TagRenderMode = TagRenderMode.StartTag; + Wrap(content, builder, new TagBuilder(builder.TagName) + { + TagRenderMode = TagRenderMode.EndTag + }); + } + + public static void Wrap(TagHelperContent content, IHtmlContent contentStart, IHtmlContent contentEnd) + { + content.Prepend(contentStart); + content.AppendHtml(contentEnd); + } + + public static void Wrap(TagHelperContent content, string contentStart, string contentEnd) + { + content.Prepend(contentStart); + content.Append(contentEnd); + } + + public static void WrapHtml(TagHelperContent content, string contentStart, string contentEnd) + { + content.PrependHtml(contentStart); + content.AppendHtml(contentEnd); + } +} diff --git a/src/TagHelpers/Extensions/TagHelperContextExtensions.cs b/src/TagHelpers/Extensions/TagHelperContextExtensions.cs new file mode 100644 index 00000000..9a692fc3 --- /dev/null +++ b/src/TagHelpers/Extensions/TagHelperContextExtensions.cs @@ -0,0 +1,229 @@ +/* + * TagHelperContextExtensions.cs + * + * Created: 2024-34-15T17:34:21-05:00 + * Modified: 2024-34-15T17:34:21-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class TagHelperContextExtensions +{ + public static bool HasContextItem(this TagHelperContext context) + { + return context.HasContextItem(useInherited: true); + } + + public static bool HasContextItem(this TagHelperContext context, bool useInherited) + { + return context.HasContextItem(typeof(T), useInherited); + } + + public static bool HasContextItem(this TagHelperContext context, Type type) + { + return context.HasContextItem(type, useInherited: true); + } + + public static bool HasContextItem(this TagHelperContext context, Type type, bool useInherited) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + if (type == null) + { + throw new ArgumentNullException("type"); + } + var contextItem = context.GetContextItem(type, useInherited); + if (contextItem != null) + { + return type.IsInstanceOfType(contextItem); + } + return false; + } + + public static bool HasContextItem(this TagHelperContext context, string key) + { + return context.HasContextItem(typeof(T), key); + } + + public static bool HasContextItem(this TagHelperContext context, Type type, string key) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + if (type == null) + { + throw new ArgumentNullException("type"); + } + if (key == null) + { + throw new ArgumentNullException("key"); + } + if (context.Items.ContainsKey(key)) + { + return type.IsInstanceOfType(context.Items[key]); + } + return false; + } + + public static T GetContextItem(this TagHelperContext context) where T : class + { + return context.GetContextItem(useInherited: true); + } + + public static T GetContextItem(this TagHelperContext context, bool useInherited) where T : class + { + return context.GetContextItem(typeof(T), useInherited) as T; + } + + public static object GetContextItem(this TagHelperContext context, Type type) + { + return context.GetContextItem(type, useInherit: true); + } + + public static T GetContextItem(this TagHelperContext context, string key) where T : class + { + return context.GetContextItem(typeof(T), key) as T; + } + + public static object GetContextItem(this TagHelperContext context, Type type, string key) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + if (type == null) + { + throw new ArgumentNullException("type"); + } + if (key == null) + { + throw new ArgumentNullException("key"); + } + if (!context.Items.ContainsKey(key) || !type.IsInstanceOfType(context.Items[key])) + { + return null; + } + return context.Items[key]; + } + + public static object GetContextItem(this TagHelperContext context, Type type, bool useInherit) + { + if (context.Items.ContainsKey(type)) + { + return context.Items.First((KeyValuePair kVP) => kVP.Key.Equals(type)).Value; + } + if (useInherit) + { + return context.Items.FirstOrDefault((KeyValuePair kVP) => kVP.Key is Type && type.IsAssignableFrom((Type)kVP.Key)).Value; + } + return null; + } + + public static void SetContextItem(this TagHelperContext context, T contextItem) + { + context.SetContextItem(typeof(T), contextItem); + } + + public static void SetContextItem(this TagHelperContext context, Type type, object contextItem) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + if (type == null) + { + throw new ArgumentNullException("type"); + } + if (context.Items.ContainsKey(type)) + { + context.Items[type] = contextItem; + } + else + { + context.Items.Add(type, contextItem); + } + } + + public static void SetContextItem(this TagHelperContext context, string key, object contextItem) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + if (key == null) + { + throw new ArgumentNullException("key"); + } + if (context.Items.ContainsKey(key)) + { + context.Items[key] = contextItem; + } + else + { + context.Items.Add(key, contextItem); + } + } + + public static void RemoveContextItem(this TagHelperContext context) + { + context.RemoveContextItem(useInherited: true); + } + + public static void RemoveContextItem(this TagHelperContext context, bool useInherited) + { + context.RemoveContextItem(typeof(T), useInherited); + } + + public static void RemoveContextItem(this TagHelperContext context, Type type) + { + context.RemoveContextItem(type, useInherited: true); + } + + public static void RemoveContextItem(this TagHelperContext context, Type type, bool useInherited) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + if (type == null) + { + throw new ArgumentNullException("type"); + } + if (context.Items.ContainsKey(type)) + { + context.Items.Remove(type); + } + else if (useInherited) + { + var keyValuePair = context.Items.FirstOrDefault((KeyValuePair kVP) => kVP.Key is Type && ((Type)kVP.Key).IsAssignableFrom(type)); + if (!keyValuePair.Equals(default(KeyValuePair))) + { + context.Items.Remove(keyValuePair); + } + } + } + + public static void RemoveContextItem(this TagHelperContext context, string key) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + if (key == null) + { + throw new ArgumentNullException("key"); + } + if (context.Items.ContainsKey(key)) + { + context.Items.Remove(key); + } + } +} diff --git a/src/TagHelpers/Extensions/TagHelperOutputExtensions.cs b/src/TagHelpers/Extensions/TagHelperOutputExtensions.cs new file mode 100644 index 00000000..777433b2 --- /dev/null +++ b/src/TagHelpers/Extensions/TagHelperOutputExtensions.cs @@ -0,0 +1,231 @@ +/* + * TagHelperOutputExtensions.cs + * + * Created: 2024-23-15T17:23:56-05:00 + * Modified: 2024-23-15T17:23:57-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers.Extensions; + +public static class TagHelperOutputExtensions +{ + public static void AddCssClass(this TagHelperOutput output, string cssClass) + { + output.AddCssClass([ cssClass ]); + } + + public static void AddCssClass(this TagHelperOutput output, IEnumerable cssClasses) + { + if (output.Attributes.ContainsName("class") && output.Attributes["class"] != null) + { + var classes = output.Attributes["class"].Value.ToString().Split([ ' ' ]).ToList(); + classes.AddRange(cssClasses.Where((string cssClass) => !classes.Contains(cssClass))); + output.Attributes.SetAttribute("class", classes.Aggregate((string s, string s1) => s + " " + s1)); + } + else if (output.Attributes.ContainsName("class")) + { + output.Attributes.SetAttribute("class", cssClasses.Aggregate((string s, string s1) => s + " " + s1)); + } + else + { + output.Attributes.Add("class", cssClasses.Aggregate((string s, string s1) => s + " " + s1)); + } + } + + public static void RemoveCssClass(this TagHelperOutput output, string cssClass) + { + if (!output.Attributes.ContainsName("class")) + { + return; + } + var list = output.Attributes["class"].Value.ToString().Split([ ' ' ]).ToList(); + list.Remove(cssClass); + if (list.Count == 0) + { + output.Attributes.RemoveAll("class"); + return; + } + output.Attributes.SetAttribute("class", list.Aggregate((string s, string s1) => s + " " + s1)); + } + + public static void AddCssStyle(this TagHelperOutput output, string name, string value) + { + if (output.Attributes.ContainsName("style")) + { + if (IsNullOrEmpty(output.Attributes["style"].Value.ToString())) + { + output.Attributes.SetAttribute("style", name + ": " + value + ";"); + return; + } + output.Attributes.SetAttribute("style", (output.Attributes["style"].Value.ToString().EndsWith(';') ? " " : "; ") + name + ": " + value + ";"); + } + else + { + output.Attributes.Add("style", name + ": " + value + ";"); + } + } + + public static async Task LoadChildContentAsync(this TagHelperOutput output) + { + var content = output.Content; + content.SetHtmlContent((await output.GetChildContentAsync()) ?? new DefaultTagHelperContent()); + } + + public static TagHelperContent ToTagHelperContent(this TagHelperOutput output) + { + var defaultTagHelperContent = new DefaultTagHelperContent(); + defaultTagHelperContent.AppendHtml(output.PreElement); + var tagBuilder = new TagBuilder(output.TagName); + foreach (var attribute in output.Attributes) + { + tagBuilder.Attributes.Add(attribute.Name, attribute.Value?.ToString()); + } + if (output.TagMode == TagMode.SelfClosing) + { + tagBuilder.TagRenderMode = TagRenderMode.SelfClosing; + defaultTagHelperContent.AppendHtml(tagBuilder); + } + else + { + tagBuilder.TagRenderMode = TagRenderMode.StartTag; + defaultTagHelperContent.AppendHtml(tagBuilder); + defaultTagHelperContent.AppendHtml(output.PreContent); + defaultTagHelperContent.AppendHtml(output.Content); + defaultTagHelperContent.AppendHtml(output.PostContent); + if (output.TagMode == TagMode.StartTagAndEndTag) + { + defaultTagHelperContent.AppendHtml(""); + } + } + defaultTagHelperContent.AppendHtml(output.PostElement); + return defaultTagHelperContent; + } + + public static void AddAriaAttribute(this TagHelperOutput output, string name, object value) + { + output.MergeAttribute("aria-" + name, value); + } + + public static void AddDataAttribute(this TagHelperOutput output, string name, object value) + { + output.MergeAttribute("data-" + name, value); + } + + public static void MergeAttribute(this TagHelperOutput output, string key, object value) + { + output.Attributes.SetAttribute(key, value); + } + + public static void WrapContentOutside(this TagHelperOutput output, TagBuilder builder) + { + builder.TagRenderMode = TagRenderMode.StartTag; + output.WrapContentOutside(builder, new TagBuilder(builder.TagName) + { + TagRenderMode = TagRenderMode.EndTag + }); + } + + public static void WrapContentOutside(this TagHelperOutput output, IHtmlContent startTag, IHtmlContent endTag) + { + output.PreContent.Prepend(startTag); + output.PostContent.AppendHtml(endTag); + } + + public static void WrapContentOutside(this TagHelperOutput output, string startTag, string endTag) + { + output.PreContent.Prepend(startTag); + output.PostContent.Append(endTag); + } + + public static void WrapHtmlContentOutside(this TagHelperOutput output, string startTag, string endTag) + { + output.PreContent.PrependHtml(startTag); + output.PostContent.AppendHtml(endTag); + } + + public static void WrapContentInside(this TagHelperOutput output, TagBuilder builder) + { + builder.TagRenderMode = TagRenderMode.StartTag; + output.WrapContentInside(builder, new TagBuilder(builder.TagName) + { + TagRenderMode = TagRenderMode.EndTag + }); + } + + public static void WrapContentInside(this TagHelperOutput output, IHtmlContent startTag, IHtmlContent endTag) + { + output.PreContent.AppendHtml(startTag); + output.PostContent.Prepend(endTag); + } + + public static void WrapContentInside(this TagHelperOutput output, string startTag, string endTag) + { + output.PreContent.Append(startTag); + output.PostContent.Prepend(endTag); + } + + public static void WrapHtmlContentInside(this TagHelperOutput output, string startTag, string endTag) + { + output.PreContent.AppendHtml(startTag); + output.PostContent.PrependHtml(endTag); + } + + public static void WrapOutside(this TagHelperOutput output, TagBuilder builder) + { + builder.TagRenderMode = TagRenderMode.StartTag; + output.WrapOutside(builder, new TagBuilder(builder.TagName) + { + TagRenderMode = TagRenderMode.EndTag + }); + } + + public static void WrapOutside(this TagHelperOutput output, IHtmlContent startTag, IHtmlContent endTag) + { + output.PreElement.Prepend(startTag); + output.PostElement.AppendHtml(endTag); + } + + public static void WrapOutside(this TagHelperOutput output, string startTag, string endTag) + { + output.PreElement.Prepend(startTag); + output.PostElement.Append(endTag); + } + + public static void WrapHtmlOutside(this TagHelperOutput output, string startTag, string endTag) + { + output.PreElement.PrependHtml(startTag); + output.PostElement.AppendHtml(endTag); + } + + public static void WrapInside(this TagHelperOutput output, TagBuilder builder) + { + builder.TagRenderMode = TagRenderMode.StartTag; + output.WrapInside(builder, new TagBuilder(builder.TagName) + { + TagRenderMode = TagRenderMode.EndTag + }); + } + + public static void WrapInside(this TagHelperOutput output, IHtmlContent startTag, IHtmlContent endTag) + { + output.PreElement.AppendHtml(startTag); + output.PostElement.Prepend(endTag); + } + + public static void WrapInside(this TagHelperOutput output, string startTag, string endTag) + { + output.PreElement.Append(startTag); + output.PostElement.Prepend(endTag); + } + + public static void WrapHtmlInside(this TagHelperOutput output, string startTag, string endTag) + { + output.PreElement.AppendHtml(startTag); + output.PostElement.PrependHtml(endTag); + } +} diff --git a/src/TagHelpers/GridSystem/ColumnTagHelper.cs b/src/TagHelpers/GridSystem/ColumnTagHelper.cs new file mode 100644 index 00000000..fb6f170b --- /dev/null +++ b/src/TagHelpers/GridSystem/ColumnTagHelper.cs @@ -0,0 +1,282 @@ +// namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.GridSystem +// { +// using Microsoft.AspNetCore.Razor.TagHelpers; +// using System.Linq; + +// [OutputElementHint("div")] +// [HtmlTargetElement("column", ParentTag = "row")] +// public class ColumnTagHelper : TagHelper +// { +// #region --- Attribute Names --- + +// private const string XsSizeAttributeName = "xs-size"; +// private const string SmSizeAttributeName = "sm-size"; +// private const string MdSizeAttributeName = "md-size"; +// private const string LgSizeAttributeName = "lg-size"; +// private const string XlSizeAttributeName = "xl-size"; +// private const string XsOffsetAttributeName = "xs-offset"; +// private const string SmOffsetAttributeName = "sm-offset"; +// private const string MdOffsetAttributeName = "md-offset"; +// private const string LgOffsetAttributeName = "lg-offset"; +// private const string XlOffsetAttributeName = "xl-offset"; +// private const string XsRenderAttributeName = "xs-render"; +// private const string SmRenderAttributeName = "sm-render"; +// private const string MdRenderAttributeName = "md-render"; +// private const string LgRenderAttributeName = "lg-render"; +// private const string XlRenderAttributeName = "xl-render"; +// private const string XsOrderAttributeName = "xs-order"; +// private const string SmOrderAttributeName = "sm-order"; +// private const string MdOrderAttributeName = "md-order"; +// private const string LgOrderAttributeName = "lg-order"; +// private const string XlOrderAttributeName = "xl-order"; +// private const string VerticalAlignmentAttributeName = "vertical-alignment"; + +// #endregion + +// #region --- Properties --- + +// [HtmlAttributeName(XsSizeAttributeName)] +// public int XsSize { get; set; } + +// [HtmlAttributeName(SmSizeAttributeName)] +// public int SmSize { get; set; } + +// [HtmlAttributeName(MdSizeAttributeName)] +// public int MdSize { get; set; } + +// [HtmlAttributeName(LgSizeAttributeName)] +// public int LgSize { get; set; } + +// [HtmlAttributeName(XlSizeAttributeName)] +// public int XlSize { get; set; } + +// [HtmlAttributeName(XsOffsetAttributeName)] +// public int XsOffset { get; set; } + +// [HtmlAttributeName(SmOffsetAttributeName)] +// public int SmOffset { get; set; } + +// [HtmlAttributeName(MdOffsetAttributeName)] +// public int MdOffset { get; set; } + +// [HtmlAttributeName(LgOffsetAttributeName)] +// public int LgOffset { get; set; } + +// [HtmlAttributeName(XlOffsetAttributeName)] +// public int XlOffset { get; set; } + +// [HtmlAttributeName(XsRenderAttributeName)] +// public GridColumnRenderType XsRender { get; set; } = GridColumnRenderType.Fixed; + +// [HtmlAttributeName(SmRenderAttributeName)] +// public GridColumnRenderType SmRender { get; set; } = GridColumnRenderType.Fixed; + +// [HtmlAttributeName(MdRenderAttributeName)] +// public GridColumnRenderType MdRender { get; set; } = GridColumnRenderType.Fixed; + +// [HtmlAttributeName(LgRenderAttributeName)] +// public GridColumnRenderType LgRender { get; set; } = GridColumnRenderType.Fixed; + +// [HtmlAttributeName(XlRenderAttributeName)] +// public GridColumnRenderType XlRender { get; set; } = GridColumnRenderType.Fixed; + +// [HtmlAttributeName(XsOrderAttributeName)] +// public int XsOrder { get; set; } + +// [HtmlAttributeName(SmOrderAttributeName)] +// public int SmOrder { get; set; } + +// [HtmlAttributeName(MdOrderAttributeName)] +// public int MdOrder { get; set; } + +// [HtmlAttributeName(LgOrderAttributeName)] +// public int LgOrder { get; set; } + +// [HtmlAttributeName(XlOrderAttributeName)] +// public int XlOrder { get; set; } + +// [HtmlAttributeName(VerticalAlignmentAttributeName)] +// public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Default; + +// #endregion + +// public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) +// { +// output.TagName = "div"; + +// // Size +// this.ProceedSize(output); + +// // Offset +// this.ProcessOffset(output); + +// // Order +// this.ProceedOrder(output); + +// // Alignment +// switch (this.VerticalAlignment) +// { +// case VerticalAlignment.Top: +// output.AddCssClass("align-self-start"); +// break; +// case VerticalAlignment.Middle: +// output.AddCssClass("align-self-center"); +// break; +// case VerticalAlignment.Bottom: +// output.AddCssClass("align-self-end"); +// break; +// } +// } + +// private void ProceedSize(TagHelperOutput output) +// { +// // Extra Small Size +// switch (this.XsRender) +// { +// case GridColumnRenderType.Dynamic: +// output.AddCssClass("col-auto"); +// break; +// case GridColumnRenderType.Fixed: +// if (this.XsSize > 0 && this.XsSize <= 12) +// output.AddCssClass($"col-{this.XsSize}"); +// break; +// case GridColumnRenderType.Auto: +// output.AddCssClass($"col"); +// break; +// } + +// // Small Size +// switch (this.SmRender) +// { +// case GridColumnRenderType.Dynamic: +// output.AddCssClass("col-sm-auto"); +// break; +// case GridColumnRenderType.Fixed: +// if (this.SmSize > 0 && this.SmSize <= 12) +// output.AddCssClass($"col-sm-{this.SmSize}"); +// break; +// case GridColumnRenderType.Auto: +// output.AddCssClass($"col-sm"); +// break; +// } + +// // Medium Size +// switch (this.MdRender) +// { +// case GridColumnRenderType.Dynamic: +// output.AddCssClass("col-md-auto"); +// break; +// case GridColumnRenderType.Fixed: +// if (this.MdSize > 0 && this.MdSize <= 12) +// output.AddCssClass($"col-md-{this.MdSize}"); +// break; +// case GridColumnRenderType.Auto: +// output.AddCssClass($"col-md"); +// break; +// } + +// // Large Size +// switch (this.LgRender) +// { +// case GridColumnRenderType.Dynamic: +// output.AddCssClass("col-lg-auto"); +// break; +// case GridColumnRenderType.Fixed: +// if (this.LgSize > 0 && this.LgSize <= 12) +// output.AddCssClass($"col-lg-{this.LgSize}"); +// break; +// case GridColumnRenderType.Auto: +// output.AddCssClass($"col-lg"); +// break; +// } + +// // Extra Large Size +// switch (this.XlRender) +// { +// case GridColumnRenderType.Dynamic: +// output.AddCssClass("col-xl-auto"); +// break; +// case GridColumnRenderType.Fixed: +// if (this.XlSize > 0 && this.XlSize <= 12) +// output.AddCssClass($"col-xl-{this.XlSize}"); +// break; +// case GridColumnRenderType.Auto: +// output.AddCssClass($"col-xl"); +// break; +// } + +// // Default Size +// var classAttribute = output.Attributes.FirstOrDefault(a => a.Name == "class"); +// if (classAttribute == null || !classAttribute.Value.ToString().Contains("col")) +// { +// output.AddCssClass("col"); +// } +// } + +// private void ProcessOffset(TagHelperOutput output) +// { +// // Extra Small Offset +// if (this.XsOffset > 0 && this.XsOffset <= 12) +// { +// output.AddCssClass($"offset-{this.XsOffset}"); +// } + +// // Small Offset +// if (this.SmOffset > 0 && this.SmOffset <= 12) +// { +// output.AddCssClass($"offset-sm-{this.SmOffset}"); +// } + +// // Medium Offset +// if (this.MdOffset > 0 && this.MdOffset <= 12) +// { +// output.AddCssClass($"offset-md-{this.MdOffset}"); +// } + +// // Large Offset +// if (this.LgOffset > 0 && this.LgOffset <= 12) +// { +// output.AddCssClass($"offset-lg-{this.LgOffset}"); +// } + +// // Extra Large Offset +// if (this.XlOffset > 0 && this.XlOffset <= 12) +// { +// output.AddCssClass($"offset-xl-{this.XlOffset}"); +// } +// } + +// private void ProceedOrder(TagHelperOutput output) +// { +// // Extra Small Order +// if (this.XsOrder > 0 && this.XsOrder <= 12) +// { +// output.AddCssClass($"order-{this.XsOrder}"); +// } + +// // Small Order +// if (this.SmOrder > 0 && this.SmOrder <= 12) +// { +// output.AddCssClass($"order-sm-{this.SmOrder}"); +// } + +// // Medium Order +// if (this.MdOrder > 0 && this.MdOrder <= 12) +// { +// output.AddCssClass($"order-md-{this.MdOrder}"); +// } + +// // Large Order +// if (this.LgOrder > 0 && this.LgOrder <= 12) +// { +// output.AddCssClass($"order-lg-{this.LgOrder}"); +// } + +// // Extra Large Order +// if (this.XlOrder > 0 && this.XlOrder <= 12) +// { +// output.AddCssClass($"order-xl-{this.XlOrder}"); +// } +// } +// } +// } diff --git a/src/TagHelpers/GridSystem/ContainerTagHelper.cs b/src/TagHelpers/GridSystem/ContainerTagHelper.cs new file mode 100644 index 00000000..0167eb21 --- /dev/null +++ b/src/TagHelpers/GridSystem/ContainerTagHelper.cs @@ -0,0 +1,27 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.GridSystem +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("div")] + public class ContainerTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string FluidAttributeName = "fluid"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(FluidAttributeName)] + public bool Fluid { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass(Fluid ? "container-fluid" : "container"); + } + } +} diff --git a/src/TagHelpers/GridSystem/RowTagHelper.cs b/src/TagHelpers/GridSystem/RowTagHelper.cs new file mode 100644 index 00000000..45ccba2c --- /dev/null +++ b/src/TagHelpers/GridSystem/RowTagHelper.cs @@ -0,0 +1,67 @@ +// namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.GridSystem +// { +// using Microsoft.AspNetCore.Razor.TagHelpers; + +// [OutputElementHint("div")] +// [RestrictChildren("column")] +// public class RowTagHelper : TagHelper +// { +// #region --- Attribute Names --- + +// private const string VerticalAlignmentAttributeName = "vertical-alignment"; +// private const string HorizontalAlignmentAttributeName = "alignment"; + +// #endregion + +// #region --- Properties --- + +// [HtmlAttributeName(VerticalAlignmentAttributeName)] +// public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Default; + +// [HtmlAttributeName(HorizontalAlignmentAttributeName)] +// public GridHorizontalAlignment HorizontalAlignment { get; set; } = +// GridHorizontalAlignment.Default; + +// #endregion + +// public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) +// { +// output.TagName = "div"; +// output.AddCssClass("row"); + +// // Vertical Alignment +// switch (this.VerticalAlignment) +// { +// case VerticalAlignment.Top: +// output.AddCssClass("align-items-start"); +// break; +// case VerticalAlignment.Middle: +// output.AddCssClass("align-items-center"); +// break; +// case VerticalAlignment.Bottom: +// output.AddCssClass("align-items-end"); +// break; +// } + +// // Horizontal Alignment +// switch (this.HorizontalAlignment) +// { +// case GridHorizontalAlignment.Left: +// output.AddCssClass("justify-content-start"); +// break; +// case GridHorizontalAlignment.Center: +// output.AddCssClass("justify-content-center"); +// break; +// case GridHorizontalAlignment.Right: +// output.AddCssClass("justify-content-end"); +// break; +// case GridHorizontalAlignment.Around: +// output.AddCssClass("justify-content-around"); +// break; +// case GridHorizontalAlignment.Between: +// output.AddCssClass("justify-content-between"); +// break; +// } +// } +// } +// } diff --git a/src/TagHelpers/LICENSE.md b/src/TagHelpers/LICENSE.md new file mode 100644 index 00000000..376423df --- /dev/null +++ b/src/TagHelpers/LICENSE.md @@ -0,0 +1,35 @@ +--- +date: 2023-07-13T05:44:46:00+05:00Z +description: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files, yadda, yadda, yadda... +keywords: +- IP +- copyright +- license +- mit +permissions: +- commercial-use +- modifications +- distribution +- private-use +conditions: +- include-copyright +limitations: +- liability +- warranty +lastmod: 2024-01-0T00:39:00.0000+05:00Z +license: MIT +slug: mit-license +title: MIT License +type: license +--- + +# MIT License + +## Copyright © 2022-2023 [David G. Moore, Jr.](mailto:david@dgmjr.io "Send Dr. Moore an email") ([@dgmjr](https://github.com/dgmjr "Contact Dr. Moore on GitHub")), All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/TagHelpers/ListGroup/ListGroupButtonTagHelper.cs b/src/TagHelpers/ListGroup/ListGroupButtonTagHelper.cs new file mode 100644 index 00000000..685285c6 --- /dev/null +++ b/src/TagHelpers/ListGroup/ListGroupButtonTagHelper.cs @@ -0,0 +1,11 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.ListGroup +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("button")] + [HtmlTargetElement("list-group-button", ParentTag = "list-group")] + public class ListGroupButtonTagHelper : ListGroupLinkTagHelper + { + protected override string TagName => "button"; + } +} diff --git a/src/TagHelpers/ListGroup/ListGroupItemTagHelper.cs b/src/TagHelpers/ListGroup/ListGroupItemTagHelper.cs new file mode 100644 index 00000000..83622a9a --- /dev/null +++ b/src/TagHelpers/ListGroup/ListGroupItemTagHelper.cs @@ -0,0 +1,53 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.ListGroup +{ + using System.Threading.Tasks; + + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("li")] + [HtmlTargetElement("list-group-item", ParentTag = "list-group")] + public class ListGroupItemTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string ColorAttributeName = "color"; + private const string BadgeAttributeName = "badge"; + private const string BadgeColorAttributeName = "badge-color"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ColorAttributeName)] + public Color Color { get; set; } + + [HtmlAttributeName(BadgeAttributeName)] + public string Badge { get; set; } + + [HtmlAttributeName(BadgeColorAttributeName)] + public Color BadgeColor { get; set; } = Color.None; + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "li"; + output.AddCssClass("list-group-item"); + + // Color + if (this.Color != Color.None) + { + output.AddCssClass($"list-group-item-{this.Color.GetColorInfo().Name}"); + } + + // Badge + if (!IsNullOrEmpty(this.Badge)) + { + output.AddCssClass("d-flex justify-content-between align-items-center"); + output.PostContent.AppendHtml( + $"{this.Badge}" + ); + } + } + } +} diff --git a/src/TagHelpers/ListGroup/ListGroupLinkTagHelper.cs b/src/TagHelpers/ListGroup/ListGroupLinkTagHelper.cs new file mode 100644 index 00000000..c554104a --- /dev/null +++ b/src/TagHelpers/ListGroup/ListGroupLinkTagHelper.cs @@ -0,0 +1,83 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.ListGroup +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("a")] + [HtmlTargetElement("list-group-link", ParentTag = "list-group")] + public class ListGroupLinkTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string DisableAttributeName = "disable"; + private const string ColorAttributeName = "color"; + private const string ActiveAttributeName = "active"; + private const string BadgeAttributeName = "badge"; + private const string BadgeColorAttributeName = "badge-color"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ColorAttributeName)] + public Color Color { get; set; } + + [HtmlAttributeName(DisableAttributeName)] + public bool IsDisabled { get; set; } + + [HtmlAttributeName(ActiveAttributeName)] + public bool IsActive { get; set; } + + [HtmlAttributeName(BadgeAttributeName)] + public string Badge { get; set; } + + [HtmlAttributeName(BadgeColorAttributeName)] + public Color BadgeColor { get; set; } = Color.None; + + [Context] + [HtmlAttributeNotBound] + public ListGroupTagHelper ListGroupContext { get; set; } + + #endregion + + protected virtual string TagName => "a"; + + public override void Init(TagHelperContext context) + { + base.Init(context); + this.ListGroupContext.IsLinkGroup = true; + } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = this.TagName; + output.AddCssClass("list-group-item list-group-item-action"); + + // Disable + if (this.IsDisabled) + { + output.AddCssClass("disabled"); + } + + // Style + if (this.Color != Color.None) + { + output.AddCssClass($"list-group-item-{this.Color.GetColorInfo().Name}"); + } + + // Active + if (this.IsActive) + { + output.AddCssClass("active"); + } + + // Badge + if (!IsNullOrEmpty(this.Badge)) + { + output.AddCssClass("d-flex justify-content-between align-items-center"); + output.PostContent.AppendHtml( + $"{this.Badge}" + ); + } + } + } +} diff --git a/src/TagHelpers/ListGroup/ListGroupTagHelper.cs b/src/TagHelpers/ListGroup/ListGroupTagHelper.cs new file mode 100644 index 00000000..ec83ee6c --- /dev/null +++ b/src/TagHelpers/ListGroup/ListGroupTagHelper.cs @@ -0,0 +1,21 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.ListGroup +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + using System.Threading.Tasks; + + [ContextClass] + [RestrictChildren("list-group-item", "list-group-link", "list-group-button")] + public class ListGroupTagHelper : TagHelper + { + [HtmlAttributeNotBound] + public bool IsLinkGroup { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await output.GetChildContentAsync(); + + output.TagName = this.IsLinkGroup ? "div" : "ul"; + output.AddCssClass("list-group"); + } + } +} diff --git a/src/TagHelpers/Markdown/MarkdownTagHelper.cs b/src/TagHelpers/Markdown/MarkdownTagHelper.cs new file mode 100644 index 00000000..dbc3d1cb --- /dev/null +++ b/src/TagHelpers/Markdown/MarkdownTagHelper.cs @@ -0,0 +1,32 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Markdown; + +[HtmlTargetElement("markdown", TagStructure = TagStructure.NormalOrSelfClosing)] +[HtmlTargetElement(Attributes = "markdown")] +public class MarkdownTagHelper : TagHelper +{ + public ModelExpression Content { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (output.TagName == "markdown") + { + output.TagName = null; + } + output.Attributes.RemoveAll("markdown"); + + var content = await GetContent(output); + var markdown = content; + var html = CommonMarkConverter.Convert(markdown); + output.Content.SetHtmlContent(html ?? ""); + } + + private async Task GetContent(TagHelperOutput output) + { + if (Content == null) + { + return (await output.GetChildContentAsync()).GetContent(); + } + + return Content.Model?.ToString(); + } +} diff --git a/src/TagHelpers/Media/MediaBodyTagHelper.cs b/src/TagHelpers/Media/MediaBodyTagHelper.cs new file mode 100644 index 00000000..cfdfeb12 --- /dev/null +++ b/src/TagHelpers/Media/MediaBodyTagHelper.cs @@ -0,0 +1,34 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Media +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("div")] + [HtmlTargetElement("media-body", ParentTag = "media")] + public class MediaBodyTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string HeaderAttributeName = "header"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(HeaderAttributeName)] + public string Header { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("media-body"); + + // Header + if (!IsNullOrEmpty(this.Header)) + { + output.PreContent.AppendHtml($"
    {this.Header}
    "); + } + } + } +} diff --git a/src/TagHelpers/Media/MediaImageTagHelper.cs b/src/TagHelpers/Media/MediaImageTagHelper.cs new file mode 100644 index 00000000..128bdfbc --- /dev/null +++ b/src/TagHelpers/Media/MediaImageTagHelper.cs @@ -0,0 +1,54 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Media +{ + using Microsoft.AspNetCore.Mvc.Infrastructure; + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("img")] + [HtmlTargetElement("media-image", ParentTag = "media")] + public class MediaImageTagHelper(IActionContextAccessor actionContextAccessor) + : TagHelper, + IHaveAnActionContextAccessor + { + #region --- Attribute Names --- + + private const string VerticalAlignmentAttributeName = "vertical-alignment"; + + #endregion + + #region --- Properties --- + + [CopyToOutput] + [ConvertVirtualUrl] + public string Src { get; set; } + + [HtmlAttributeName(VerticalAlignmentAttributeName)] + public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Default; + + [ActionContext] + public IActionContextAccessor ActionContextAccessor => actionContextAccessor; + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "img"; + output.TagMode = TagMode.SelfClosing; + + output.AddCssClass("d-flex"); + + // Vertical Alignment + switch (this.VerticalAlignment) + { + case VerticalAlignment.Top: + output.AddCssClass("align-self-start"); + break; + case VerticalAlignment.Middle: + output.AddCssClass("align-self-center"); + break; + case VerticalAlignment.Bottom: + output.AddCssClass("align-self-end"); + break; + } + } + } +} diff --git a/src/TagHelpers/Media/MediaTagHelper.cs b/src/TagHelpers/Media/MediaTagHelper.cs new file mode 100644 index 00000000..c5736f35 --- /dev/null +++ b/src/TagHelpers/Media/MediaTagHelper.cs @@ -0,0 +1,16 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Media +{ + using Microsoft.AspNetCore.Mvc.Infrastructure; + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("div")] + [RestrictChildren("media-image", "media-body")] + public class MediaTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.TagName = "div"; + output.AddCssClass("media"); + } + } +} diff --git a/src/TagHelpers/Modal/ModalBodyTagHelper.cs b/src/TagHelpers/Modal/ModalBodyTagHelper.cs new file mode 100644 index 00000000..5f3860c2 --- /dev/null +++ b/src/TagHelpers/Modal/ModalBodyTagHelper.cs @@ -0,0 +1,21 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Modal +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + using System.Threading.Tasks; + + [HtmlTargetElement("modal-body", ParentTag = "modal")] + public class ModalBodyTagHelper : TagHelper + { + [HtmlAttributeNotBound] + [Context] + public ModalTagHelper ModalContext { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await output.LoadChildContentAsync(); + + output.TagName = "div"; + output.AddCssClass("modal-body"); + } + } +} diff --git a/src/TagHelpers/Modal/ModalFooterTagHelper.cs b/src/TagHelpers/Modal/ModalFooterTagHelper.cs new file mode 100644 index 00000000..df54e17d --- /dev/null +++ b/src/TagHelpers/Modal/ModalFooterTagHelper.cs @@ -0,0 +1,26 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Modal +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + using System.Threading.Tasks; + + [HtmlTargetElement("modal-footer", ParentTag = "modal")] + [OutputElementHint("div")] + public class ModalFooterTagHelper : TagHelper + { + #region --- Properties --- + + [HtmlAttributeNotBound] + [Context] + public ModalTagHelper ModalContext { get; set; } + + #endregion + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await output.LoadChildContentAsync(); + + output.TagName = "div"; + output.AddCssClass("modal-footer"); + } + } +} diff --git a/src/TagHelpers/Modal/ModalHeaderTagHelper.cs b/src/TagHelpers/Modal/ModalHeaderTagHelper.cs new file mode 100644 index 00000000..13ecbaf0 --- /dev/null +++ b/src/TagHelpers/Modal/ModalHeaderTagHelper.cs @@ -0,0 +1,57 @@ +// namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Modal +// { +// using Microsoft.AspNetCore.Razor.TagHelpers; +// using System.Threading.Tasks; + +// [HtmlTargetElement("modal-header", ParentTag = "modal")] +// [OutputElementHint("div")] +// public class ModalHeaderTagHelper : TagHelper +// { +// #region --- Attribute Names --- + +// private const string DisableCloseIconAttributeName = "disable-close-icon"; + +// #endregion + +// #region --- Properties --- + +// [HtmlAttributeName(DisableCloseIconAttributeName)] +// public bool DisableCloseIcon { get; set; } + +// [HtmlAttributeNotBound] +// [Context] +// public ModalTagHelper ModalContext { get; set; } + +// #endregion + +// public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) +// { +// output.TagName = "div"; +// output.AddCssClass("modal-header"); + +// // Close Button +// if (!this.DisableCloseIcon) +// { +// output.PostContent.AppendHtml( +// "" +// ); +// } + +// // Header +// var content = await output.GetChildContentAsync(); + +// if (this.ModalContext != null && !IsNullOrEmpty(this.ModalContext.Title)) +// { +// output.Content.AppendHtml($"

    {content.GetContent()}

    "); +// } +// else +// { +// output.Content.AppendHtml(content.GetContent()); +// } + +// this.ModalContext.HeaderHtml = output.ToTagHelperContent().GetContent(); + +// output.SuppressOutput(); +// } +// } +// } diff --git a/src/TagHelpers/Modal/ModalTagHelper.cs b/src/TagHelpers/Modal/ModalTagHelper.cs new file mode 100644 index 00000000..979db1e1 --- /dev/null +++ b/src/TagHelpers/Modal/ModalTagHelper.cs @@ -0,0 +1,127 @@ +// namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Modal +// { +// using Microsoft.AspNetCore.Html; +// using Microsoft.AspNetCore.Mvc.Rendering; +// using Microsoft.AspNetCore.Razor.TagHelpers; +// using System.Threading.Tasks; + +// [ContextClass] +// [GenerateId("modal-")] +// [RestrictChildren("modal-header", "modal-body", "modal-footer")] +// public class ModalTagHelper : TagHelper +// { +// #region --- Attribute Names --- + +// private const string ToggleButtonTextAttributeName = "toggle-text"; +// private const string ToggleButtonColorAttributeName = "toggle-color"; +// private const string TitleAttributeName = "title"; +// private const string SizeAttributeName = "size"; +// private const string PositionAttributeName = "position"; + +// #endregion + +// #region --- Properties --- + +// [HtmlAttributeName(ToggleButtonTextAttributeName)] +// public string ToggleButtonText { get; set; } + +// [HtmlAttributeName(ToggleButtonColorAttributeName)] +// public Color ToggleButtonColor { get; set; } + +// [HtmlAttributeName(TitleAttributeName)] +// public string Title { get; set; } + +// [HtmlAttributeNotBound] +// public string HeaderHtml { get; set; } + +// [HtmlAttributeName(SizeAttributeName)] +// public Size Size { get; set; } + +// [HtmlAttributeName(PositionAttributeName)] +// public ModalPosition Position { get; set; } = ModalPosition.Top; + +// #endregion + +// public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) +// { +// await output.LoadChildContentAsync(); + +// // Header +// if (IsNullOrEmpty(this.HeaderHtml) && !IsNullOrEmpty(this.Title)) +// { +// await new ModalHeaderTagHelper().RunTagHelperAsync( +// new TagHelperExtensions.Options +// { +// Context = context, +// Content = new DefaultTagHelperContent().AppendHtml(this.Title) +// } +// ); +// } + +// // Toogle Button +// if (!IsNullOrEmpty(this.ToggleButtonText)) +// { +// output.PreElement.AppendHtml( +// await new ITagHelper[] +// { +// new ButtonTagHelper { Color = this.ToggleButtonColor }, +// new ModalToggleTagHelper { ModalTarget = this.Id } +// }.ToTagHelperContentAsync( +// new TagHelperExtensions.Options +// { +// Content = new DefaultTagHelperContent().AppendHtml( +// this.ToggleButtonText +// ), +// TagName = "button" +// } +// ) +// ); +// } + +// // Modal +// output.TagName = "div"; +// output.AddCssClass("modal fade"); +// output.Attributes.Add("tabindex", "-1"); +// output.Attributes.Add("role", "dialog"); +// output.Attributes.Add("aria-hidden", "true"); + +// // Header +// output.PreContent.AppendHtmlLine(this.HeaderHtml); + +// // Content +// output.WrapContentOutside(this.BuildContent()); + +// // Dialog +// output.WrapContentOutside(this.BuildDialog()); +// } + +// private TagBuilder BuildDialog() +// { +// TagBuilder dialog = new TagBuilder("div"); +// dialog.MergeAttribute("role", "document"); +// dialog.AddCssClass("modal-dialog"); + +// // Size +// if (this.Size != Size.Default) +// { +// dialog.AddCssClass($"modal-{this.Size.GetEnumInfo().Name}"); +// } + +// // Position +// if (this.Position != ModalPosition.Top) +// { +// dialog.AddCssClass("modal-dialog-centered"); +// } + +// return dialog; +// } + +// private TagBuilder BuildContent() +// { +// TagBuilder content = new TagBuilder("div"); +// content.AddCssClass("modal-content"); + +// return content; +// } +// } +// } diff --git a/src/TagHelpers/Modal/ModalTitleTagHelper.cs b/src/TagHelpers/Modal/ModalTitleTagHelper.cs new file mode 100644 index 00000000..70131d29 --- /dev/null +++ b/src/TagHelpers/Modal/ModalTitleTagHelper.cs @@ -0,0 +1,18 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Modal +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("h1", ParentTag = "modal-header")] + [HtmlTargetElement("h2", ParentTag = "modal-header")] + [HtmlTargetElement("h3", ParentTag = "modal-header")] + [HtmlTargetElement("h4", ParentTag = "modal-header")] + [HtmlTargetElement("h5", ParentTag = "modal-header")] + [HtmlTargetElement("h6", ParentTag = "modal-header")] + public class ModalTitleTagHelper : TagHelper + { + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.AddCssClass("modal-title"); + } + } +} diff --git a/src/TagHelpers/Modal/ModalToggleTagHelper.cs b/src/TagHelpers/Modal/ModalToggleTagHelper.cs new file mode 100644 index 00000000..1f240fbb --- /dev/null +++ b/src/TagHelpers/Modal/ModalToggleTagHelper.cs @@ -0,0 +1,27 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Modal +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [HtmlTargetElement("button", Attributes = ModalTargetAttributeName)] + public class ModalToggleTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string ModalTargetAttributeName = "modal-target"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ModalTargetAttributeName)] + public string ModalTarget { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.Add("data-target", "#" + this.ModalTarget); + output.Attributes.Add("data-toggle", "modal"); + } + } +} diff --git a/src/TagHelpers/Resources/ClientSideValidationScript.js b/src/TagHelpers/Resources/ClientSideValidationScript.js new file mode 100644 index 00000000..6d993967 --- /dev/null +++ b/src/TagHelpers/Resources/ClientSideValidationScript.js @@ -0,0 +1,19 @@ + +(() => { + 'use strict' + + // Fetch all the forms we want to apply custom Bootstrap validation styles to + const forms = document.querySelectorAll('.needs-validation') + + // Loop over them and prevent submission + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault() + event.stopPropagation() + } + + form.classList.add('was-validated') + }, false) + }) +})() diff --git a/src/TagHelpers/Table/TableTagHelper.cs b/src/TagHelpers/Table/TableTagHelper.cs new file mode 100644 index 00000000..04b0545b --- /dev/null +++ b/src/TagHelpers/Table/TableTagHelper.cs @@ -0,0 +1,88 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Table +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + public class TableTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string StripedAttributeName = "striped"; + private const string SmallAttributeName = "small"; + private const string BorderAttributeName = "border"; + private const string HoverAttributeName = "hover"; + private const string ThemeAttributeName = "theme"; + private const string ResponsiveAttributeName = "responsive"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(StripedAttributeName)] + public bool IsStriped { get; set; } + + [HtmlAttributeName(SmallAttributeName)] + public bool IsSmall { get; set; } + + // [HtmlAttributeName(BorderAttributeName)] + // public TableBorder Border { get; set; } = TableBorder.Regular; + + [HtmlAttributeName(HoverAttributeName)] + public bool IsHover { get; set; } + + [HtmlAttributeName(ThemeAttributeName)] + public Theme Theme { get; set; } + + // [HtmlAttributeName(ResponsiveAttributeName)] + // public Breakpoint? Responsive { get; set; } + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.AddCssClass("table"); + + // Theme + output.AddCssClass( + Theme != Theme.Default ? $"table-{Theme.GetEnumInfo().Name}" : string.Empty + ); + + // Striped + if (IsStriped) + { + output.AddCssClass("table-striped"); + } + + // Small + if (IsSmall) + { + output.AddCssClass("table-sm"); + } + + // // Border + // switch (Border) + // { + // case TableBorder.Bordered: + // output.AddCssClass("table-bordered"); + // break; + // case TableBorder.Borderless: + // output.AddCssClass("table-borderless"); + // break; + // } + + // Hover + if (IsHover) + { + output.AddCssClass("table-hover"); + } + + // Responsive + // if (Responsive.HasValue) + // { + // output.WrapHtmlOutside( + // $"
    ", + // "
    " + // ); + // } + } + } +} diff --git a/src/TagHelpers/Table/TdTagHelper.cs b/src/TagHelpers/Table/TdTagHelper.cs new file mode 100644 index 00000000..3eb0adb4 --- /dev/null +++ b/src/TagHelpers/Table/TdTagHelper.cs @@ -0,0 +1,31 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Table +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("td")] + [HtmlTargetElement("td", ParentTag = "tr")] + public class TdTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string BackgroundAttributeName = "background"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(BackgroundAttributeName)] + public Color BackgroundColor { get; set; } = Color.None; + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + // Background + if (BackgroundColor != Color.None) + { + output.AddCssClass($"table-{BackgroundColor.GetColorInfo().Name}"); + } + } + } +} diff --git a/src/TagHelpers/Table/ThTagHelper.cs b/src/TagHelpers/Table/ThTagHelper.cs new file mode 100644 index 00000000..d636f7e7 --- /dev/null +++ b/src/TagHelpers/Table/ThTagHelper.cs @@ -0,0 +1,11 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Table +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + using System; + using System.Collections.Generic; + using System.Text; + + [OutputElementHint("th")] + [HtmlTargetElement("th", ParentTag = "tr")] + public class ThTagHelper : TdTagHelper { } +} diff --git a/src/TagHelpers/Table/TheadTagHelper.cs b/src/TagHelpers/Table/TheadTagHelper.cs new file mode 100644 index 00000000..118f78d7 --- /dev/null +++ b/src/TagHelpers/Table/TheadTagHelper.cs @@ -0,0 +1,30 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Table +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("thead")] + [HtmlTargetElement("thead", ParentTag = "table")] + public class TheadTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string ThemeAttributeName = "theme"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ThemeAttributeName)] + public Theme Theme { get; set; } = Theme.Default; + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + // Theme + output.AddCssClass( + Theme != Theme.Default ? $"thead-{Theme.GetEnumInfo().Name}" : string.Empty + ); + } + } +} diff --git a/src/TagHelpers/Table/TrTagHelper.cs b/src/TagHelpers/Table/TrTagHelper.cs new file mode 100644 index 00000000..41eebc81 --- /dev/null +++ b/src/TagHelpers/Table/TrTagHelper.cs @@ -0,0 +1,31 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Table +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + + [OutputElementHint("tr")] + [HtmlTargetElement("tr", ParentTag = "tbody")] + public class TrTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string BackgroundAttributeName = "background"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(BackgroundAttributeName)] + public Color Background { get; set; } = Color.None; + + #endregion + + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + // Background + if (Background != Color.None) + { + output.AddCssClass($"table-{Background.GetColorInfo().Name}"); + } + } + } +} diff --git a/src/TagHelpers/Tabs/TabsPaneTagHelper.cs b/src/TagHelpers/Tabs/TabsPaneTagHelper.cs new file mode 100644 index 00000000..bfceea5b --- /dev/null +++ b/src/TagHelpers/Tabs/TabsPaneTagHelper.cs @@ -0,0 +1,48 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Tabs +{ + using Microsoft.AspNetCore.Razor.TagHelpers; + using System.Threading.Tasks; + + [HtmlTargetElement("tabs-pane", ParentTag = "tabs")] + [GenerateId("pane-")] + public class TabsPaneTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string HeaderAttributeName = "header"; + private const string ActiveAttributeName = "active"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(HeaderAttributeName)] + public string Header { get; set; } + + [HtmlAttributeName(ActiveAttributeName)] + public bool IsActive { get; set; } + + [Context] + [HtmlAttributeNotBound] + public TabsTagHelper TabsContext { get; set; } + + [HtmlAttributeNotBound] + public TagHelperContent Content { get; set; } + + public virtual string Id { get; set; } = guid.NewGuid().ToString("N")[..8]; + + #endregion + + public override void Init(TagHelperContext context) + { + base.Init(context); + this.TabsContext.Panes.Add(this); + } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + this.Content = await output.GetChildContentAsync(); + output.SuppressOutput(); + } + } +} diff --git a/src/TagHelpers/Tabs/TabsTagHelper.cs b/src/TagHelpers/Tabs/TabsTagHelper.cs new file mode 100644 index 00000000..b28df06c --- /dev/null +++ b/src/TagHelpers/Tabs/TabsTagHelper.cs @@ -0,0 +1,90 @@ +namespace Dgmjr.AspNetCore.TagHelpers.Bootstrap.Tabs +{ + using Microsoft.AspNetCore.Html; + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Razor.TagHelpers; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + [OutputElementHint("nav")] + [GenerateId("tabs-")] + [ContextClass] + [RestrictChildren("tabs-pane")] + public class TabsTagHelper : TagHelper + { + #region --- Attribute Names --- + + private const string PillsAttributeName = "pills"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(PillsAttributeName)] + public bool Pills { get; set; } + + [HtmlAttributeNotBound] + public List Panes { get; set; } = new List(); + + #endregion + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await output.GetChildContentAsync(); + + output.TagName = "nav"; + output.AddCssClass(this.Pills ? "nav nav-pills mb-3" : "nav nav-tabs"); + output.MergeAttribute("role", "tablist"); + + // Active + if (!this.Panes.Any(p => p.IsActive)) + { + this.Panes.First().IsActive = true; + } + + // Tab Item + foreach (var pane in this.Panes) + { + output.PreContent.AppendHtml(this.BuildTabItem(pane)); + } + + // Tab Pane + output.PostElement.AppendHtml("
    "); + + foreach (var pane in this.Panes) + { + output.PostElement.AppendHtml(this.BuildTabPane(pane)); + } + + output.PostElement.AppendHtml("
    "); + } + + private IHtmlContent BuildTabItem(TabsPaneTagHelper pane) + { + TagBuilder item = new TagBuilder("a"); + item.AddCssClass(pane.IsActive ? "nav-item nav-link active" : "nav-item nav-link"); + item.GenerateId($"{pane.Id}-tab", "-"); + item.MergeAttribute("data-toggle", this.Pills ? "pill" : "tab"); + item.MergeAttribute("role", "tab"); + // item.MergeAttribute("href", $"#{pane.Id}"); + // item.MergeAttribute("aria-controls", pane.Id); + item.MergeAttribute("aria-expanded", pane.IsActive ? "true" : "false"); + item.InnerHtml.Append(pane.Header); + + return item; + } + + private IHtmlContent BuildTabPane(TabsPaneTagHelper pane) + { + TagBuilder item = new TagBuilder("div"); + item.AddCssClass(pane.IsActive ? "tab-pane fade show active" : "tab-pane fade"); + item.GenerateId($"{pane.Id}", "-"); + item.MergeAttribute("role", "tabpanel"); + item.MergeAttribute("aria-labelledby", $"{pane.Id}-tab"); + item.InnerHtml.AppendHtml(pane.Content.GetContent()); + + return item; + } + } +} diff --git a/src/TagHelpers/TagHelperConstants.cs b/src/TagHelpers/TagHelperConstants.cs new file mode 100644 index 00000000..3e166e18 --- /dev/null +++ b/src/TagHelpers/TagHelperConstants.cs @@ -0,0 +1,451 @@ +namespace Dgmjr.AspNetCore.TagHelpers; + +public static class TagHelperConstants +{ + public static class TagNames + { + public const string Accordion = "accordion"; + public const string AccordionItem = "accordion-item"; + public const string Alert = "alert"; + public const string Anchor = "a"; + public const string Any = "*"; + public const string Btn = "btn"; + public const string Button = "button"; + public const string Contact = "contact"; + public const string Container = "container"; + public const string ClientSideValidationScript = "client-side-validation-script"; + public const string Div = "div"; + public const string Divider = "hr"; + public const string DropdownItem = "dropdown-item"; + public const string Email = "email"; + public const string Expanded = "expanded"; + public const string Footer = "footer"; + public const string Form = "form"; + public const string Img = "img"; + public const string Input = "input"; + public const string Li = "li"; + public const string Nav = "nav"; + public const string Navbar = "navbar"; + public const string NavbarLink = "navbar-link"; + public const string NavbarNav = "navbar-nav"; + public const string NavbarNavRight = "navbar-nav-right"; + public const string NavbarText = "navbar-text"; + public const string NavDropdown = "nav-dropdown"; + public const string NavDropdownItem = "nav-dropdown-item"; + public const string NavDropdownLink = "nav-dropdown-link"; + public const string NavForm = "nav-form"; + public const string NavItem = "nav-item"; + public const string NavLink = "nav-link"; + public const string NavText = "nav-text"; + public const string PageHeader = "page-header"; + public const string Panel = "panel"; + public const string PanelBody = "panel-body"; + public const string PanelFooter = "panel-footer"; + public const string PanelHeading = "panel-heading"; + public const string PanelTitle = "panel-title"; + public const string Paragraph = "p"; + public const string Select = "select"; + public const string Span = "span"; + public const string Table = "table"; + public const string TextArea = "textarea"; + public const string Ul = "ul"; + } + + public static class AttributeNames + { + public const string Action = "asp-action"; + public const string Alt = "alt"; + public const string Area = "asp-area"; + public const string AriaControls = "aria-controls"; + public const string AriaExpanded = "aria-expanded"; + public const string AriaLabel = "aria-label"; + public const string AspIf = "asp-if"; + public const string Class = "class"; + public const string BsLabelPosition = "bs-label-position"; + public const string BorderColor = "bs-border-color"; + public const string BackgroundColor = "bs-bg-color"; + public const string Color = "bs-color"; + public const string Controller = "asp-controller"; + public const string DataDismiss = "data-dismiss"; + public const string DataParent = "data-parent"; + public const string DataRide = "data-ride"; + public const string DataSlide = "data-slide"; + public const string DataSlideTo = "data-slide-to"; + public const string DataBsTarget = "data-bs-target"; + public const string DataTarget = "data-target"; + public const string DataTargetCarousel = "data-target-carousel"; + public const string DataTargetCollapse = "data-target-collapse"; + public const string DataTargetDismiss = "data-target-dismiss"; + public const string DataTargetModal = "data-target-modal"; + public const string DataTargetModalBackdrop = "data-target-modal-backdrop"; + public const string DataTargetModalBackdropDismiss = "data-target-modal-backdrop-dismiss"; + public const string DataTargetModalBackdropStatic = "data-target-modal-backdrop-static"; + public const string DataTargetModalBackdropToggle = "data-target-modal-backdrop-toggle"; + public const string DataTargetModalDismiss = "data-target-modal-dismiss"; + public const string DataTargetModalKeyboard = "data-target-modal-keyboard"; + public const string DataTargetModalRemote = "data-target-modal-remote"; + public const string DataTargetModalScroll = "data-target-modal-scroll"; + public const string DataTargetModalShow = "data-target-modal-show"; + public const string DataTargetModalStatic = "data-target-modal-static"; + public const string DataTargetModalToggle = "data-target-modal-toggle"; + public const string DataTargetParent = "data-target-parent"; + public const string DataTargetPopover = "data-target-popover"; + public const string DataTargetRide = "data-target-ride"; + public const string DataTargetSlide = "data-target-slide"; + public const string DataTargetSlideTo = "data-target-slide-to"; + public const string DataTargetTab = "data-target-tab"; + public const string DataTargetToggle = "data-target-toggle"; + public const string DataTargetTooltip = "data-target-tooltip"; + public const string DataBsToggle = "data-bs-toggle"; + public const string DataToggle = "data-toggle"; + public const string Disabled = "disabled"; + public const string For = "for"; + public const string Header = "header"; + public const string HeaderBorderColor = "header-border-color"; + public const string HeaderColor = "header-color"; + public const string HeadingType = "heading-type"; + public const string Height = "height"; + public const string Href = "href"; + public const string Id = "id"; + public const string IsExpanded = "expanded"; + public const string Label = "label"; + public const string MarginBottom = "margin-bottom"; + public const string Name = "name"; + public const string Placeholder = "placeholder"; + public const string TextBefore = "text-before"; + public const string TextAfter = "text-after"; + public const string Position = "position"; + public const string Readonly = "readonly"; + public const string Required = "required"; + public const string Role = "role"; + public const string Rows = "rows"; + public const string Size = "size"; + public const string Src = "src"; + public const string SrcSet = "srcset"; + public const string SrcSetMedia = "media"; + public const string SrcSetSizes = "sizes"; + public const string SrcSetType = "type"; + public const string SrcSetTypeApplication = "application"; + public const string SrcSetTypeAudio = "audio"; + public const string SrcSetTypeFont = "font"; + public const string SrcSetTypeImage = "image"; + public const string SrcSetTypeJson = "json"; + public const string SrcSetTypeText = "text"; + public const string SrcSetTypeVideo = "video"; + public const string SrcSetTypeXhtml = "xhtml"; + public const string SrcSetTypeXml = "xml"; + public const string Target = "target"; + public const string Title = "title"; + public const string Type = "type"; + public const string Value = "value"; + public const string Variant = "variant"; + } + + public static class CssClasses + { + public const string Accordion = "accordion"; + public const string AccordionBody = "accordion-body"; + public const string AccordionButton = "accordion-button"; + public const string AccordionCollapse = "accordion-collapse"; + public const string AccordionHeader = "accordion-header"; + public const string AccordionItem = "accordion-item"; + public const string Active = "active"; + public const string Alert = "alert"; + public const string AlertDanger = "alert-danger"; + public const string AlertDark = "alert-dark"; + public const string AlertDismissable = "alert-dismissable"; + public const string AlertInfo = "alert-info"; + public const string AlertLight = "alert-light"; + public const string AlertLink = "alert-link"; + public const string AlertPrimary = "alert-primary"; + public const string AlertSecondary = "alert-secondary"; + public const string AlertSuccess = "alert-success"; + public const string AlertWarning = "alert-warning"; + public const string AlignTop = "align-top"; + public const string Badge = "badge"; + public const string BadgeDanger = "badge-danger"; + public const string BadgeDark = "badge-dark"; + public const string BadgeInfo = "badge-info"; + public const string BadgeLight = "badge-light"; + public const string BadgePill = "badge-pill"; + public const string BadgePrimary = "badge-primary"; + public const string BadgeSecondary = "badge-secondary"; + public const string BadgeSuccess = "badge-success"; + public const string BadgeWarning = "badge-warning"; + public const string BgDanger = "bg-danger"; + public const string BgDark = "bg-dark"; + public const string BgInfo = "bg-info"; + public const string BgLight = "bg-light"; + public const string BgPrimary = "bg-primary"; + public const string BgSecondary = "bg-secondary"; + public const string BgSuccess = "bg-success"; + public const string BgWarning = "bg-warning"; + public const string Border = "border"; + public const string Border_ = "border-"; + public const string BorderBottom = "border-bottom"; + public const string BorderBottomDanger = "border-bottom-danger"; + public const string BorderBottomDark = "border-bottom-dark"; + public const string BorderBottomInfo = "border-bottom-info"; + public const string BorderBottomLight = "border-bottom-light"; + public const string BorderBottomPrimary = "border-bottom-primary"; + public const string BorderBottomSecondary = "border-bottom-secondary"; + public const string BorderBottomSuccess = "border-bottom-success"; + public const string BorderBottomWarning = "border-bottom-warning"; + public const string BorderDanger = "border-danger"; + public const string BorderDark = "border-dark"; + public const string BorderInfo = "border-info"; + public const string BorderLeft = "border-left"; + public const string BorderLeftDanger = "border-left-danger"; + public const string BorderLeftDark = "border-left-dark"; + public const string BorderLeftInfo = "border-left-info"; + public const string BorderLeftLight = "border-left-light"; + public const string BorderLeftPrimary = "border-left-primary"; + public const string BorderLeftSecondary = "border-left-secondary"; + public const string BorderLeftSuccess = "border-left-success"; + public const string BorderLeftWarning = "border-left-warning"; + public const string BorderLight = "border-light"; + public const string BorderPrimary = "border-primary"; + public const string BorderRight = "border-right"; + public const string BorderRightDanger = "border-right-danger"; + public const string BorderRightDark = "border-right-dark"; + public const string BorderRightInfo = "border-right-info"; + public const string BorderRightLight = "border-right-light"; + public const string BorderRightPrimary = "border-right-primary"; + public const string BorderRightSecondary = "border-right-secondary"; + public const string BorderRightSuccess = "border-right-success"; + public const string BorderRightWarning = "border-right-warning"; + public const string BorderSecondary = "border-secondary"; + public const string BorderSuccess = "border-success"; + public const string BorderTop = "border-top"; + public const string BorderTopDanger = "border-top-danger"; + public const string BorderTopDark = "border-top-dark"; + public const string BorderTopInfo = "border-top-info"; + public const string BorderTopLight = "border-top-light"; + public const string BorderTopPrimary = "border-top-primary"; + public const string BorderTopSecondary = "border-top-secondary"; + public const string BorderTopSuccess = "border-top-success"; + public const string BorderTopWarning = "border-top-warning"; + public const string BorderWarning = "border-warning"; + public const string Breadcrumb = "breadcrumb"; + public const string BreadcrumbItem = "breadcrumb-item"; + public const string Btn = "btn"; + public const string BtnBlock = "btn-block"; + public const string BtnDanger = "btn-danger"; + public const string BtnDark = "btn-dark"; + public const string BtnGroup = "btn-group"; + public const string BtnGroupJustified = "btn-group-justified"; + public const string BtnGroupLg = "btn-group-lg"; + public const string BtnGroupSm = "btn-group-sm"; + public const string BtnGroupVertical = "btn-group-vertical"; + public const string BtnInfo = "btn-info"; + public const string BtnLg = "btn-lg"; + public const string BtnLight = "btn-light"; + public const string BtnLink = "btn-link"; + public const string BtnOutlineDanger = "btn-outline-danger"; + public const string BtnOutlineDark = "btn-outline-dark"; + public const string BtnOutlineInfo = "btn-outline-info"; + public const string BtnOutlineLight = "btn-outline-light"; + public const string BtnOutlinePrimary = "btn-outline-primary"; + public const string BtnOutlineSecondary = "btn-outline-secondary"; + public const string BtnOutlineSuccess = "btn-outline-success"; + public const string BtnOutlineWarning = "btn-outline-warning"; + public const string BtnPrimary = "btn-primary"; + public const string BtnSecondary = "btn-secondary"; + public const string BtnSm = "btn-sm"; + public const string BtnSuccess = "btn-success"; + public const string BtnToolbar = "btn-toolbar"; + public const string BtnWarning = "btn-warning"; + public const string Card = "card"; + public const string CardBody = "card-body"; + public const string CardColumns = "card-columns"; + public const string CardDeck = "card-deck"; + public const string CardFooter = "card-footer"; + public const string CardGroup = "card-group"; + public const string CardHeader = "card-header"; + public const string CardImg = "card-img"; + public const string CardImgBottom = "card-img-bottom"; + public const string CardImgOverlay = "card-img-overlay"; + public const string CardImgTop = "card-img-top"; + public const string CardLink = "card-link"; + public const string CardSubtitle = "card-subtitle"; + public const string CardText = "card-text"; + public const string CardTitle = "card-title"; + public const string Carousel = "carousel"; + public const string CarouselCaption = "carousel-caption"; + public const string CarouselControlNext = "carousel-control-next"; + public const string CarouselControlPrev = "carousel-control-prev"; + public const string CarouselIndicators = "carousel-indicators"; + public const string CarouselInner = "carousel-inner"; + public const string CarouselItem = "carousel-item"; + public const string Close = "close"; + public const string Collapse = "collapse"; + public const string Collapsed = "collapsed"; + public const string Container = "container"; + public const string ContainerFluid = "container-fluid"; + public const string CustomControl = "custom-control"; + public const string CustomControlDisabled = "custom-control-disabled"; + public const string CustomControlInline = "custom-control-inline"; + public const string CustomControlInput = "custom-control-input"; + public const string CustomControlInvalid = "custom-control-invalid"; + public const string CustomControlLabel = "custom-control-label"; + public const string CustomControlSwitch = "custom-control-switch"; + public const string CustomControlSwitchIcon = "custom-control-switch-icon"; + public const string CustomControlSwitchIconChecked = "custom-control-switch-icon-checked"; + public const string CustomControlSwitchIconUnchecked = + "custom-control-switch-icon-unchecked"; + public const string CustomControlSwitchIndicator = "custom-control-switch-indicator"; + public const string CustomControlSwitchInput = "custom-control-switch-input"; + public const string CustomControlSwitchLabel = "custom-control-switch-label"; + public const string CustomControlValid = "custom-control-valid"; + public const string DBlock = "d-block"; + public const string DFlex = "d-flex"; + public const string DInline = "d-inline"; + public const string DInlineBlock = "d-inline-block"; + public const string DInlineFlex = "d-inline-flex"; + public const string Disabled = "disabled"; + public const string DNone = "d-none"; + public const string DPrintBlock = "d-print-block"; + public const string DPrintInline = "d-print-inline"; + public const string DPrintInlineBlock = "d-print-inline-block"; + public const string DPrintNone = "d-print-none"; + public const string DPrintTable = "d-print-table"; + public const string DPrintTableCell = "d-print-table-cell"; + public const string DPrintTableColumn = "d-print-table-column"; + public const string DPrintTableRow = "d-print-table-row"; + public const string Dropdown = "dropdown"; + public const string DropdownDivider = "dropdown-divider"; + public const string DropdownItem = "dropdown-item"; + public const string DropdownMenu = "dropdown-menu"; + public const string DropdownToggle = "dropdown-toggle"; + public const string DSmBlock = "d-sm-block"; + public const string DSmFlex = "d-sm-flex"; + public const string DSmInline = "d-sm-inline"; + public const string DSmInlineBlock = "d-sm-inline-block"; + public const string DSmInlineFlex = "d-sm-inline-flex"; + public const string DSmNone = "d-sm-none"; + public const string DSmPrintBlock = "d-sm-print-block"; + public const string DSmPrintInline = "d-sm-print-inline"; + public const string DSmPrintInlineBlock = "d-sm-print-inline-block"; + public const string DSmPrintNone = "d-sm-print-none"; + public const string DSmPrintTable = "d-sm-print-table"; + public const string DSmPrintTableCell = "d-sm-print-table-cell"; + public const string DSmPrintTableColumn = "d-sm-print-table-column"; + public const string DSmPrintTableRow = "d-sm-print-table-row"; + public const string DSmTable = "d-sm-table"; + public const string DSmTableCell = "d-sm-table-cell"; + public const string DSmTableColumn = "d-sm-table-column"; + public const string DSmTableRow = "d-sm-table-row"; + public const string MeAuto = "me-auto"; + public const string Nav = TagNames.Nav; + public const string Navbar = TagNames.Navbar; + public const string NavbarBrand = "navbar-brand"; + public const string NavbarCollapse = "navbar-collapse"; + public const string NavbarDark = "navbar-dark"; + public const string NavbarExpandLg = "navbar-expand-lg"; + public const string NavbarExpandMd = "navbar-expand-md"; + public const string NavbarExpandSm = "navbar-expand-sm"; + public const string NavbarExpandXl = "navbar-expand-xl"; + public const string NavbarExpandXs = "navbar-expand-xs"; + public const string NavbarFixedBottom = "navbar-fixed-bottom"; + public const string NavbarFixedTop = "navbar-fixed-top"; + public const string NavbarFormInline = "navbar-form-inline"; + public const string NavbarLight = "navbar-light"; + public const string NavbarNav = TagNames.NavbarNav; + public const string NavbarNavDropdown = "navbar-nav-dropdown"; + public const string NavbarNavFill = "navbar-nav-fill"; + public const string NavbarNavJustified = "navbar-nav-justified"; + public const string NavbarNavPills = "navbar-nav-pills"; + public const string NavbarNavTabs = "navbar-nav-tabs"; + public const string NavbarNavVertical = "navbar-nav-vertical"; + public const string NavbarText = "navbar-text"; + public const string NavbarToggleable = "navbar-toggleable"; + public const string NavbarToggleableBrand = "navbar-toggleable-brand"; + public const string NavbarToggleableCollapse = "navbar-toggleable-collapse"; + public const string NavbarToggleableContent = "navbar-toggleable-content"; + public const string NavbarToggleableForm = "navbar-toggleable-form"; + public const string NavbarToggleableLg = "navbar-toggleable-lg"; + public const string NavbarToggleableMd = "navbar-toggleable-md"; + public const string NavbarToggleableNav = "navbar-toggleable-nav"; + public const string NavbarToggleableNavBrand = "navbar-toggleable-nav-brand"; + public const string NavbarToggleableNavCollapse = "navbar-toggleable-nav-collapse"; + public const string NavbarToggleableNavContent = "navbar-toggleable-nav-content"; + public const string NavbarToggleableNavDropdown = "navbar-toggleable-nav-dropdown"; + public const string NavbarToggleableNavFill = "navbar-toggleable-nav-fill"; + public const string NavbarToggleableNavForm = "navbar-toggleable-nav-form"; + public const string NavbarToggleableNavJustified = "navbar-toggleable-nav-justified"; + public const string NavbarToggleableNavPills = "navbar-toggleable-nav-pills"; + public const string NavbarToggleableNavTabs = "navbar-toggleable-nav-tabs"; + public const string NavbarToggleableNavText = "navbar-toggleable-nav-text"; + public const string NavbarToggleableNavToggler = "navbar-toggleable-nav-toggler"; + public const string NavbarToggleableNavTogglerIcon = "navbar-toggleable-nav-toggler-icon"; + public const string NavbarToggleableNavVertical = "navbar-toggleable-nav-vertical"; + public const string NavbarToggleableSm = "navbar-toggleable-sm"; + public const string NavbarToggleableText = "navbar-toggleable-text"; + public const string NavbarToggleableToggler = "navbar-toggleable-toggler"; + public const string NavbarToggleableTogglerIcon = "navbar-toggleable-toggler-icon"; + public const string NavbarToggleableXl = "navbar-toggleable-xl"; + public const string NavbarToggleableXs = "navbar-toggleable-xs"; + public const string NavbarToggler = "navbar-toggler"; + public const string NavbarTogglerIcon = "navbar-toggler-icon"; + public const string NavItem = "nav-item"; + public const string Navlink = "nav-link"; + public const string NavRight = "nav-right"; + public const string NavText = "nav-text"; + public const string Offset1 = "offset-1"; + public const string Offset2 = "offset-2"; + public const string Offset3 = "offset-3"; + public const string Offset4 = "offset-4"; + public const string Offset5 = "offset-5"; + public const string Offset6 = "offset-6"; + public const string Offset8 = "offset-8"; + public const string PaddingX1 = "px-1"; + public const string PaddingX2 = "px-2"; + public const string PaddingX3 = "px-3"; + public const string PaddingX4 = "px-4"; + public const string PaddingX5 = "px-5"; + public const string PaddingY1 = "py-1"; + public const string PaddingY2 = "py-2"; + public const string PaddingY3 = "py-3"; + public const string PaddingY4 = "py-4"; + public const string PaddingY5 = "py-5"; + public const string PageHeader = "page-header"; + public const string PageItem = "page-item"; + public const string PageLink = "page-link"; + public const string Pagination = "pagination"; + public const string PaginationLg = "pagination-lg"; + public const string PaginationSm = "pagination-sm"; + public const string Panel = "panel"; + public const string PanelBody = "panel-body"; + public const string PanelCollapse = "panel-collapse"; + public const string PanelDanger = "panel-danger"; + public const string PanelDefault = "panel-default"; + public const string PanelFooter = "panel-footer"; + public const string PanelGroup = "panel-group"; + public const string PanelHeading = "panel-heading"; + public const string PanelInfo = "panel-info"; + public const string PanelPrimary = "panel-primary"; + public const string PanelSuccess = "panel-success"; + public const string PanelTitle = "panel-title"; + public const string PanelWarning = "panel-warning"; + public const string PositionAbsolute = "position-absolute"; + public const string PositionFixed = "position-fixed"; + public const string PositionRelative = "position-relative"; + public const string PositionStatic = "position-static"; + public const string PositionSticky = "position-sticky"; + public const string Progress = "progress"; + public const string ProgressBar = "progress-bar"; + public const string ProgressBarAnimated = "progress-bar-animated"; + public const string ProgressBarDanger = "progress-bar-danger"; + public const string ProgressBarInfo = "progress-bar-info"; + public const string ProgressBarStriped = "progress-bar-striped"; + public const string ProgressBarSuccess = "progress-bar-success"; + public const string ProgressBarWarning = "progress-bar-warning"; + public const string ProgressLg = "progress-lg"; + public const string ProgressSm = "progress-sm"; + public const string ProgressStriped = "progress-striped"; + public const string Row = "row"; + public const string Show = "show"; + public const string StickyTop = "sticky-top"; + public const string TextBackground_ = "text-bg-"; + } +} diff --git a/src/TagHelpers/TagHelpers/AuthzTagHelper.cs b/src/TagHelpers/TagHelpers/AuthzTagHelper.cs new file mode 100644 index 00000000..19321992 --- /dev/null +++ b/src/TagHelpers/TagHelpers/AuthzTagHelper.cs @@ -0,0 +1,142 @@ +/* + * AuthzTagHelper.cs + * + * Created: 2024-39-16T11:39:42-05:00 + * Modified: 2024-39-16T11:39:43-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers; + +/// +/// Suppresses rendering of an element unless specific authorization policies are met. +/// +[HtmlTargetElement(TagNames.Any, Attributes = AspAuthzAttributeName)] +[HtmlTargetElement(TagNames.Any, Attributes = AspAuthzPolicyAttributeName)] +public class AuthzTagHelper : TagHelper +{ + internal static readonly object SuppressedKey = new(); + internal static readonly object SuppressedValue = new(); + + private const string AspAuthzAttributeName = "asp-authz"; + private const string AspAuthzPolicyAttributeName = "asp-authz-policy"; + + private readonly IAuthorizationService _authz; + + /// + /// Creates a new instance of the class. + /// + /// The . + public AuthzTagHelper(IAuthorizationService authz) + { + _authz = authz; + } + + /// + // Run before other Tag Helpers (default Order is 0) so they can cooperatively decide not to run. + // Note this value is coordinated with the value of IfTagHelper.Order to ensure the IfTagHelper logic runs first. + // (Lower values run earlier). + public override int Order => -10; + + /// + /// A boolean indicating whether the current element requires authentication in order to be rendered. + /// + [HtmlAttributeName(AspAuthzAttributeName)] + public bool RequiresAuthentication { get; set; } + + /// + /// An authorization policy name that must be satisfied in order for the current element to be rendered. + /// + [HtmlAttributeName(AspAuthzPolicyAttributeName)] + public string RequiredPolicy { get; set; } + + /// + /// Gets or sets the . + /// + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + /// + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (context.SuppressedByAspIf() || context.SuppressedByAspAuthz()) + { + return; + } + + var requiresAuth = RequiresAuthentication || !IsNullOrEmpty(RequiredPolicy); + var showOutput = false; + + var user = ViewContext.HttpContext.User; + if ( + context.AllAttributes[AspAuthzAttributeName] != null + && !requiresAuth + && !user.Identity.IsAuthenticated + ) + { + // authz="false" & user isn't authenticated + showOutput = true; + } + else if (!IsNullOrEmpty(RequiredPolicy)) + { + // auth-policy="foo" & user is authorized for policy "foo" + var cacheKey = AspAuthzPolicyAttributeName + "." + RequiredPolicy; + bool authorized; + var cachedResult = ViewContext.ViewData[cacheKey]; + if (cachedResult != null) + { + authorized = (bool)cachedResult; + } + else + { + var authResult = await _authz.AuthorizeAsync(user, ViewContext, RequiredPolicy); + authorized = authResult.Succeeded; + ViewContext.ViewData[cacheKey] = authorized; + } + + showOutput = authorized; + } + else if (requiresAuth && user.Identity.IsAuthenticated) + { + // auth="true" & user is authenticated + showOutput = true; + } + + if (!showOutput) + { + output.SuppressOutput(); + context.Items[SuppressedKey] = SuppressedValue; + } + } +} + +/// +/// Extension methods for . +/// +public static class AuthzTagHelperContextExtensions +{ + /// + /// Determines if the (asp-authz) has suppressed rendering for the element associated with + /// this . + /// + /// The . + /// true if asp-authz suppressed rendering of this Tag Helper. + public static bool SuppressedByAspAuthz(this TagHelperContext context) => + context.Items.TryGetValue(AuthzTagHelper.SuppressedKey, out var value) + && value == AuthzTagHelper.SuppressedValue; +} diff --git a/src/TagHelpers/TagHelpers/ColorsTagHelper.cs b/src/TagHelpers/TagHelpers/ColorsTagHelper.cs new file mode 100644 index 00000000..1457226f --- /dev/null +++ b/src/TagHelpers/TagHelpers/ColorsTagHelper.cs @@ -0,0 +1,79 @@ +/* + * ColorsTagHelper.cs + * + * Created: 2024-40-17T09:40:05-05:00 + * Modified: 2024-22-18T12:22:17-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.TagHelpers; + +[HtmlTargetElement(TagNames.Any, Attributes = ColorAttributeName)] +[HtmlTargetElement(TagNames.Any, Attributes = BackgroundColorAttributeName)] +[HtmlTargetElement(TagNames.Any, Attributes = BorderColorAttributeName)] +public class VariantTagHelper : TagHelper +{ + #region --- Attribute Names --- + + private const string ColorAttributeName = "bs-color"; + + #endregion + + #region --- Properties --- + + [HtmlAttributeName(ColorAttributeName)] + public Color Color { get; set; } = Color.None; + + #endregion + #region --- Attribute Names --- + + private const string BackgroundColorAttributeName = "bs-bg-color"; + private const string BorderColorAttributeName = "bs-border-color"; + #endregion + + #region --- Properties --- + + + /// The color of the background of the element + [HtmlAttributeName(BackgroundColorAttributeName)] + public Color BackgroundColor { get; set; } = Color.None; + + /// The color of the element's border + [HtmlAttributeName(BorderColorAttributeName)] + public Color BorderColor { get; set; } = Color.None; + + public static string ColorCssClass(TagHelperContext context) => + context.TagName switch + { + TagNames.Span => "badge", + TagNames.Div => "alert", + TagNames.Alert => "alert", + TagNames.Anchor => "link", + TagNames.Table => "table", + TagNames.Button => "btn", + TagNames.Input + when context.AllAttributes.ContainsName("type") + && context.AllAttributes["type"].Value.ToString() is "button" + => "btn", + _ => string.Empty, + }; + + #endregion + + public override int Order => int.MaxValue; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await output.GetChildContentAsync(); + + output.AddCssClass(ColorCssClass(context)); + output.AddCssClass($"{Color.GetColorInfo().TextCssClass}"); + + output.AddCssClass($"{BackgroundColor.GetColorInfo().BackgroundCssClass}"); + output.AddCssClass($"{BackgroundColor.GetColorInfo().BorderCssClass}"); + } +} diff --git a/src/TagHelpers/TagHelpers/IfTagHelper.cs b/src/TagHelpers/TagHelpers/IfTagHelper.cs new file mode 100644 index 00000000..220f5867 --- /dev/null +++ b/src/TagHelpers/TagHelpers/IfTagHelper.cs @@ -0,0 +1,74 @@ +/* + * IfTagHelper.cs + * + * Created: 2024-31-15T22:31:30-05:00 + * Modified: 2024-31-15T22:31:31-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + + +namespace Dgmjr.AspNetCore.TagHelpers; + +/// +/// Suppresses the output of the element if the supplied predicate equates to false. +/// +[HtmlTargetElement("*", Attributes = AttributeNames.AspIf)] +public class IfTagHelper : TagHelper +{ + internal static readonly object SuppressedKey = new(); + internal static readonly object SuppressedValue = new(); + + /// + /// Gets or sets the predicate expression to test. + /// + [HtmlAttributeName(AttributeNames.AspIf)] + public bool Predicate { get; set; } + + /// + /// + /// Run before other Tag Helpers (default Order is 0) so they can cooperatively decide not to run. + /// Note this value is coordinated with the value of AuthzTagHelper.Order to ensure the IfTagHelper logic runs first. + /// (Lower values run earlier). + /// + public override int Order => -100; + + /// + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (!Predicate) + { + output.SuppressOutput(); + context.Items[SuppressedKey] = SuppressedValue; + } + } +} + +/// +/// Extension methods for . +/// +public static class IfTagHelperContextExtensions +{ + /// + /// Determines if the (asp-if) has suppressed rendering for the element associated with + /// this . + /// + /// The . + /// true if asp-if evaluated to false, else false. + public static bool SuppressedByAspIf(this TagHelperContext context) => + context.Items.TryGetValue(IfTagHelper.SuppressedKey, out var value) + && value == IfTagHelper.SuppressedValue; +} diff --git a/src/TagHelpers/icon.png b/src/TagHelpers/icon.png new file mode 100644 index 00000000..db07a039 Binary files /dev/null and b/src/TagHelpers/icon.png differ