✨ More file operations
🐛 Bug fixes on file uploading
			
			
This commit is contained in:
		| @@ -7,7 +7,7 @@ namespace DysonNetwork.Sphere.Account; | ||||
|  | ||||
| [ApiController] | ||||
| [Route("/accounts")] | ||||
| public class AccountController(AppDatabase db, IHttpContextAccessor httpContext) | ||||
| public class AccountController(AppDatabase db, IHttpContextAccessor httpContext) : ControllerBase | ||||
| { | ||||
|     [HttpGet("{name}")] | ||||
|     [ProducesResponseType<Account>(StatusCodes.Status200OK)] | ||||
| @@ -37,7 +37,7 @@ public class AccountController(AppDatabase db, IHttpContextAccessor httpContext) | ||||
|     { | ||||
|         var dupeNameCount = await db.Accounts.Where(a => a.Name == request.Name).CountAsync(); | ||||
|         if (dupeNameCount > 0) | ||||
|             return new BadRequestObjectResult("The name is already taken."); | ||||
|             return BadRequest("The name is already taken."); | ||||
|  | ||||
|         var account = new Account | ||||
|         { | ||||
| @@ -77,6 +77,6 @@ public class AccountController(AppDatabase db, IHttpContextAccessor httpContext) | ||||
|  | ||||
|         var account = await db.Accounts.FindAsync(userId); | ||||
|  | ||||
|         return new OkObjectResult(account); | ||||
|         return Ok(account); | ||||
|     } | ||||
| } | ||||
| @@ -10,7 +10,12 @@ namespace DysonNetwork.Sphere.Auth; | ||||
|  | ||||
| [ApiController] | ||||
| [Route("/auth")] | ||||
| public class AuthController(AppDatabase db, AccountService accounts, AuthService auth, IHttpContextAccessor httpContext) | ||||
| public class AuthController( | ||||
|     AppDatabase db, | ||||
|     AccountService accounts, | ||||
|     AuthService auth, | ||||
|     IHttpContextAccessor httpContext | ||||
| ) : ControllerBase | ||||
| { | ||||
|     public class ChallengeRequest | ||||
|     { | ||||
| @@ -24,7 +29,7 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService | ||||
|     public async Task<ActionResult<Challenge>> StartChallenge([FromBody] ChallengeRequest request) | ||||
|     { | ||||
|         var account = await accounts.LookupAccount(request.Account); | ||||
|         if (account is null) return new NotFoundObjectResult("Account was not found."); | ||||
|         if (account is null) return NotFound("Account was not found."); | ||||
|  | ||||
|         var ipAddress = httpContext.HttpContext?.Connection.RemoteIpAddress?.ToString(); | ||||
|         var userAgent = httpContext.HttpContext?.Request.Headers.UserAgent.ToString(); | ||||
| @@ -66,7 +71,7 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService | ||||
|             .Include(e => e.Account.AuthFactors) | ||||
|             .Where(e => e.Id == id).FirstOrDefaultAsync(); | ||||
|         return challenge is null | ||||
|             ? new NotFoundObjectResult("Auth challenge was not found.") | ||||
|             ? NotFound("Auth challenge was not found.") | ||||
|             : challenge.Account.AuthFactors.ToList(); | ||||
|     } | ||||
|  | ||||
| @@ -83,14 +88,14 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService | ||||
|     ) | ||||
|     { | ||||
|         var challenge = await db.AuthChallenges.FindAsync(id); | ||||
|         if (challenge is null) return new NotFoundObjectResult("Auth challenge was not found."); | ||||
|         if (challenge is null) return NotFound("Auth challenge was not found."); | ||||
|  | ||||
|         var factor = await db.AccountAuthFactors.FindAsync(request.FactorId); | ||||
|         if (factor is null) return new NotFoundObjectResult("Auth factor was not found."); | ||||
|         if (factor is null) return NotFound("Auth factor was not found."); | ||||
|  | ||||
|         if (challenge.StepRemain == 0) return challenge; | ||||
|         if (challenge.ExpiredAt.HasValue && challenge.ExpiredAt.Value < Instant.FromDateTimeUtc(DateTime.UtcNow)) | ||||
|             return new BadRequestResult(); | ||||
|             return BadRequest(); | ||||
|  | ||||
|         try | ||||
|         { | ||||
| @@ -102,7 +107,7 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService | ||||
|         } | ||||
|         catch | ||||
|         { | ||||
|             return new BadRequestResult(); | ||||
|             return BadRequest(); | ||||
|         } | ||||
|  | ||||
|         await db.SaveChangesAsync(); | ||||
| @@ -125,21 +130,21 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService | ||||
|             case "authorization_code": | ||||
|                 var code = Guid.TryParse(request.Code, out var codeId) ? codeId : Guid.Empty; | ||||
|                 if (code == Guid.Empty) | ||||
|                     return new BadRequestObjectResult("Invalid or missing authorization code."); | ||||
|                     return BadRequest("Invalid or missing authorization code."); | ||||
|                 var challenge = await db.AuthChallenges | ||||
|                     .Include(e => e.Account) | ||||
|                     .Where(e => e.Id == code) | ||||
|                     .FirstOrDefaultAsync(); | ||||
|                 if (challenge is null) | ||||
|                     return new NotFoundObjectResult("Authorization code not found or expired."); | ||||
|                     return BadRequest("Authorization code not found or expired."); | ||||
|                 if (challenge.StepRemain != 0) | ||||
|                     return new BadRequestObjectResult("Challenge not yet completed."); | ||||
|                     return BadRequest("Challenge not yet completed."); | ||||
|  | ||||
|                 session = await db.AuthSessions | ||||
|                     .Where(e => e.Challenge == challenge) | ||||
|                     .FirstOrDefaultAsync(); | ||||
|                 if (session is not null) | ||||
|                     return new BadRequestObjectResult("Session already exists for this challenge."); | ||||
|                     return BadRequest("Session already exists for this challenge."); | ||||
|  | ||||
|                 session = new Session | ||||
|                 { | ||||
| @@ -159,21 +164,21 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService | ||||
|                 var sessionIdClaim = token.Claims.FirstOrDefault(c => c.Type == "session_id")?.Value; | ||||
|  | ||||
|                 if (!Guid.TryParse(sessionIdClaim, out var sessionId)) | ||||
|                     return new UnauthorizedObjectResult("Invalid or missing session_id claim in refresh token."); | ||||
|                     return Unauthorized("Invalid or missing session_id claim in refresh token."); | ||||
|  | ||||
|                 session = await db.AuthSessions | ||||
|                     .Include(e => e.Account) | ||||
|                     .Include(e => e.Challenge) | ||||
|                     .FirstOrDefaultAsync(s => s.Id == sessionId); | ||||
|                 if (session is null) | ||||
|                     return new NotFoundObjectResult("Session not found or expired."); | ||||
|                     return NotFound("Session not found or expired."); | ||||
|  | ||||
|                 session.LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); | ||||
|                 await db.SaveChangesAsync(); | ||||
|  | ||||
|                 return auth.CreateToken(session); | ||||
|             default: | ||||
|                 return new BadRequestObjectResult("Unsupported grant type."); | ||||
|                 return BadRequest("Unsupported grant type."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -183,11 +188,11 @@ public class AuthController(AppDatabase db, AccountService accounts, AuthService | ||||
|     { | ||||
|         var sessionIdClaim = httpContext.HttpContext?.User.FindFirst("session_id")?.Value; | ||||
|         if (!Guid.TryParse(sessionIdClaim, out var sessionId)) | ||||
|             return new UnauthorizedResult(); | ||||
|             return Unauthorized(); | ||||
|  | ||||
|         var session = await db.AuthSessions.FirstOrDefaultAsync(s => s.Id == sessionId); | ||||
|         if (session is null) return new NotFoundResult(); | ||||
|         if (session is null) return NotFound(); | ||||
|  | ||||
|         return new OkObjectResult(session); | ||||
|         return Ok(session); | ||||
|     } | ||||
| } | ||||
| @@ -10,7 +10,9 @@ using DysonNetwork.Sphere.Auth; | ||||
| using DysonNetwork.Sphere.Storage; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.HttpOverrides; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.Extensions.Options; | ||||
| using Microsoft.IdentityModel.Tokens; | ||||
| using Microsoft.OpenApi.Models; | ||||
| using NodaTime; | ||||
| @@ -21,6 +23,8 @@ using File = System.IO.File; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
|  | ||||
| builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); | ||||
|  | ||||
| // Add services to the container. | ||||
|  | ||||
| builder.Services.AddDbContext<AppDatabase>(); | ||||
| @@ -201,20 +205,28 @@ app.MapTus("/files/tus", (_) => Task.FromResult<DefaultTusConfiguration>(new() | ||||
|             var fileService = eventContext.HttpContext.RequestServices.GetRequiredService<FileService>(); | ||||
|  | ||||
|             var info = await fileService.AnalyzeFileAsync(account, file.Id, fileStream, fileName, contentType); | ||||
|  | ||||
|             var jsonOptions = httpContext.RequestServices.GetRequiredService<IOptions<JsonOptions>>().Value | ||||
|                 .JsonSerializerOptions; | ||||
|             var infoJson = JsonSerializer.Serialize(info, jsonOptions); | ||||
|             eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson); | ||||
|  | ||||
| #pragma warning disable CS4014 | ||||
|             Task.Run(async () => | ||||
|             { | ||||
|                 await fileService.UploadFileToRemoteAsync(info, fileStream, null); | ||||
|                 await tusDiskStore.DeleteFileAsync(file.Id, eventContext.CancellationToken); | ||||
|                 using var scope = eventContext.HttpContext.RequestServices | ||||
|                     .GetRequiredService<IServiceScopeFactory>() | ||||
|                     .CreateScope(); | ||||
|                 // Keep the service didn't be disposed | ||||
|                 var fs = scope.ServiceProvider.GetRequiredService<FileService>(); | ||||
|                 // Keep the file stream opened | ||||
|                 var fileData = await tusDiskStore.GetFileAsync(file.Id, CancellationToken.None); | ||||
|                 var newStream = await fileData.GetContentAsync(CancellationToken.None); | ||||
|                 await fs.UploadFileToRemoteAsync(info, newStream, null); | ||||
|                 await tusDiskStore.DeleteFileAsync(file.Id, CancellationToken.None); | ||||
|             }); | ||||
| #pragma warning restore CS4014 | ||||
|         }, | ||||
|         OnCreateCompleteAsync = eventContext => | ||||
|         { | ||||
|             // var baseUrl = builder.Configuration.GetValue<string>("Storage:BaseUrl")!; | ||||
|             // eventContext.SetUploadUrl(new Uri($"{baseUrl}/files/{eventContext.FileId}")); | ||||
|             return Task.CompletedTask; | ||||
|         } | ||||
|     } | ||||
| })); | ||||
|  | ||||
|   | ||||
							
								
								
									
										103
									
								
								DysonNetwork.Sphere/Storage/FileController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								DysonNetwork.Sphere/Storage/FileController.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Minio.DataModel.Args; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Storage; | ||||
|  | ||||
| [ApiController] | ||||
| [Route("/files")] | ||||
| public class FileController( | ||||
|     AppDatabase db, | ||||
|     FileService fs, | ||||
|     IConfiguration configuration, | ||||
|     IWebHostEnvironment env | ||||
| ) : ControllerBase | ||||
| { | ||||
|     [HttpGet("{id}")] | ||||
|     public async Task<ActionResult> OpenFile(string id) | ||||
|     { | ||||
|         var file = await db.Files.FindAsync(id); | ||||
|         if (file is null) return NotFound(); | ||||
|  | ||||
|         if (file.UploadedTo is null) | ||||
|         { | ||||
|             var tusStorePath = configuration.GetValue<string>("Tus:StorePath")!; | ||||
|             var filePath = Path.Combine(env.ContentRootPath, tusStorePath, file.Id); | ||||
|             if (!System.IO.File.Exists(filePath)) return new NotFoundResult(); | ||||
|             return PhysicalFile(filePath, file.MimeType ?? "application/octet-stream", file.Name); | ||||
|         } | ||||
|  | ||||
|         var dest = fs.GetRemoteStorageConfig(file.UploadedTo); | ||||
|  | ||||
|         if (dest.ImageProxy is not null && (file.MimeType?.StartsWith("image/") ?? false)) | ||||
|         { | ||||
|             var proxyUrl = dest.ImageProxy; | ||||
|             var baseUri = new Uri(proxyUrl.EndsWith('/') ? proxyUrl : $"{proxyUrl}/"); | ||||
|             var fullUri = new Uri(baseUri, file.Id); | ||||
|             return Redirect(fullUri.ToString()); | ||||
|         } | ||||
|  | ||||
|         if (dest.AccessProxy is not null) | ||||
|         { | ||||
|             var proxyUrl = dest.AccessProxy; | ||||
|             var baseUri = new Uri(proxyUrl.EndsWith('/') ? proxyUrl : $"{proxyUrl}/"); | ||||
|             var fullUri = new Uri(baseUri, file.Id); | ||||
|             return Redirect(fullUri.ToString()); | ||||
|         } | ||||
|  | ||||
|         if (dest.EnableSigned) | ||||
|         { | ||||
|             var client = fs.CreateMinioClient(dest); | ||||
|             if (client is null) | ||||
|                 return BadRequest( | ||||
|                     "Failed to configure client for remote destination, file got an invalid storage remote."); | ||||
|  | ||||
|             var bucket = dest.Bucket; | ||||
|             var openUrl = await client.PresignedGetObjectAsync( | ||||
|                 new PresignedGetObjectArgs() | ||||
|                     .WithBucket(bucket) | ||||
|                     .WithObject(file.Id) | ||||
|                     .WithExpiry(3600) | ||||
|             ); | ||||
|  | ||||
|             return Redirect(openUrl); | ||||
|         } | ||||
|  | ||||
|         // Fallback redirect to the S3 endpoint (public read) | ||||
|         var protocol = dest.EnableSsl ? "https" : "http"; | ||||
|         // Use the path bucket lookup mode | ||||
|         return Redirect($"{protocol}://{dest.Endpoint}/{dest.Bucket}/{file.Id}"); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("{id}/info")] | ||||
|     public async Task<ActionResult<CloudFile>> GetFileInfo(string id) | ||||
|     { | ||||
|         var file = await db.Files.FindAsync(id); | ||||
|         if (file is null) return NotFound(); | ||||
|  | ||||
|         return file; | ||||
|     } | ||||
|  | ||||
|     [Authorize] | ||||
|     [HttpDelete("{id}")] | ||||
|     public async Task<ActionResult> DeleteFile(string id) | ||||
|     { | ||||
|         var userIdClaim = User.FindFirst("user_id")?.Value; | ||||
|         if (userIdClaim is null) return Unauthorized(); | ||||
|         var userId = long.Parse(userIdClaim); | ||||
|  | ||||
|         var file = await db.Files | ||||
|             .Where(e => e.Id == id) | ||||
|             .Where(e => e.Account.Id == userId) | ||||
|             .FirstOrDefaultAsync(); | ||||
|         if (file is null) return NotFound(); | ||||
|  | ||||
|         await fs.DeleteFileDataAsync(file); | ||||
|  | ||||
|         db.Files.Remove(file); | ||||
|         await db.SaveChangesAsync(); | ||||
|  | ||||
|         return NoContent(); | ||||
|     } | ||||
| } | ||||
| @@ -117,11 +117,30 @@ public class FileService(AppDatabase db, IConfiguration configuration) | ||||
|         ); | ||||
|  | ||||
|         file.UploadedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); | ||||
|         db.Update(file); | ||||
|         await db.SaveChangesAsync(); | ||||
|         return file; | ||||
|     } | ||||
|  | ||||
|     private RemoteStorageConfig GetRemoteStorageConfig(string destination) | ||||
|     public async Task DeleteFileDataAsync(CloudFile file) | ||||
|     { | ||||
|         if (file.UploadedTo is null) return; | ||||
|         var dest = GetRemoteStorageConfig(file.UploadedTo); | ||||
|         var client = CreateMinioClient(dest); | ||||
|         if (client is null) | ||||
|             throw new InvalidOperationException( | ||||
|                 $"Failed to configure client for remote destination '{file.UploadedTo}'" | ||||
|             ); | ||||
|  | ||||
|         var bucket = dest.Bucket; | ||||
|         await client.RemoveObjectAsync( | ||||
|             new RemoveObjectArgs().WithBucket(bucket).WithObject(file.Id) | ||||
|         ); | ||||
|  | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     public RemoteStorageConfig GetRemoteStorageConfig(string destination) | ||||
|     { | ||||
|         var destinations = configuration.GetSection("Storage:Remote").Get<List<RemoteStorageConfig>>()!; | ||||
|         var dest = destinations.FirstOrDefault(d => d.Id == destination); | ||||
| @@ -129,7 +148,7 @@ public class FileService(AppDatabase db, IConfiguration configuration) | ||||
|         return dest; | ||||
|     } | ||||
|  | ||||
|     private IMinioClient? CreateMinioClient(RemoteStorageConfig dest) | ||||
|     public IMinioClient? CreateMinioClient(RemoteStorageConfig dest) | ||||
|     { | ||||
|         var client = new MinioClient() | ||||
|             .WithEndpoint(dest.Endpoint) | ||||
|   | ||||
| @@ -1,14 +1,17 @@ | ||||
| <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AChapterData_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fe6_003F64a6c0f7_003FChapterData_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AClaim_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa7fdc52b6e574ae7b9822133be91162a15800_003Ff7_003Feebffd8d_003FClaim_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACorsPolicyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F051ad509d0504b7ca10dedd9c2cabb9914200_003F8e_003Fb28257cb_003FCorsPolicyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbContext_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fa0b45f29f34f594814a7b1fbc25fe5ef3c18257956ed4f4fbfa68717db58_003FDbContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADirectory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fde_003F94973e27_003FDirectory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpointConventionBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F8a_003F101938e3_003FEndpointConventionBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkQueryableExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fe096e6f12c5d6b49356bc34ff1ea08738f910c0929c9d717c9cba7f44288_003FEntityFrameworkQueryableExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F4a28847852ee9ba45fd3107526c0a749a733bd4f4ebf33aa3c9a59737a3f758_003FEntityFrameworkServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F832399abc13b45b6bdbabfa022e4a28487e00_003F7f_003F7aece4dd_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEvents_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F20_003F86914b63_003FEvents_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F8c_003F9f6e3f4f_003FFileResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcfe5737f9bb84738979cbfedd11822a8ea00_003F50_003F9a335f87_003FForwardedHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| @@ -17,9 +20,12 @@ | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFound_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff2c049af93e430aac427e8ff3cc9edd8763d5c9f006d7121ed1c5921585cba_003FNotFound_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOk_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003F3b_003F237bf104_003FOk_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fd3_003F7b05b2bd_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APresignedGetObjectArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F0df26a9d89e29319e9efcaea0a8489db9e97bc1aedcca3f7e360cc50f8f4ea_003FPresignedGetObjectArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASecuritySchemeType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F29898ce74e3763a786ac1bd9a6db2152e1af75769440b1e53b9cbdf1dda1bd99_003FSecuritySchemeType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStatusCodeResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F7c_003F8b7572ae_003FStatusCodeResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATagging_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F36f4c2e6baa65ba603de42eedad12ea36845aa35a910a6a82d82baf688e3e1_003FTagging_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATusDiskStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fe1_003Fefd9af34_003FTusDiskStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUri_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5d2c480da9be415dab9be535bb6d08713cc00_003Fd0_003Fffc36a51_003FUri_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidationContext_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F6b_003F741ceebe_003FValidationContext_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> | ||||
		Reference in New Issue
	
	Block a user