|
DreamineVMS.Web 1.0.0.0
DreamineVMS 에이전트가 제공하는 HLS 카메라 영상을 브라우저에서 관리·재생하는 원격 CCTV 웹 서비스입니다.
|
함수 | |
| builder.Configuration. | AddUserSecrets ("codemaru-oauth-2ba4e1b2") |
| builder.Services. | AddRazorComponents () .AddInteractiveServerComponents() |
| builder.Services. | Configure< CircuitOptions > (o=> o.DetailedErrors=true) |
| builder.Services. | AddHttpClient () |
| builder.Services. | AddDreamineIdentityWeb (authOptions, usersDbPath) |
| builder.Services. | AddSingleton< VmsDatabase > () |
| builder.Services. | AddSingleton< VmsUserService > () |
| builder.Services. | AddSingleton< VmsSessionService > () |
| builder.Services. | AddScoped< VmsAuthState > () |
| builder.Services. | AddSingleton< IVmsCameraRepository, SqliteCameraRepository > () |
| builder.Services. | AddSingleton< AgentTokenService > () |
| builder.Services. | AddSingleton< HlsSegmentStore > () |
| app. | UseForwardedHeaders () |
| app. | UseStaticFiles (new StaticFileOptions { ContentTypeProvider=provider }) |
| app. | UseRouting () |
| app. | UseAuthentication () |
| app. | UseAuthorization () |
| app. | UseAntiforgery () |
| app. | Use (async(ctx, next)=> { var ua=ctx.Request.Headers.UserAgent.ToString();bool looksLikeBrowser=ua.Contains("AppleWebKit") &&(ua.Contains("Chrome")||ua.Contains("Safari")||ua.Contains("Mobile"));bool isCrawler=!looksLikeBrowser &&(ua.Contains("Kakaotalk", StringComparison.OrdinalIgnoreCase)||ua.Contains("kakaostory", StringComparison.OrdinalIgnoreCase)||ua.Contains("Naverbot", StringComparison.OrdinalIgnoreCase)||ua.Contains("DaumApps", StringComparison.OrdinalIgnoreCase))||(ua.Contains("facebookexternalhit", StringComparison.OrdinalIgnoreCase)||ua.Contains("Twitterbot", StringComparison.OrdinalIgnoreCase)||ua.Contains("LinkedInBot", StringComparison.OrdinalIgnoreCase)||ua.Contains("Slackbot", StringComparison.OrdinalIgnoreCase)||ua.Contains("Discordbot", StringComparison.OrdinalIgnoreCase));if(!isCrawler) { await next();return;} var path=ctx.Request.Path.Value ?? "";if(path=="/"||path=="") { var siteUrl=$"https://{ctx.Request.Host}";const string title="CodeMaru CCTV Viewer";const string desc="카메라 주인은 앱 하나 설치, 보는 사람은 링크만으로 실시간 시청";var image=$"{siteUrl}/images/cctvviewer-og.png";ctx.Response.ContentType="text/html; charset=utf-8";await ctx.Response.WriteAsync($""" <!DOCTYPE html> <html lang="ko"> <head> <meta charset="utf-8"/> <title>{title}</title> <meta property="og:type" content="website"/> <meta property="og:url" content="{siteUrl}/"/> <meta property="og:title" content="{title}"/> <meta property="og:description" content="{desc}"/> <meta property="og:image" content="{image}"/> <meta property="og:site_name" content="CodeMaru CCTV Viewer"/> <meta name="description" content="{desc}"/> </head> <body><p><a href="{siteUrl}/">{title}</a></p></body> </html> """);return;} var parts=path.Trim('/').Split('/');if(parts.Length==2 &&parts[1]=="live") { var slug=parts[0];var userSvc=ctx.RequestServices.GetRequiredService< VmsUserService >();var user=await userSvc.FindBySlugAsync(slug);if(user is not null) { var siteUrl=$"https://{ctx.Request.Host}";var pageUrl=$"{siteUrl}/{slug}/live";var title=string.IsNullOrWhiteSpace(user.OgTitle) ? $"{user.DisplayName} CCTV Live" :user.OgTitle;var desc=string.IsNullOrWhiteSpace(user.OgDescription) ? $"{user.DisplayName}의 실시간 카메라 스트림입니다." :user.OgDescription;var image=string.IsNullOrWhiteSpace(user.OgImage) ? $"{siteUrl}/img/og-default.png" :user.OgImage;ctx.Response.ContentType="text/html; charset=utf-8";await ctx.Response.WriteAsync($""" <!DOCTYPE html> <html lang="ko"> <head> <meta charset="utf-8"/> <title>{title}</title> <meta property="og:type" content="website"/> <meta property="og:url" content="{pageUrl}"/> <meta property="og:title" content="{title}"/> <meta property="og:description" content="{desc}"/> <meta property="og:image" content="{image}"/> <meta property="og:site_name" content="CodeMaru CCTV Viewer"/> <meta name="description" content="{desc}"/> </head> <body><p><a href="{pageUrl}">{title}</a></p></body> </html> """);return;} } await next();}) |
| app. | MapPost ("/api/agent/login", async(AgentLoginRequest req, HttpContext ctx, VmsUserService users, AgentTokenService tokens, IVmsCameraRepository repo)=> { var(ok, _, user)=await users.LoginAsync(req.Email, req.Password);if(!ok||user is null) return Results.Unauthorized();var token=tokens.Issue(user);var cameras=repo.GetAll() .Where(c=> c.TenantId==user.Id &&c.Enabled &&!c.IsDirectHls) .Select(c=> new AgentCameraDto(c.Id, c.Name, c.Host, c.RtspUrl, c.AutoReconnect, c.IsPublic)) .ToList();var hlsStore=ctx.RequestServices.GetRequiredService< HlsSegmentStore >();foreach(var cam in cameras) hlsStore.ClearCameraHls(user.Id, cam.Id);return Results.Ok(new AgentLoginResponse(token, user.Id, cameras));}) |
| app. | MapPost ("/api/agent/sync-cameras", async(AgentSyncRequest req, AgentTokenService tokens, IVmsCameraRepository repo, HlsSegmentStore store)=> { var user=tokens.Validate(req.Token);if(user is null) return Results.Unauthorized();var existing=repo.GetAll().Where(c=> c.TenantId==user.Id).ToDictionary(c=> c.Id);int order=existing.Count > 0 ? existing.Values.Max(c=> c.DisplayOrder)+1 :1;foreach(var cam in req.Cameras) { var device=new DreamineVMS.Web.Models.CameraDevice { Id=cam.Id, TenantId=user.Id, Name=cam.Name, Host=cam.Host, RtspUrl=cam.RtspUrl, DisplayOrder=existing.ContainsKey(cam.Id) ? existing[cam.Id].DisplayOrder :order++, Enabled=true, AutoReconnect=cam.AutoReconnect, IsPublic=cam.IsPublic };if(existing.ContainsKey(cam.Id)) await repo.UpdateAsync(device);else await repo.AddAsync(device);} return Results.Ok();}) |
| app. | MapPost ("/api/agent/hls/{token}/{cameraId}/{filename}", async(string token, string cameraId, string filename, HttpContext ctx, AgentTokenService tokens, HlsSegmentStore store, ILoggerFactory loggerFactory)=> { var log=loggerFactory.CreateLogger("AgentPush");var user=tokens.Validate(token);if(user is null) { log.LogWarning("[Push] 토큰 인증 실패: {cam}/{file}", cameraId, filename);return Results.Unauthorized();} await store.SaveSegmentAsync(user.Id, cameraId, filename, ctx.Request.Body);log.LogDebug("[Push] OK {cam}/{file} ({bytes}B)", cameraId, filename, ctx.Request.ContentLength ?? -1);return Results.Ok();}) |
| app. | MapGet ("/hls/{tenantId}/{cameraId}/{filename}",(string tenantId, string cameraId, string filename, HlsSegmentStore store, HttpContext ctx, ILoggerFactory loggerFactory)=> { var log=loggerFactory.CreateLogger("HlsServe");var(stream, ct)=store.GetFile(tenantId, cameraId, filename);if(stream is null) { log.LogWarning("[Serve] 404 {cam}/{file}", cameraId, filename);return Results.NotFound();} if(filename.EndsWith(".m3u8", StringComparison.OrdinalIgnoreCase)) { ctx.Response.Headers.CacheControl="no-store, no-cache, must-revalidate, max-age=0";ctx.Response.Headers.Pragma="no-cache";ctx.Response.Headers.Expires="0";} else { ctx.Response.Headers.CacheControl="public, max-age=60, immutable";} return Results.Stream(stream, ct);}) |
| app. | MapPost ("/api/upload/og-image", async(HttpContext ctx, AgentTokenService tokens, IWebHostEnvironment env)=> { var sessionToken=ctx.Request.Headers["X-Session-Token"].ToString();if(string.IsNullOrWhiteSpace(sessionToken)) return Results.Unauthorized();var sessSvc=ctx.RequestServices.GetRequiredService< VmsSessionService >();var user=sessSvc.ValidateToken(sessionToken);if(user is null) return Results.Unauthorized();if(!ctx.Request.HasFormContentType) return Results.BadRequest("multipart form required");var form=await ctx.Request.ReadFormAsync();var file=form.Files.GetFile("file");if(file is null||file.Length==0) return Results.BadRequest("no file");var ext=Path.GetExtension(file.FileName).ToLowerInvariant();if(ext is not(".jpg" or ".jpeg" or ".png" or ".webp" or ".gif")) return Results.BadRequest("허용되지 않는 파일 형식입니다.");if(file.Length > 5 *1024 *1024) return Results.BadRequest("파일이 너무 큽니다 (최대 5MB).");var saveDir=Path.Combine(env.WebRootPath, "images", "og");Directory.CreateDirectory(saveDir);var fileName=$"{user.PublicSlug}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}{ext}";var savePath=Path.Combine(saveDir, fileName);await using var fs=File.Create(savePath);await file.CopyToAsync(fs);var url=$"https://{ctx.Request.Host}/images/og/{fileName}";return Results.Ok(new { url });}) |
| app. | MapRazorComponents< AppShell > () .AddInteractiveServerRenderMode() |
| app. | Run () |
| static string | ResolvePath (string? configuredPath, string fallback) |
| record | AgentLoginRequest (string Email, string Password) |
| record | AgentCameraDto (string Id, string Name, string Host, string RtspUrl, bool AutoReconnect, bool IsPublic) |
| record | AgentLoginResponse (string Token, string TenantId, List< AgentCameraDto > Cameras) |
| record | AgentSyncRequest (string Token, List< AgentCameraDto > Cameras) |
변수 | |
| var | builder = WebApplication.CreateBuilder(args) |
| AuthOptions | authOptions |
| string | usersDbPath |
| var | app = builder.Build() |
| var | provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider() |
| provider. | Mappings [".exe"] = "application/octet-stream" |
| builder.Services. AddDreamineIdentityWeb | ( | authOptions | , |
| usersDbPath | ) |
다음을 참조함 : authOptions, builder, usersDbPath.
| builder.Services. AddHttpClient | ( | ) |
다음을 참조함 : builder.
| builder.Services. AddRazorComponents | ( | ) |
| builder.Services. AddScoped< VmsAuthState > | ( | ) |
다음을 참조함 : builder.
| builder.Services. AddSingleton< AgentTokenService > | ( | ) |
다음을 참조함 : builder.
| builder.Services. AddSingleton< HlsSegmentStore > | ( | ) |
다음을 참조함 : builder.
| builder.Services. AddSingleton< IVmsCameraRepository, SqliteCameraRepository > | ( | ) |
다음을 참조함 : builder.
| builder.Services. AddSingleton< VmsDatabase > | ( | ) |
다음을 참조함 : builder.
| builder.Services. AddSingleton< VmsSessionService > | ( | ) |
다음을 참조함 : builder.
| builder.Services. AddSingleton< VmsUserService > | ( | ) |
다음을 참조함 : builder.
| builder.Configuration. AddUserSecrets | ( | "codemaru-oauth-2ba4e1b2" | ) |
다음을 참조함 : builder.
| record AgentCameraDto | ( | string | Id, |
| string | Name, | ||
| string | Host, | ||
| string | RtspUrl, | ||
| bool | AutoReconnect, | ||
| bool | IsPublic ) |
| record AgentLoginRequest | ( | string | Email, |
| string | Password ) |
| record AgentLoginResponse | ( | string | Token, |
| string | TenantId, | ||
| List< AgentCameraDto > | Cameras ) |
| record AgentSyncRequest | ( | string | Token, |
| List< AgentCameraDto > | Cameras ) |
| builder.Services. Configure< CircuitOptions > | ( | o | , |
| o. | DetailedErrors = true ) |
다음을 참조함 : builder.
| app. MapGet | ( | "/hls/{tenantId}/{cameraId}/{filename}" | , |
| (string tenantId, string cameraId, string filename, HlsSegmentStore store, HttpContext ctx, ILoggerFactory loggerFactory) | , | ||
| { var log=loggerFactory.CreateLogger("HlsServe");var(stream, ct)=store.GetFile(tenantId, cameraId, filename);if(stream is null) { log.LogWarning("[Serve] 404 {cam}/{file}", cameraId, filename);return Results.NotFound();} if(filename.EndsWith(".m3u8", StringComparison.OrdinalIgnoreCase)) { ctx.Response.Headers.CacheControl="no-store, no-cache, must-revalidate, max-age=0";ctx.Response.Headers.Pragma="no-cache";ctx.Response.Headers.Expires="0";} else { ctx.Response.Headers.CacheControl="public, max-age=60, immutable";} return Results.Stream(stream, ct);} | ) |
다음을 참조함 : app, DreamineVMS.Web.Services.Hls.HlsSegmentStore.GetFile().

| app. MapPost | ( | "/api/agent/hls/{token}/{cameraId}/{filename}" | , |
| async(string token, string cameraId, string filename, HttpContext ctx, AgentTokenService tokens, HlsSegmentStore store, ILoggerFactory loggerFactory) | , | ||
| { var log=loggerFactory.CreateLogger("AgentPush");var user=tokens.Validate(token);if(user is null) { log.LogWarning("[Push] 토큰 인증 실패: {cam}/{file}", cameraId, filename);return Results.Unauthorized();} await store.SaveSegmentAsync(user.Id, cameraId, filename, ctx.Request.Body);log.LogDebug("[Push] OK {cam}/{file} ({bytes}B)", cameraId, filename, ctx.Request.ContentLength ?? -1);return Results.Ok();} | ) |
다음을 참조함 : app, DreamineVMS.Web.Services.Hls.HlsSegmentStore.SaveSegmentAsync(), DreamineVMS.Web.Services.Agent.AgentTokenService.Validate().

| app. MapPost | ( | "/api/agent/login" | , |
| async(AgentLoginRequest req, HttpContext ctx, VmsUserService users, AgentTokenService tokens, IVmsCameraRepository repo) | , | ||
| { var(ok, _, user)=await users.LoginAsync(req.Email, req.Password);if(!ok||user is null) return Results.Unauthorized();var token=tokens.Issue(user);var cameras=repo.GetAll() .Where(c=> c.TenantId==user.Id &&c.Enabled &&!c.IsDirectHls) .Select(c=> new AgentCameraDto(c.Id, c.Name, c.Host, c.RtspUrl, c.AutoReconnect, c.IsPublic)) .ToList();var hlsStore=ctx.RequestServices.GetRequiredService< HlsSegmentStore >();foreach(var cam in cameras) hlsStore.ClearCameraHls(user.Id, cam.Id);return Results.Ok(new AgentLoginResponse(token, user.Id, cameras));} | ) |
다음을 참조함 : AgentCameraDto(), AgentLoginRequest(), AgentLoginResponse(), app, DreamineVMS.Web.Services.Hls.HlsSegmentStore.ClearCameraHls(), DreamineVMS.Web.Services.Cameras.IVmsCameraRepository.GetAll(), DreamineVMS.Web.Services.Agent.AgentTokenService.Issue(), DreamineVMS.Web.Services.Auth.VmsUserService.LoginAsync().

| app. MapPost | ( | "/api/agent/sync-cameras" | , |
| async(AgentSyncRequest req, AgentTokenService tokens, IVmsCameraRepository repo, HlsSegmentStore store) | , | ||
| { var user=tokens.Validate(req.Token);if(user is null) return Results.Unauthorized();var existing=repo.GetAll().Where(c=> c.TenantId==user.Id).ToDictionary(c=> c.Id);int order=existing.Count > 0 ? existing.Values.Max(c=> c.DisplayOrder)+1 :1;foreach(var cam in req.Cameras) { var device=new DreamineVMS.Web.Models.CameraDevice { Id=cam.Id, TenantId=user.Id, Name=cam.Name, Host=cam.Host, RtspUrl=cam.RtspUrl, DisplayOrder=existing.ContainsKey(cam.Id) ? existing[cam.Id].DisplayOrder :order++, Enabled=true, AutoReconnect=cam.AutoReconnect, IsPublic=cam.IsPublic };if(existing.ContainsKey(cam.Id)) await repo.UpdateAsync(device);else await repo.AddAsync(device);} return Results.Ok();} | ) |
다음을 참조함 : DreamineVMS.Web.Services.Cameras.IVmsCameraRepository.AddAsync(), AgentSyncRequest(), app, DreamineVMS.Web.Services.Cameras.IVmsCameraRepository.GetAll(), DreamineVMS.Web.Services.Cameras.IVmsCameraRepository.UpdateAsync(), DreamineVMS.Web.Services.Agent.AgentTokenService.Validate().

| app. MapPost | ( | "/api/upload/og-image" | , |
| async(HttpContext ctx, AgentTokenService tokens, IWebHostEnvironment env) | , | ||
| { var sessionToken=ctx.Request.Headers["X-Session-Token"].ToString();if(string.IsNullOrWhiteSpace(sessionToken)) return Results.Unauthorized();var sessSvc=ctx.RequestServices.GetRequiredService< VmsSessionService >();var user=sessSvc.ValidateToken(sessionToken);if(user is null) return Results.Unauthorized();if(!ctx.Request.HasFormContentType) return Results.BadRequest("multipart form required");var form=await ctx.Request.ReadFormAsync();var file=form.Files.GetFile("file");if(file is null||file.Length==0) return Results.BadRequest("no file");var ext=Path.GetExtension(file.FileName).ToLowerInvariant();if(ext is not(".jpg" or ".jpeg" or ".png" or ".webp" or ".gif")) return Results.BadRequest("허용되지 않는 파일 형식입니다.");if(file.Length > 5 *1024 *1024) return Results.BadRequest("파일이 너무 큽니다 (최대 5MB).");var saveDir=Path.Combine(env.WebRootPath, "images", "og");Directory.CreateDirectory(saveDir);var fileName=$"{user.PublicSlug}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}{ext}";var savePath=Path.Combine(saveDir, fileName);await using var fs=File.Create(savePath);await file.CopyToAsync(fs);var url=$"https://{ctx.Request.Host}/images/og/{fileName}";return Results.Ok(new { url });} | ) |
다음을 참조함 : app, DreamineVMS.Web.Services.Auth.VmsSessionService.ValidateToken().

| app. MapRazorComponents< AppShell > | ( | ) |
다음을 참조함 : app.
|
static |
Program.cs 파일의 331 번째 라인에서 정의되었습니다.
| app. Run | ( | ) |
다음을 참조함 : app.
| app. Use | ( | async(ctx, next) | , |
| { var ua=ctx.Request.Headers.UserAgent.ToString();bool looksLikeBrowser=ua.Contains("AppleWebKit") &&(ua.Contains("Chrome")||ua.Contains("Safari")||ua.Contains("Mobile"));bool isCrawler=!looksLikeBrowser &&(ua.Contains("Kakaotalk", StringComparison.OrdinalIgnoreCase)||ua.Contains("kakaostory", StringComparison.OrdinalIgnoreCase)||ua.Contains("Naverbot", StringComparison.OrdinalIgnoreCase)||ua.Contains("DaumApps", StringComparison.OrdinalIgnoreCase))||(ua.Contains("facebookexternalhit", StringComparison.OrdinalIgnoreCase)||ua.Contains("Twitterbot", StringComparison.OrdinalIgnoreCase)||ua.Contains("LinkedInBot", StringComparison.OrdinalIgnoreCase)||ua.Contains("Slackbot", StringComparison.OrdinalIgnoreCase)||ua.Contains("Discordbot", StringComparison.OrdinalIgnoreCase));if(!isCrawler) { await next();return;} var path=ctx.Request.Path.Value ?? "";if(path=="/"||path=="") { var siteUrl=$"https://{ctx.Request.Host}";const string title="CodeMaru CCTV Viewer";const string desc="카메라 주인은 앱 하나 설치, 보는 사람은 링크만으로 실시간 시청";var image=$"{siteUrl}/images/cctvviewer-og.png";ctx.Response.ContentType="text/html; charset=utf-8";await ctx.Response.WriteAsync($""" <!DOCTYPE html> <html lang="ko"> <head> <meta charset="utf-8"/> <title>{title}</title> <meta property="og:type" content="website"/> <meta property="og:url" content="{siteUrl}/"/> <meta property="og:title" content="{title}"/> <meta property="og:description" content="{desc}"/> <meta property="og:image" content="{image}"/> <meta property="og:site_name" content="CodeMaru CCTV Viewer"/> <meta name="description" content="{desc}"/> </head> <body><p><a href="{siteUrl}/">{title}</a></p></body> </html> """);return;} var parts=path.Trim('/').Split('/');if(parts.Length==2 &&parts[1]=="live") { var slug=parts[0];var userSvc=ctx.RequestServices.GetRequiredService< VmsUserService >();var user=await userSvc.FindBySlugAsync(slug);if(user is not null) { var siteUrl=$"https://{ctx.Request.Host}";var pageUrl=$"{siteUrl}/{slug}/live";var title=string.IsNullOrWhiteSpace(user.OgTitle) ? $"{user.DisplayName} CCTV Live" :user.OgTitle;var desc=string.IsNullOrWhiteSpace(user.OgDescription) ? $"{user.DisplayName}의 실시간 카메라 스트림입니다." :user.OgDescription;var image=string.IsNullOrWhiteSpace(user.OgImage) ? $"{siteUrl}/img/og-default.png" :user.OgImage;ctx.Response.ContentType="text/html; charset=utf-8";await ctx.Response.WriteAsync($""" <!DOCTYPE html> <html lang="ko"> <head> <meta charset="utf-8"/> <title>{title}</title> <meta property="og:type" content="website"/> <meta property="og:url" content="{pageUrl}"/> <meta property="og:title" content="{title}"/> <meta property="og:description" content="{desc}"/> <meta property="og:image" content="{image}"/> <meta property="og:site_name" content="CodeMaru CCTV Viewer"/> <meta name="description" content="{desc}"/> </head> <body><p><a href="{pageUrl}">{title}</a></p></body> </html> """);return;} } await next();} | ) |
다음을 참조함 : app, DreamineVMS.Web.Services.Auth.VmsUserService.FindBySlugAsync().

| app. UseAntiforgery | ( | ) |
다음을 참조함 : app.
| app. UseAuthentication | ( | ) |
다음을 참조함 : app.
| app. UseAuthorization | ( | ) |
다음을 참조함 : app.
| app. UseForwardedHeaders | ( | ) |
다음을 참조함 : app.
| app. UseRouting | ( | ) |
다음을 참조함 : app.
| app. UseStaticFiles | ( | new StaticFileOptions { ContentTypeProvider=provider } | ) |
| var app = builder.Build() |
Program.cs 파일의 36 번째 라인에서 정의되었습니다.
다음에 의해서 참조됨 : MapGet(), MapPost(), MapPost(), MapPost(), MapPost(), MapRazorComponents< AppShell >(), Run(), Use(), UseAntiforgery(), UseAuthentication(), UseAuthorization(), UseForwardedHeaders(), UseRouting(), UseStaticFiles().
| AuthOptions authOptions |
Program.cs 파일의 14 번째 라인에서 정의되었습니다.
다음에 의해서 참조됨 : AddDreamineIdentityWeb().
| var builder = WebApplication.CreateBuilder(args) |
Program.cs 파일의 11 번째 라인에서 정의되었습니다.
다음에 의해서 참조됨 : AddDreamineIdentityWeb(), AddHttpClient(), AddScoped< VmsAuthState >(), AddSingleton< AgentTokenService >(), AddSingleton< HlsSegmentStore >(), AddSingleton< IVmsCameraRepository, SqliteCameraRepository >(), AddSingleton< VmsDatabase >(), AddSingleton< VmsSessionService >(), AddSingleton< VmsUserService >(), AddUserSecrets(), Configure< CircuitOptions >().
| provider. Mappings[".exe"] = "application/octet-stream" |
Program.cs 파일의 39 번째 라인에서 정의되었습니다.
| var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider() |
Program.cs 파일의 38 번째 라인에서 정의되었습니다.
다음에 의해서 참조됨 : DreamineVMS.Web.Services.Auth.VmsUserService.BuildPseudoEmail(), DreamineVMS.Web.Services.Auth.VmsUserService.EnsureExternalUserAsync(), UseStaticFiles().
| string usersDbPath |
Program.cs 파일의 16 번째 라인에서 정의되었습니다.
다음에 의해서 참조됨 : AddDreamineIdentityWeb().