From 9c18c50297a68598bbdc4091c74ff1e1920f9931 Mon Sep 17 00:00:00 2001 From: VahidN Date: Mon, 11 Dec 2023 19:04:50 +0330 Subject: [PATCH] Upgrade to .NET 8.0, Close #166 --- .editorconfig | 3 + .github/workflows/build.yml | 6 +- .github/workflows/codeql.yml | 86 ++++++++++++++++ .github/workflows/codeql/codeql-config.yml | 10 ++ global.json | 2 +- .../CaptchaServiceCollectionExtensions.cs | 58 +++++------ src/DNTCaptcha.Core/DNTCaptcha.Core.csproj | 17 ++-- .../DNTCaptchaImageController.cs | 99 +++++++------------ .../DNTCaptchaRateLimiterPolicy.cs | 42 ++++---- src/DNTCaptcha.Core/DNTCaptchaTagHelper.cs | 2 +- .../DistributedSerializationProvider.cs | 23 +++-- .../InMemorySerializationProvider.cs | 27 ++--- .../DNTCaptcha.TestApiApp.csproj | 2 +- .../DNTCaptcha.TestRazorPages.csproj | 2 +- .../DNTCaptcha.TestWebApp.csproj | 6 +- 15 files changed, 229 insertions(+), 156 deletions(-) create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/codeql/codeql-config.yml diff --git a/.editorconfig b/.editorconfig index 3d96862..da5e01a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,9 @@ charset = utf-8-bom [*.cs] +dotnet_diagnostic.ASP0023.severity = suggestion +dotnet_diagnostic.CA1510.severity = suggestion + dotnet_diagnostic.SYSLIB0021.severity = suggestion dotnet_diagnostic.SYSLIB0020.severity = suggestion diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7fc7d7..4ca0ce6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,11 @@ jobs: runs-on: windows-2019 steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v4 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.400 + dotnet-version: 8.0.x - name: Build DNTCaptcha.Core lib run: dotnet build ./src/DNTCaptcha.Core/DNTCaptcha.Core.csproj --configuration Release diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..fc359d9 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,86 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: windows-2019 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + config-file: ./.github/workflows/codeql/codeql-config.yml + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0.x + - name: Build + run: dotnet build --configuration Release + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/codeql/codeql-config.yml b/.github/workflows/codeql/codeql-config.yml new file mode 100644 index 0000000..f528f04 --- /dev/null +++ b/.github/workflows/codeql/codeql-config.yml @@ -0,0 +1,10 @@ +name: "Security and Quality" + +queries: + - uses: security-and-quality + +query-filters: + - exclude: + id: cs/useless-if-statement + - exclude: + id: cs/empty-block diff --git a/global.json b/global.json index afa700d..72d38cd 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.400", + "version": "8.0.100", "rollForward": "latestMajor", "allowPrerelease": false } diff --git a/src/DNTCaptcha.Core/CaptchaServiceCollectionExtensions.cs b/src/DNTCaptcha.Core/CaptchaServiceCollectionExtensions.cs index 55c2fde..48beeea 100644 --- a/src/DNTCaptcha.Core/CaptchaServiceCollectionExtensions.cs +++ b/src/DNTCaptcha.Core/CaptchaServiceCollectionExtensions.cs @@ -21,10 +21,7 @@ public static void AddDNTCaptcha( this IServiceCollection services, Action? options = null) { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } + if (services == null) throw new ArgumentNullException(nameof(services)); configOptions(services, options); @@ -34,11 +31,11 @@ public static void AddDNTCaptcha( services.AddMvcCore().AddCookieTempDataProvider(); -#if NET7_0 +#if NET7_0 || NET8_0 // Also we need to have app.UseRateLimiter() after app.UseRouting() services.AddRateLimiter(limiterOptions => - limiterOptions.AddPolicy( - DNTCaptchaRateLimiterPolicy.Name)); + limiterOptions.AddPolicy( + DNTCaptchaRateLimiterPolicy.Name)); #endif services.TryAddSingleton(); @@ -46,8 +43,8 @@ public static void AddDNTCaptcha( services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton>(serviceProvider => - key => GetCaptchaTextProvider(key, - serviceProvider)); + key => GetCaptchaTextProvider(key, + serviceProvider)); services.TryAddSingleton(); services.TryAddSingleton(); @@ -57,31 +54,28 @@ public static void AddDNTCaptcha( services.TryAddScoped(); services.TryAddSingleton(); services.TryAddScoped(serviceProvider => - { - var actionContext = serviceProvider - .GetRequiredService() - .ActionContext; - if (actionContext is null) - { - throw new InvalidOperationException("actionContext is null"); - } + { + var actionContext = serviceProvider + .GetRequiredService() + .ActionContext; + if (actionContext is null) throw new InvalidOperationException("actionContext is null"); - var factory = serviceProvider.GetRequiredService(); - return factory.GetUrlHelper(actionContext); - }); + var factory = serviceProvider.GetRequiredService(); + return factory.GetUrlHelper(actionContext); + }); } private static ICaptchaTextProvider GetCaptchaTextProvider(DisplayMode key, IServiceProvider serviceProvider) { return key switch - { - DisplayMode.NumberToWord => serviceProvider.GetRequiredService(), - DisplayMode.ShowDigits => serviceProvider.GetRequiredService(), - DisplayMode.SumOfTwoNumbers => serviceProvider.GetRequiredService(), - DisplayMode.SumOfTwoNumbersToWords => - serviceProvider.GetRequiredService(), - _ => throw new InvalidOperationException($"Service of type {key} is not implemented."), - }; + { + DisplayMode.NumberToWord => serviceProvider.GetRequiredService(), + DisplayMode.ShowDigits => serviceProvider.GetRequiredService(), + DisplayMode.SumOfTwoNumbers => serviceProvider.GetRequiredService(), + DisplayMode.SumOfTwoNumbersToWords => + serviceProvider.GetRequiredService(), + _ => throw new InvalidOperationException($"Service of type {key} is not implemented.") + }; } private static void configOptions(IServiceCollection services, Action? options) @@ -96,24 +90,16 @@ private static void configOptions(IServiceCollection services, Action(); - } else - { services.TryAddSingleton(typeof(ISerializationProvider), captchaOptions.CaptchaSerializationProvider); - } } private static void setCaptchaStorageProvider(IServiceCollection services, DNTCaptchaOptions captchaOptions) { if (captchaOptions.CaptchaStorageProvider == null) - { services.TryAddSingleton(); - } else - { services.TryAddSingleton(typeof(ICaptchaStorageProvider), captchaOptions.CaptchaStorageProvider); - } } } \ No newline at end of file diff --git a/src/DNTCaptcha.Core/DNTCaptcha.Core.csproj b/src/DNTCaptcha.Core/DNTCaptcha.Core.csproj index 36f0c16..a5237fd 100644 --- a/src/DNTCaptcha.Core/DNTCaptcha.Core.csproj +++ b/src/DNTCaptcha.Core/DNTCaptcha.Core.csproj @@ -1,9 +1,9 @@ DNTCaptcha.Core is a captcha generator and validator for ASP.NET Core applications. - 4.9.1 + 4.9.2 Vahid Nasiri - net7.0;net6.0; + net8.0;net7.0;net6.0; true DNTCaptcha.Core DNTCaptcha.Core @@ -42,11 +42,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -58,7 +58,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -67,12 +67,12 @@ anycpu - + - + NET6_0 @@ -80,4 +80,7 @@ NET7_0 + + NET8_0 + diff --git a/src/DNTCaptcha.Core/DNTCaptchaImageController.cs b/src/DNTCaptcha.Core/DNTCaptchaImageController.cs index 9066bd9..0fa5243 100644 --- a/src/DNTCaptcha.Core/DNTCaptchaImageController.cs +++ b/src/DNTCaptcha.Core/DNTCaptchaImageController.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -#if NET7_0 +#if NET7_0 || NET8_0 using Microsoft.AspNetCore.RateLimiting; #endif @@ -24,7 +24,7 @@ namespace DNTCaptcha.Core; /// [Route("[controller]")] [AllowAnonymous] -#if NET7_0 +#if NET7_0 || NET8_0 [EnableRateLimiting(DNTCaptchaRateLimiterPolicy.Name)] #endif public class DNTCaptchaImageController : Controller @@ -88,22 +88,13 @@ public IActionResult Refresh(string data) { try { - if (string.IsNullOrWhiteSpace(data)) - { - return BadRequest(TheReceivedDataIsNullOrEmpty); - } + if (string.IsNullOrWhiteSpace(data)) return BadRequest(TheReceivedDataIsNullOrEmpty); var decryptedModel = _captchaProtectionProvider.Decrypt(data); - if (decryptedModel == null) - { - return BadRequest(CouldntDecryptTheReceivedData); - } + if (decryptedModel == null) return BadRequest(CouldntDecryptTheReceivedData); var model = _serializationProvider.Deserialize(decryptedModel); - if (model == null) - { - return BadRequest(IsYourNetworkDistributed); - } + if (model == null) return BadRequest(IsYourNetworkDistributed); invalidateToken(model); @@ -127,42 +118,40 @@ public IActionResult Refresh(string data) tagHelper.ShowRefreshButton = model.ShowRefreshButton; var tagHelperContext = new TagHelperContext( - new TagHelperAttributeList(), - new Dictionary { { typeof(IUrlHelper), Url } }, - Guid.NewGuid().ToString("N")); + new TagHelperAttributeList(), + new Dictionary { { typeof(IUrlHelper), Url } }, + Guid.NewGuid().ToString("N")); var tagHelperOutput = new TagHelperOutput( - "div", - new TagHelperAttributeList(), - (useCachedResult, encoder) => - { - var tagHelperContent = new DefaultTagHelperContent(); - tagHelperContent.SetContent(string.Empty); - return Task.FromResult(tagHelperContent); - }); + "div", + new TagHelperAttributeList(), + (useCachedResult, encoder) => + { + var tagHelperContent = new DefaultTagHelperContent(); + tagHelperContent.SetContent(string.Empty); + return Task.FromResult(tagHelperContent); + }); tagHelper.ViewContext = ViewContext ?? new ViewContext( - new ActionContext(HttpContext, - HttpContext.GetRouteData(), - ControllerContext.ActionDescriptor), - new FakeView(), - new - ViewDataDictionary(new EmptyModelMetadataProvider(), - new ModelStateDictionary()) - { - Model = null, - }, - new TempDataDictionary(HttpContext, - _tempDataProvider), - TextWriter.Null, - new HtmlHelperOptions()); + new ActionContext(HttpContext, + HttpContext.GetRouteData(), + ControllerContext.ActionDescriptor), + new FakeView(), + new + ViewDataDictionary(new EmptyModelMetadataProvider(), + new ModelStateDictionary()) + { + Model = null + }, + new TempDataDictionary(HttpContext, + _tempDataProvider), + TextWriter.Null, + new HtmlHelperOptions()); tagHelper.Process(tagHelperContext, tagHelperOutput); var attrs = new StringBuilder(); foreach (var attr in tagHelperOutput.Attributes) - { attrs.Append(' ').Append(attr.Name).Append("='").Append(attr.Value).Append('\''); - } var content = $"
{tagHelperOutput.Content.GetContent()}
"; return Content(content); @@ -189,34 +178,22 @@ public IActionResult Show(string data) { try { - if (string.IsNullOrWhiteSpace(data)) - { - return BadRequest(TheReceivedDataIsNullOrEmpty); - } + if (string.IsNullOrWhiteSpace(data)) return BadRequest(TheReceivedDataIsNullOrEmpty); var decryptedModel = _captchaProtectionProvider.Decrypt(data); - if (decryptedModel == null) - { - return BadRequest(CouldntDecryptTheReceivedData); - } + if (decryptedModel == null) return BadRequest(CouldntDecryptTheReceivedData); var model = _serializationProvider.Deserialize(decryptedModel); - if (model == null) - { - return BadRequest(IsYourNetworkDistributed); - } + if (model == null) return BadRequest(IsYourNetworkDistributed); var decryptedText = _captchaProtectionProvider.Decrypt(model.Text); - if (decryptedText == null) - { - return BadRequest("Couldn't decrypt the text."); - } + if (decryptedText == null) return BadRequest("Couldn't decrypt the text."); var image = _captchaImageProvider.DrawCaptcha(decryptedText, - model.ForeColor, - model.BackColor, - model.FontSize, - model.FontName); + model.ForeColor, + model.BackColor, + model.FontSize, + model.FontName); return new FileContentResult(image, "image/png"); } catch (Exception ex) diff --git a/src/DNTCaptcha.Core/DNTCaptchaRateLimiterPolicy.cs b/src/DNTCaptcha.Core/DNTCaptchaRateLimiterPolicy.cs index 70032b5..4115e7c 100644 --- a/src/DNTCaptcha.Core/DNTCaptchaRateLimiterPolicy.cs +++ b/src/DNTCaptcha.Core/DNTCaptchaRateLimiterPolicy.cs @@ -1,4 +1,4 @@ -#if NET7_0 +#if NET7_0 || NET8_0 using System; using System.Globalization; using System.Threading; @@ -25,31 +25,33 @@ public class DNTCaptchaRateLimiterPolicy : IRateLimiterPolicy /// /// limiting the number of requests that can be made in a given period of time /// - public DNTCaptchaRateLimiterPolicy(IOptions options) => + public DNTCaptchaRateLimiterPolicy(IOptions options) + { _options = options == null ? throw new ArgumentNullException(nameof(options)) : options.Value; + } /// public Func? OnRejected { get; } = (context, cancellationToken) => - { - if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) - { - context.HttpContext.Response.Headers.RetryAfter = - ((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo); - } + { + if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) + context.HttpContext.Response.Headers.RetryAfter = + ((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo); - context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; - return default; - }; + context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; + return default; + }; /// - public RateLimitPartition GetPartition(HttpContext httpContext) => - RateLimitPartition.GetFixedWindowLimiter(httpContext.GetClientIP(), - partition => new FixedWindowRateLimiterOptions - { - AutoReplenishment = true, - PermitLimit = _options.PermitLimit, - QueueLimit = 0, - Window = TimeSpan.FromMinutes(1), - }); + public RateLimitPartition GetPartition(HttpContext httpContext) + { + return RateLimitPartition.GetFixedWindowLimiter(httpContext.GetClientIP(), + partition => new FixedWindowRateLimiterOptions + { + AutoReplenishment = true, + PermitLimit = _options.PermitLimit, + QueueLimit = 0, + Window = TimeSpan.FromMinutes(1) + }); + } } #endif \ No newline at end of file diff --git a/src/DNTCaptcha.Core/DNTCaptchaTagHelper.cs b/src/DNTCaptcha.Core/DNTCaptchaTagHelper.cs index 7474606..34b9c09 100644 --- a/src/DNTCaptcha.Core/DNTCaptchaTagHelper.cs +++ b/src/DNTCaptcha.Core/DNTCaptchaTagHelper.cs @@ -300,7 +300,7 @@ private TagBuilder getRefreshButtonTagBuilder(ViewContext viewContext, string ca return refreshButton; } - private IHtmlContent getOnRefreshButtonDataAjaxScripts(ViewContext viewContext) + private DNTScriptTag getOnRefreshButtonDataAjaxScripts(ViewContext viewContext) { var requestVerificationToken = _antiforgery.GetAndStoreTokens(viewContext.HttpContext).RequestToken; return new diff --git a/src/DNTCaptcha.Core/DistributedSerializationProvider.cs b/src/DNTCaptcha.Core/DistributedSerializationProvider.cs index 1554a6a..b7b6e32 100644 --- a/src/DNTCaptcha.Core/DistributedSerializationProvider.cs +++ b/src/DNTCaptcha.Core/DistributedSerializationProvider.cs @@ -14,6 +14,10 @@ public class DistributedSerializationProvider : ISerializationProvider { private readonly ICaptchaCryptoProvider _captchaProtectionProvider; private readonly IDistributedCache _distributedCache; + + private readonly JsonSerializerOptions _jsonSerializerOptions = + new() { WriteIndented = false, IgnoreNullValues = true }; + private readonly ILogger _logger; private readonly DNTCaptchaOptions _options; @@ -39,17 +43,15 @@ public DistributedSerializationProvider( /// public string Serialize(object data) { - var resultBytes = JsonSerializer.SerializeToUtf8Bytes(data, - new JsonSerializerOptions - { WriteIndented = false, IgnoreNullValues = true }); + var resultBytes = JsonSerializer.SerializeToUtf8Bytes(data, _jsonSerializerOptions); var token = _captchaProtectionProvider.Hash(Encoding.UTF8.GetString(resultBytes)).HashString; _distributedCache.Set(token, - resultBytes, - new DistributedCacheEntryOptions - { - AbsoluteExpiration = - DateTimeOffset.UtcNow.AddMinutes(_options.AbsoluteExpirationMinutes), - }); + resultBytes, + new DistributedCacheEntryOptions + { + AbsoluteExpiration = + DateTimeOffset.UtcNow.AddMinutes(_options.AbsoluteExpirationMinutes) + }); return token; } @@ -61,7 +63,8 @@ public string Serialize(object data) var resultBytes = _distributedCache.Get(data); if (resultBytes == null) { - _logger.LogDebug("The registered distributed cache provider returned null. Which means your data is expired."); + _logger.LogDebug( + "The registered distributed cache provider returned null. Which means your data is expired."); return default; } diff --git a/src/DNTCaptcha.Core/InMemorySerializationProvider.cs b/src/DNTCaptcha.Core/InMemorySerializationProvider.cs index 4fd27ce..70c0b19 100644 --- a/src/DNTCaptcha.Core/InMemorySerializationProvider.cs +++ b/src/DNTCaptcha.Core/InMemorySerializationProvider.cs @@ -12,6 +12,10 @@ namespace DNTCaptcha.Core; public class InMemorySerializationProvider : ISerializationProvider { private readonly ICaptchaCryptoProvider _captchaProtectionProvider; + + private readonly JsonSerializerOptions _jsonSerializerOptions = + new() { WriteIndented = false, IgnoreNullValues = true }; + private readonly ILogger _logger; private readonly IMemoryCache _memoryCache; private readonly DNTCaptchaOptions _options; @@ -38,19 +42,17 @@ public InMemorySerializationProvider( /// public string Serialize(object data) { - var result = JsonSerializer.Serialize(data, - new JsonSerializerOptions - { WriteIndented = false, IgnoreNullValues = true }); + var result = JsonSerializer.Serialize(data, _jsonSerializerOptions); var token = _captchaProtectionProvider.Hash(result).HashString; _memoryCache.Set(token, - result, - new MemoryCacheEntryOptions - { - AbsoluteExpiration = - DateTimeOffset.UtcNow - .AddMinutes(_options.AbsoluteExpirationMinutes), - Size = 1, // the size limit is the count of entries - }); + result, + new MemoryCacheEntryOptions + { + AbsoluteExpiration = + DateTimeOffset.UtcNow + .AddMinutes(_options.AbsoluteExpirationMinutes), + Size = 1 // the size limit is the count of entries + }); return token; } @@ -61,7 +63,8 @@ public string Serialize(object data) { if (!_memoryCache.TryGetValue(data, out string? result) || result is null) { - _logger.LogDebug("The registered memory cache provider returned null. Which means your data is expired. Please read the `How to choose a correct storage mode` in the readme file. Probably a local `memory cache` shouldn't be used with your distributed servers."); + _logger.LogDebug( + "The registered memory cache provider returned null. Which means your data is expired. Please read the `How to choose a correct storage mode` in the readme file. Probably a local `memory cache` shouldn't be used with your distributed servers."); return default; } diff --git a/src/DNTCaptcha.TestApiApp/DNTCaptcha.TestApiApp.csproj b/src/DNTCaptcha.TestApiApp/DNTCaptcha.TestApiApp.csproj index 693e402..f9d40a7 100644 --- a/src/DNTCaptcha.TestApiApp/DNTCaptcha.TestApiApp.csproj +++ b/src/DNTCaptcha.TestApiApp/DNTCaptcha.TestApiApp.csproj @@ -1,6 +1,6 @@ - net7.0 + net8.0 diff --git a/src/DNTCaptcha.TestRazorPages/DNTCaptcha.TestRazorPages.csproj b/src/DNTCaptcha.TestRazorPages/DNTCaptcha.TestRazorPages.csproj index 7c6aeb8..3aaa147 100644 --- a/src/DNTCaptcha.TestRazorPages/DNTCaptcha.TestRazorPages.csproj +++ b/src/DNTCaptcha.TestRazorPages/DNTCaptcha.TestRazorPages.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 diff --git a/src/DNTCaptcha.TestWebApp/DNTCaptcha.TestWebApp.csproj b/src/DNTCaptcha.TestWebApp/DNTCaptcha.TestWebApp.csproj index 5c53e4c..74e2005 100644 --- a/src/DNTCaptcha.TestWebApp/DNTCaptcha.TestWebApp.csproj +++ b/src/DNTCaptcha.TestWebApp/DNTCaptcha.TestWebApp.csproj @@ -1,6 +1,6 @@  - net70 + net8.0 true DNTCaptcha.TestWebApp Exe @@ -15,7 +15,7 @@ - - + + \ No newline at end of file