├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── CarvedRock.Api ├── CarvedRock.Api.csproj ├── CarvedRockEvents.cs ├── Controllers │ └── ProductController.cs ├── CriticalExceptionMiddleware.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── SwaggerHelpers.cs ├── UserScopeMiddleware.cs ├── appsettings.json └── nlog.config ├── CarvedRock.Data ├── CarvedRock.Data.csproj ├── CarvedRockRepository.cs ├── Entities │ └── Product.cs ├── ICarvedRockRepository.cs ├── LocalContext.cs └── Migrations │ ├── 20211218131519_Initial.Designer.cs │ ├── 20211218131519_Initial.cs │ ├── 20211231150113_ImgUrl.Designer.cs │ ├── 20211231150113_ImgUrl.cs │ └── LocalContextModelSnapshot.cs ├── CarvedRock.Domain ├── CarvedRock.Domain.csproj ├── IProductLogic.cs └── ProductLogic.cs ├── CarvedRock.WebApp ├── CarvedRock.WebApp.csproj ├── CarvedRock.WebApp.csproj.user ├── Models │ └── Product.cs ├── Pages │ ├── CurrentPromotion.cshtml │ ├── CurrentPromotion.cshtml.cs │ ├── Error.cshtml │ ├── Error.cshtml.cs │ ├── Index.cshtml │ ├── Index.cshtml.cs │ ├── Listing.cshtml │ ├── Listing.cshtml.cs │ ├── Shared │ │ ├── _Layout.cshtml │ │ └── _Layout.cshtml.css │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── UserScopeMiddleware.cs ├── appsettings.json ├── nlog.config └── wwwroot │ ├── css │ ├── custom.css │ └── main.css │ ├── favicon.ico │ ├── images │ ├── shutterstock_222721876.jpg │ └── shutterstock_475046062.jpg │ └── js │ └── site.js ├── CarvedRock.sln └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/.idea/** 2 | **/.vs/** 3 | **/bin 4 | **/obj 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "API - .NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build-api", 12 | "program": "${workspaceFolder}/CarvedRock.Api/bin/Debug/net6.0/CarvedRock.Api.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}/CarvedRock.Api", 15 | "stopAtEntry": false, 16 | "serverReadyAction": { 17 | "action": "openExternally", 18 | "pattern": "Now listening on:\\s+(https?://\\S+)\"?" 19 | }, 20 | "env": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | //"Logging__LogLevel__CarvedRock.Api.Controllers": "Debug" 23 | }, 24 | "sourceFileMap": { 25 | "/Views": "${workspaceFolder}/Views" 26 | } 27 | }, 28 | { 29 | "name": "UI - .NET Core Launch (web)", 30 | "type": "coreclr", 31 | "request": "launch", 32 | "preLaunchTask": "build-ui", 33 | "program": "${workspaceFolder}/CarvedRock.WebApp/bin/Debug/net6.0/CarvedRock.WebApp.dll", 34 | "args": [], 35 | "cwd": "${workspaceFolder}/CarvedRock.WebApp", 36 | "stopAtEntry": false, 37 | "serverReadyAction": { 38 | "action": "openExternally", 39 | "pattern": "Now listening on:\\s+(https?://\\S+)\"?" 40 | }, 41 | "env": { 42 | "ASPNETCORE_ENVIRONMENT": "Development" 43 | //"Logging__LogLevel__CarvedRock.Api.Controllers": "Debug" 44 | }, 45 | "sourceFileMap": { 46 | "/Views": "${workspaceFolder}/Views" 47 | } 48 | }, 49 | { 50 | "name": ".NET Core Attach", 51 | "type": "coreclr", 52 | "request": "attach" 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build-api", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/CarvedRock.Api/CarvedRock.Api.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish-api", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/CarvedRock.Api/CarvedRock.Api.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch-api", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/CarvedRock.Api/CarvedRock.Api.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | }, 41 | { 42 | "label": "build-ui", 43 | "command": "dotnet", 44 | "type": "process", 45 | "args": [ 46 | "build", 47 | "${workspaceFolder}/CarvedRock.WebApp/CarvedRock.WebApp.csproj", 48 | "/property:GenerateFullPaths=true", 49 | "/consoleloggerparameters:NoSummary" 50 | ], 51 | "problemMatcher": "$msCompile" 52 | }, 53 | { 54 | "label": "publish-ui", 55 | "command": "dotnet", 56 | "type": "process", 57 | "args": [ 58 | "publish", 59 | "${workspaceFolder}/CarvedRock.WebApp/CarvedRock.WebApp.csproj", 60 | "/property:GenerateFullPaths=true", 61 | "/consoleloggerparameters:NoSummary" 62 | ], 63 | "problemMatcher": "$msCompile" 64 | }, 65 | { 66 | "label": "watch-ui", 67 | "command": "dotnet", 68 | "type": "process", 69 | "args": [ 70 | "watch", 71 | "run", 72 | "${workspaceFolder}/CarvedRock.WebApp/CarvedRock.WebApp.csproj", 73 | "/property:GenerateFullPaths=true", 74 | "/consoleloggerparameters:NoSummary" 75 | ], 76 | "problemMatcher": "$msCompile" 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /CarvedRock.Api/CarvedRock.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /CarvedRock.Api/CarvedRockEvents.cs: -------------------------------------------------------------------------------- 1 | namespace CarvedRock.Api; 2 | 3 | public class CarvedRockEvents 4 | { 5 | public const int GettingProducts = 1901; 6 | } -------------------------------------------------------------------------------- /CarvedRock.Api/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using CarvedRock.Data.Entities; 2 | using CarvedRock.Domain; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace CarvedRock.Api.Controllers; 6 | 7 | [ApiController] 8 | [Route("[controller]")] 9 | public partial class ProductController : ControllerBase 10 | { 11 | private readonly ILogger _logger; 12 | private readonly IProductLogic _productLogic; 13 | 14 | [LoggerMessage(CarvedRockEvents.GettingProducts, LogLevel.Information, 15 | "SourceGenerated - Getting products in API.")] 16 | partial void LogGettingProducts(); 17 | 18 | public ProductController(ILogger logger, IProductLogic productLogic) 19 | { 20 | _logger = logger; 21 | _productLogic = productLogic; 22 | } 23 | 24 | [HttpGet] 25 | public async Task> Get(string category = "all") 26 | { 27 | using (_logger.BeginScope("ScopeCat: {ScopeCat}", category)) 28 | { 29 | LogGettingProducts(); 30 | //_logger.LogInformation(CarvedRockEvents.GettingProducts, "Getting products in API."); 31 | return await _productLogic.GetProductsForCategoryAsync(category); 32 | } 33 | 34 | //return _productLogic.GetProductsForCategory(category); 35 | } 36 | 37 | [HttpGet("{id:int}")] 38 | [ProducesResponseType(typeof(Product), StatusCodes.Status200OK)] 39 | [ProducesResponseType(StatusCodes.Status404NotFound)] 40 | public async Task Get(int id) 41 | { 42 | //var product = await _productLogic.GetProductByIdAsync(id); 43 | _logger.LogDebug("Getting single product in API for {id}", id); 44 | var product = _productLogic.GetProductById(id); 45 | if (product != null) 46 | { 47 | return Ok(product); 48 | } 49 | _logger.LogWarning("No product found for ID: {id}", id); 50 | return NotFound(); 51 | } 52 | } -------------------------------------------------------------------------------- /CarvedRock.Api/CriticalExceptionMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.Sqlite; 2 | 3 | namespace CarvedRock.Api; 4 | public class CriticalExceptionMiddleware 5 | { 6 | private readonly RequestDelegate _next; 7 | private readonly ILogger _logger; 8 | 9 | public CriticalExceptionMiddleware(RequestDelegate next, ILogger logger) 10 | { 11 | _next = next; 12 | _logger = logger; 13 | } 14 | 15 | public async Task InvokeAsync(HttpContext context) 16 | { 17 | try 18 | { 19 | await _next(context); 20 | } 21 | catch (SqliteException sqlex) 22 | { 23 | if (sqlex.SqliteErrorCode == 551) 24 | { 25 | _logger.LogCritical(sqlex, "Fatal error occurred in database!!"); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /CarvedRock.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using CarvedRock.Data; 3 | using CarvedRock.Domain; 4 | using Hellang.Middleware.ProblemDetails; 5 | using Microsoft.Data.Sqlite; 6 | using CarvedRock.Api; 7 | using Microsoft.Extensions.Options; 8 | using Microsoft.IdentityModel.Tokens; 9 | using Swashbuckle.AspNetCore.SwaggerGen; 10 | // using NLog; 11 | // using NLog.Web; 12 | using Serilog; 13 | using Serilog.Enrichers.Span; 14 | using Serilog.Exceptions; 15 | using OpenTelemetry.Trace; 16 | using OpenTelemetry.Resources; 17 | 18 | var builder = WebApplication.CreateBuilder(args); 19 | builder.Services.AddHealthChecks() 20 | .AddDbContextCheck(); 21 | 22 | builder.Logging.ClearProviders(); 23 | // builder.Logging.AddDebug(); 24 | //builder.Logging.AddSimpleConsole(); 25 | //builder.Services.AddApplicationInsightsTelemetry(); 26 | 27 | builder.Host.UseSerilog((context, loggerConfig) => { 28 | loggerConfig 29 | .ReadFrom.Configuration(context.Configuration) 30 | .WriteTo.Console() 31 | .Enrich.WithExceptionDetails() 32 | .Enrich.FromLogContext() 33 | .Enrich.With() 34 | .WriteTo.Seq("http://localhost:5341"); 35 | }); 36 | 37 | builder.Services.AddOpenTelemetryTracing(b => { 38 | b.SetResourceBuilder( 39 | ResourceBuilder.CreateDefault().AddService(builder.Environment.ApplicationName)) 40 | .AddAspNetCoreInstrumentation() 41 | .AddEntityFrameworkCoreInstrumentation() 42 | .AddOtlpExporter(opts => { opts.Endpoint = new Uri("http://localhost:4317"); }); 43 | }); 44 | 45 | //NLog.LogManager.Setup().LoadConfigurationFromFile(); 46 | //builder.Host.UseNLog(); 47 | 48 | builder.Services.AddProblemDetails(opts => 49 | { 50 | opts.IncludeExceptionDetails = (ctx, ex) => false; 51 | 52 | opts.OnBeforeWriteDetails = (ctx, dtls) => { 53 | if (dtls.Status == 500) 54 | { 55 | dtls.Detail = "An error occurred in our API. Use the trace id when contacting us."; 56 | } 57 | }; 58 | opts.Rethrow(); 59 | opts.MapToStatusCode(StatusCodes.Status500InternalServerError); 60 | }); 61 | 62 | JwtSecurityTokenHandler.DefaultMapInboundClaims = false; 63 | builder.Services.AddAuthentication("Bearer") 64 | .AddJwtBearer("Bearer", options => 65 | { 66 | options.Authority = "https://demo.duendesoftware.com"; 67 | options.Audience = "api"; 68 | options.TokenValidationParameters = new TokenValidationParameters 69 | { 70 | NameClaimType = "email" 71 | }; 72 | }); 73 | 74 | builder.Services.AddControllers(); 75 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 76 | builder.Services.AddEndpointsApiExplorer(); 77 | builder.Services.AddTransient, SwaggerOptions>(); 78 | builder.Services.AddSwaggerGen(); 79 | 80 | builder.Services.AddScoped(); 81 | builder.Services.AddDbContext(); 82 | builder.Services.AddScoped(); 83 | 84 | 85 | 86 | var app = builder.Build(); 87 | 88 | using (var scope = app.Services.CreateScope()) 89 | { 90 | var services = scope.ServiceProvider; 91 | var context = services.GetRequiredService(); 92 | context.MigrateAndCreateData(); 93 | } 94 | 95 | app.UseMiddleware(); 96 | app.UseProblemDetails(); 97 | 98 | if (app.Environment.IsDevelopment()) 99 | { 100 | app.UseSwagger(); 101 | app.UseSwaggerUI(options => 102 | { 103 | options.OAuthClientId("interactive.public.short"); 104 | options.OAuthAppName("CarvedRock API"); 105 | options.OAuthUsePkce(); 106 | }); 107 | } 108 | app.MapFallback(() => Results.Redirect("/swagger")); 109 | //app.UseHttpsRedirection(); 110 | app.UseAuthentication(); 111 | app.UseMiddleware(); 112 | app.UseAuthorization(); 113 | app.MapControllers().RequireAuthorization(); 114 | 115 | app.MapHealthChecks("health").AllowAnonymous(); 116 | 117 | app.Run(); 118 | -------------------------------------------------------------------------------- /CarvedRock.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:28649", 8 | "sslPort": 44358 9 | } 10 | }, 11 | "profiles": { 12 | "CarvedRock.Api": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "applicationUrl": "https://localhost:7213;http://localhost:5022", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CarvedRock.Api/SwaggerHelpers.cs: -------------------------------------------------------------------------------- 1 | using IdentityModel.Client; 2 | using Microsoft.Extensions.Options; 3 | using Microsoft.OpenApi.Models; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | 6 | namespace CarvedRock.Api; 7 | 8 | public class SwaggerOptions : IConfigureOptions 9 | { 10 | private readonly IConfiguration _config; 11 | private readonly ILogger _logger; 12 | 13 | public SwaggerOptions(IConfiguration config, ILogger logger) 14 | { 15 | _config = config; 16 | _logger = logger; 17 | } 18 | 19 | public void Configure(SwaggerGenOptions options) 20 | { 21 | try 22 | { 23 | var disco = GetDiscoveryDocument(); 24 | var oauthScopes = new Dictionary 25 | { 26 | { "api", "Resource access: api" }, 27 | { "openid", "OpenID information"}, 28 | { "profile", "User profile information" }, 29 | { "email", "User email address" } 30 | }; 31 | options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme 32 | { 33 | Type = SecuritySchemeType.OAuth2, 34 | Flows = new OpenApiOAuthFlows 35 | { 36 | AuthorizationCode = new OpenApiOAuthFlow 37 | { 38 | AuthorizationUrl = new Uri(disco.AuthorizeEndpoint), 39 | TokenUrl = new Uri(disco.TokenEndpoint), 40 | Scopes = oauthScopes 41 | } 42 | } 43 | }); 44 | options.AddSecurityRequirement(new OpenApiSecurityRequirement 45 | { 46 | { 47 | new OpenApiSecurityScheme 48 | { 49 | Reference = new OpenApiReference 50 | { 51 | Type = ReferenceType.SecurityScheme, 52 | Id = "oauth2" 53 | } 54 | }, 55 | oauthScopes.Keys.ToArray() 56 | } 57 | }); 58 | } 59 | catch (Exception ex) 60 | { 61 | _logger.LogWarning(ex, "Error loading discovery document for Swagger UI"); 62 | } 63 | } 64 | 65 | private DiscoveryDocumentResponse GetDiscoveryDocument() 66 | { 67 | var client = new HttpClient(); 68 | var authority = "https://demo.duendesoftware.com"; 69 | return client.GetDiscoveryDocumentAsync(authority) 70 | .GetAwaiter() 71 | .GetResult(); 72 | } 73 | } -------------------------------------------------------------------------------- /CarvedRock.Api/UserScopeMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace CarvedRock.Api; 4 | public class UserScopeMiddleware 5 | { 6 | private readonly RequestDelegate _next; 7 | private readonly ILogger _logger; 8 | 9 | public UserScopeMiddleware(RequestDelegate next, ILogger logger) 10 | { 11 | _next = next; 12 | _logger = logger; 13 | } 14 | 15 | public async Task InvokeAsync(HttpContext context) 16 | { 17 | if (context.User.Identity is { IsAuthenticated: true }) 18 | { 19 | var user = context.User; 20 | var pattern = @"(?<=[\w]{1})[\w-\._\+%]*(?=[\w]{1}@)"; 21 | var maskedUsername = Regex.Replace(user.Identity.Name??"", pattern, m => new string('*', m.Length)); 22 | 23 | var subjectId = user.Claims.First(c=> c.Type == "sub")?.Value; 24 | 25 | using (_logger.BeginScope("User:{user}, SubjectId:{subject}", maskedUsername, subjectId)) 26 | { 27 | await _next(context); 28 | } 29 | } 30 | else 31 | { 32 | await _next(context); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /CarvedRock.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApplicationInsights": { 3 | "ConnectionString": "InstrumentationKey=ec748731-430b-4f07-bd28-951e421fa034;IngestionEndpoint=https://westus2-2.in.applicationinsights.azure.com/" 4 | }, 5 | "Serilog": { 6 | "MinimumLevel": { 7 | "Default": "Information", 8 | "Override": { 9 | "Microsoft.AspNetCore": "Warning", 10 | "CarvedRock": "Debug", 11 | "System": "Warning", 12 | "Microsoft.Hosting.Diagnostics": "Warning" 13 | } 14 | } 15 | }, 16 | "Logging": { 17 | "LogLevel": { 18 | "Default": "Information", 19 | "Microsoft.AspNetCore": "Warning", 20 | "CarvedRock": "Debug" 21 | }, 22 | "Console": { 23 | "FormatterName": "json", 24 | "FormatterOptions": { 25 | "SingleLine": true, 26 | "IncludeScopes": true, 27 | "TimestampFormat": "HH:mm:ss ", 28 | "UseUtcTimestamp": true, 29 | "JsonWriterOptions": { 30 | "Indented": true 31 | } 32 | } 33 | }, 34 | "ApplicationInsights": { 35 | "LogLevel": { 36 | "Default": "Information", 37 | "Microsoft.AspNetCore": "Warning", 38 | "CarvedRock": "Debug" 39 | } 40 | } 41 | // }, 42 | // "Debug": { 43 | // "LogLevel": { 44 | // "Default": "Critical", 45 | // "CarvedRock": "Information" 46 | // } 47 | // } 48 | }, 49 | "AllowedHosts": "*" 50 | } 51 | -------------------------------------------------------------------------------- /CarvedRock.Api/nlog.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CarvedRock.Data/CarvedRock.Data.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /CarvedRock.Data/CarvedRockRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using CarvedRock.Data.Entities; 3 | using Microsoft.Data.Sqlite; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace CarvedRock.Data 8 | { 9 | public class CarvedRockRepository :ICarvedRockRepository 10 | { 11 | private readonly LocalContext _ctx; 12 | private readonly ILogger _logger; 13 | private readonly ILogger _factoryLogger; 14 | 15 | public CarvedRockRepository(LocalContext ctx, ILogger logger, 16 | ILoggerFactory loggerFactory) 17 | { 18 | _ctx = ctx; 19 | _logger = logger; 20 | _factoryLogger = loggerFactory.CreateLogger("DataAccessLayer"); 21 | } 22 | public async Task> GetProductsAsync(string category) 23 | { 24 | _logger.LogInformation("Getting products in repository for {category}", category); 25 | if (category == "clothing") 26 | { 27 | var ex = new ApplicationException("Database error occurred!!"); 28 | ex.Data.Add("Category", category); 29 | throw ex; 30 | } 31 | if (category == "equip") 32 | { 33 | throw new SqliteException("Simulated fatal database error occurred!", 551); 34 | } 35 | 36 | try 37 | { 38 | return await _ctx.Products.Where(p => p.Category == category || category == "all").ToListAsync(); 39 | } 40 | catch (Exception ex) 41 | { 42 | var newEx = new ApplicationException("Something bad happened in database", ex); 43 | newEx.Data.Add("Category", category); 44 | throw newEx; 45 | } 46 | } 47 | 48 | public async Task GetProductByIdAsync(int id) 49 | { 50 | return await _ctx.Products.FindAsync(id); 51 | } 52 | 53 | public List GetProducts(string category) 54 | { 55 | return _ctx.Products.Where(p => p.Category == category || category == "all").ToList(); 56 | } 57 | 58 | public Product? GetProductById(int id) 59 | { 60 | var timer = new Stopwatch(); 61 | timer.Start(); 62 | 63 | var product = _ctx.Products.Find(id); 64 | timer.Stop(); 65 | 66 | _logger.LogDebug("Querying products for {id} finished in {milliseconds} milliseconds", 67 | id, timer.ElapsedMilliseconds); 68 | 69 | _factoryLogger.LogInformation("(F) Querying products for {id} finished in {ticks} ticks", 70 | id, timer.ElapsedTicks); 71 | 72 | return product; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CarvedRock.Data/Entities/Product.cs: -------------------------------------------------------------------------------- 1 | namespace CarvedRock.Data.Entities 2 | { 3 | public class Product 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } = null!; 7 | public string Description { get; set; } = null!; 8 | public double Price { get; set; } 9 | public string Category { get; set; } = null!; 10 | public string ImgUrl { get; set; } = null!; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CarvedRock.Data/ICarvedRockRepository.cs: -------------------------------------------------------------------------------- 1 | using CarvedRock.Data.Entities; 2 | 3 | namespace CarvedRock.Data 4 | { 5 | public interface ICarvedRockRepository 6 | { 7 | Task> GetProductsAsync(string category); 8 | Task GetProductByIdAsync(int id); 9 | 10 | List GetProducts(string category); 11 | Product? GetProductById(int id); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CarvedRock.Data/LocalContext.cs: -------------------------------------------------------------------------------- 1 | using CarvedRock.Data.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace CarvedRock.Data 5 | { 6 | public class LocalContext : DbContext 7 | { 8 | public DbSet Products { get; set; } = null!; 9 | 10 | public string DbPath { get; set; } 11 | 12 | public LocalContext() 13 | { 14 | var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 15 | DbPath = Path.Join(path, "carvedrock-logging.db"); 16 | } 17 | 18 | protected override void OnConfiguring(DbContextOptionsBuilder options) 19 | => options.UseSqlite($"Data Source={DbPath}"); 20 | 21 | public void MigrateAndCreateData() 22 | { 23 | Database.Migrate(); 24 | 25 | if (Products.Any()) 26 | { 27 | Products.RemoveRange(Products); 28 | SaveChanges(); 29 | }; 30 | 31 | Products.Add(new Product 32 | { 33 | Name = "Trailblazer", 34 | Category = "boots", 35 | Price = 69.99, 36 | Description = "Great support in this high-top to take you to great heights and trails.", 37 | ImgUrl = "https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/img-brownboots.jpg" 38 | }); 39 | Products.Add(new Product 40 | { 41 | Name = "Coastliner", 42 | Category = "boots", 43 | Price = 49.99, 44 | Description = 45 | "Easy in and out with this lightweight but rugged shoe with great ventilation to get your around shores, beaches, and boats.", 46 | ImgUrl = "https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/img-greyboots.jpg" 47 | }); 48 | Products.Add(new Product 49 | { 50 | Name = "Woodsman", 51 | Category = "boots", 52 | Price = 64.99, 53 | Description = 54 | "All the insulation and support you need when wandering the rugged trails of the woods and backcountry.", 55 | ImgUrl = "/images/shutterstock_222721876.jpg" 56 | }); 57 | Products.Add(new Product 58 | { 59 | Name = "Billy", 60 | Category = "boots", 61 | Price = 79.99, 62 | Description = 63 | "Get up and down rocky terrain like a billy-goat with these awesome high-top boots with outstanding support.", 64 | ImgUrl = "/images/shutterstock_475046062.jpg" 65 | }); 66 | 67 | SaveChanges(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CarvedRock.Data/Migrations/20211218131519_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using CarvedRock.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | 8 | #nullable disable 9 | 10 | namespace CarvedRock.Data.Migrations 11 | { 12 | [DbContext(typeof(LocalContext))] 13 | [Migration("20211218131519_Initial")] 14 | partial class Initial 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder.HasAnnotation("ProductVersion", "6.0.1"); 20 | 21 | modelBuilder.Entity("CarvedRock.Data.Entities.Product", b => 22 | { 23 | b.Property("Id") 24 | .ValueGeneratedOnAdd() 25 | .HasColumnType("INTEGER"); 26 | 27 | b.Property("Category") 28 | .IsRequired() 29 | .HasColumnType("TEXT"); 30 | 31 | b.Property("Description") 32 | .IsRequired() 33 | .HasColumnType("TEXT"); 34 | 35 | b.Property("Name") 36 | .IsRequired() 37 | .HasColumnType("TEXT"); 38 | 39 | b.Property("Price") 40 | .HasColumnType("REAL"); 41 | 42 | b.HasKey("Id"); 43 | 44 | b.ToTable("Products"); 45 | }); 46 | #pragma warning restore 612, 618 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CarvedRock.Data/Migrations/20211218131519_Initial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace CarvedRock.Data.Migrations 6 | { 7 | public partial class Initial : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Products", 13 | columns: table => new 14 | { 15 | Id = table.Column(type: "INTEGER", nullable: false) 16 | .Annotation("Sqlite:Autoincrement", true), 17 | Name = table.Column(type: "TEXT", nullable: false), 18 | Description = table.Column(type: "TEXT", nullable: false), 19 | Price = table.Column(type: "REAL", nullable: false), 20 | Category = table.Column(type: "TEXT", nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_Products", x => x.Id); 25 | }); 26 | } 27 | 28 | protected override void Down(MigrationBuilder migrationBuilder) 29 | { 30 | migrationBuilder.DropTable( 31 | name: "Products"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CarvedRock.Data/Migrations/20211231150113_ImgUrl.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using CarvedRock.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | 8 | #nullable disable 9 | 10 | namespace CarvedRock.Data.Migrations 11 | { 12 | [DbContext(typeof(LocalContext))] 13 | [Migration("20211231150113_ImgUrl")] 14 | partial class ImgUrl 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder.HasAnnotation("ProductVersion", "6.0.1"); 20 | 21 | modelBuilder.Entity("CarvedRock.Data.Entities.Product", b => 22 | { 23 | b.Property("Id") 24 | .ValueGeneratedOnAdd() 25 | .HasColumnType("INTEGER"); 26 | 27 | b.Property("Category") 28 | .IsRequired() 29 | .HasColumnType("TEXT"); 30 | 31 | b.Property("Description") 32 | .IsRequired() 33 | .HasColumnType("TEXT"); 34 | 35 | b.Property("ImgUrl") 36 | .IsRequired() 37 | .HasColumnType("TEXT"); 38 | 39 | b.Property("Name") 40 | .IsRequired() 41 | .HasColumnType("TEXT"); 42 | 43 | b.Property("Price") 44 | .HasColumnType("REAL"); 45 | 46 | b.HasKey("Id"); 47 | 48 | b.ToTable("Products"); 49 | }); 50 | #pragma warning restore 612, 618 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CarvedRock.Data/Migrations/20211231150113_ImgUrl.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace CarvedRock.Data.Migrations 6 | { 7 | public partial class ImgUrl : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "ImgUrl", 13 | table: "Products", 14 | type: "TEXT", 15 | nullable: false, 16 | defaultValue: ""); 17 | } 18 | 19 | protected override void Down(MigrationBuilder migrationBuilder) 20 | { 21 | migrationBuilder.DropColumn( 22 | name: "ImgUrl", 23 | table: "Products"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CarvedRock.Data/Migrations/LocalContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using CarvedRock.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | 7 | #nullable disable 8 | 9 | namespace CarvedRock.Data.Migrations 10 | { 11 | [DbContext(typeof(LocalContext))] 12 | partial class LocalContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder.HasAnnotation("ProductVersion", "6.0.1"); 18 | 19 | modelBuilder.Entity("CarvedRock.Data.Entities.Product", b => 20 | { 21 | b.Property("Id") 22 | .ValueGeneratedOnAdd() 23 | .HasColumnType("INTEGER"); 24 | 25 | b.Property("Category") 26 | .IsRequired() 27 | .HasColumnType("TEXT"); 28 | 29 | b.Property("Description") 30 | .IsRequired() 31 | .HasColumnType("TEXT"); 32 | 33 | b.Property("ImgUrl") 34 | .IsRequired() 35 | .HasColumnType("TEXT"); 36 | 37 | b.Property("Name") 38 | .IsRequired() 39 | .HasColumnType("TEXT"); 40 | 41 | b.Property("Price") 42 | .HasColumnType("REAL"); 43 | 44 | b.HasKey("Id"); 45 | 46 | b.ToTable("Products"); 47 | }); 48 | #pragma warning restore 612, 618 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CarvedRock.Domain/CarvedRock.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | enable 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CarvedRock.Domain/IProductLogic.cs: -------------------------------------------------------------------------------- 1 | using CarvedRock.Data.Entities; 2 | 3 | namespace CarvedRock.Domain; 4 | 5 | public interface IProductLogic 6 | { 7 | Task> GetProductsForCategoryAsync(string category); 8 | Task GetProductByIdAsync(int id); 9 | IEnumerable GetProductsForCategory(string category); 10 | Product? GetProductById(int id); 11 | } -------------------------------------------------------------------------------- /CarvedRock.Domain/ProductLogic.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using CarvedRock.Data; 3 | using CarvedRock.Data.Entities; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace CarvedRock.Domain; 7 | 8 | public class ProductLogic : IProductLogic 9 | { 10 | private readonly ILogger _logger; 11 | private readonly ICarvedRockRepository _repo; 12 | public ProductLogic(ILogger logger, ICarvedRockRepository repo) 13 | { 14 | _logger = logger; 15 | _repo = repo; 16 | } 17 | public async Task> GetProductsForCategoryAsync(string category) 18 | { 19 | _logger.LogInformation("Getting products in logic for {category}", category); 20 | 21 | Activity.Current?.AddEvent(new ActivityEvent("Getting products from repository")); 22 | var results = await _repo.GetProductsAsync(category); 23 | Activity.Current?.AddEvent(new ActivityEvent("Retrieved products from repository")); 24 | 25 | return results; 26 | } 27 | 28 | public async Task GetProductByIdAsync(int id) 29 | { 30 | return await _repo.GetProductByIdAsync(id); 31 | } 32 | 33 | public IEnumerable GetProductsForCategory(string category) 34 | { 35 | return _repo.GetProducts(category); 36 | } 37 | 38 | public Product? GetProductById(int id) 39 | { 40 | _logger.LogDebug("Logic for single product ({id})", id); 41 | return _repo.GetProductById(id); 42 | } 43 | } -------------------------------------------------------------------------------- /CarvedRock.WebApp/CarvedRock.WebApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/CarvedRock.WebApp.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | RazorPageScaffolder 5 | root/Common/RazorPage 6 | 7 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Models/Product.cs: -------------------------------------------------------------------------------- 1 | namespace CarvedRock.WebApp.Models 2 | { 3 | public class Product 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } = null!; 7 | public string Description { get; set; } = null!; 8 | public double Price { get; set; } 9 | public string Category { get; set; } = null!; 10 | public string ImgUrl { get; set; } = null!; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/CurrentPromotion.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model CarvedRock.WebApp.Pages.CurrentPromotionModel 3 | @{ 4 | } 5 | 6 |

Current Promotion

7 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/CurrentPromotion.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace CarvedRock.WebApp.Pages 4 | { 5 | public class CurrentPromotionModel : PageModel 6 | { 7 | public void OnGet() 8 | { 9 | throw new NotImplementedException(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |
15 | HTTP TraceId: @Model.TraceId 16 |
17 | Activity.Id: @Model.CurrentActivity?.Id 18 |
19 | Activity.SpanId: @Model.CurrentActivity?.SpanId 20 |
21 | Activity.TraceId: @Model.CurrentActivity?.TraceId 22 |
23 | Activity.Parent: @Model.CurrentActivity?.Parent 24 |
25 | Activity.ParentId: @Model.CurrentActivity?.ParentId 26 |
27 | Activity.ParentSpanId: @Model.CurrentActivity?.ParentSpanId 28 |
29 | Activity.RootId: @Model.CurrentActivity?.RootId 30 |
31 | Activity.Kind: @Model.CurrentActivity?.Kind 32 |

33 | } 34 | 35 | @*

Development Mode

36 |

37 | Swapping to the Development environment displays detailed information about the error that occurred. 38 |

39 |

40 | The Development environment shouldn't be enabled for deployed applications. 41 | It can result in displaying sensitive information from exceptions to end users. 42 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 43 | and restarting the app. 44 |

*@ 45 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | using System.Diagnostics; 4 | using Microsoft.AspNetCore.Authorization; 5 | 6 | namespace CarvedRock.WebApp.Pages 7 | { 8 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 9 | [IgnoreAntiforgeryToken] 10 | [AllowAnonymous] 11 | public class ErrorModel : PageModel 12 | { 13 | public string? RequestId { get; set; } 14 | public Activity? CurrentActivity { get; set; } 15 | public string? TraceId { get; set; } 16 | 17 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 18 | 19 | private readonly ILogger _logger; 20 | 21 | public ErrorModel(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | public void OnGet() 27 | { 28 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 29 | CurrentActivity = Activity.Current; 30 | TraceId = HttpContext.TraceIdentifier; 31 | 32 | var userName = User.Identity?.IsAuthenticated ?? false? User.Identity.Name : ""; 33 | _logger.LogWarning("User {userName} experienced an error.", userName); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{} 4 | 5 | 6 |
7 |
8 |

GET A GRIP

9 |

20% OFF

10 |

THROUGHOUT THE SEASON

11 |
12 |
13 | 14 | 15 | 30 | 31 | 32 |
33 | 63 |
64 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace CarvedRock.WebApp.Pages 5 | { 6 | [AllowAnonymous] 7 | public class IndexModel : PageModel 8 | { 9 | private readonly ILogger _logger; 10 | 11 | public IndexModel(ILogger logger) 12 | { 13 | _logger = logger; 14 | } 15 | 16 | public void OnGet() 17 | { 18 | 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/Listing.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model CarvedRock.WebApp.Pages.ListingModel 3 | @{} 4 | 5 |

@Model.CategoryName

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @foreach (var product in Model.Products) { 17 | 18 | 23 | 26 | 29 | 30 | } 31 | 32 |
NameDescriptionPrice
19 | @product.Name 20 |
21 | @Html.DisplayFor(modelItem => product.Name) 22 |
24 | @Html.DisplayFor(modelItem => product.Description) 25 | 27 | @Html.DisplayFor(modelItem => product.Price) 28 |
33 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/Listing.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Headers; 2 | using CarvedRock.WebApp.Models; 3 | using Microsoft.AspNetCore.Authentication; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace CarvedRock.WebApp.Pages 8 | { 9 | public partial class ListingModel : PageModel 10 | { 11 | private readonly HttpClient _apiClient; 12 | private readonly ILogger _logger; 13 | private readonly HttpContext? _httpCtx; 14 | 15 | public ListingModel(HttpClient apiClient, ILogger logger, 16 | IHttpContextAccessor httpContextAccessor) 17 | { 18 | _logger = logger; 19 | _apiClient = apiClient; 20 | _apiClient.BaseAddress = new Uri("https://localhost:7213"); 21 | _httpCtx = httpContextAccessor.HttpContext; 22 | } 23 | 24 | public List Products { get; set; } = new List(); 25 | public string CategoryName { get; set; } = ""; 26 | 27 | [LoggerMessage(0, LogLevel.Warning, "API failure: {fullPath} Response: {statusCode}, Trace: {traceId}")] 28 | partial void LogApiFailure(string fullPath, int statusCode, string traceId); 29 | public async Task OnGetAsync() 30 | { 31 | _logger.LogInformation("Making API call to get products..."); 32 | var cat = Request.Query["cat"].ToString(); 33 | if (string.IsNullOrEmpty(cat)) 34 | { 35 | throw new Exception("failed"); 36 | } 37 | 38 | if (_httpCtx != null) 39 | { 40 | var accessToken = await _httpCtx.GetTokenAsync("access_token"); 41 | _apiClient.DefaultRequestHeaders.Authorization = 42 | new AuthenticationHeaderValue("Bearer", accessToken); 43 | // for a better way to include and manage access tokens for API calls: 44 | // https://identitymodel.readthedocs.io/en/latest/aspnetcore/web.html 45 | } 46 | 47 | var response = await _apiClient.GetAsync($"Product?category={cat}"); 48 | if (!response.IsSuccessStatusCode) 49 | { 50 | var fullPath = $"{_apiClient.BaseAddress}Product?category={cat}"; 51 | 52 | // trace id 53 | var details = await response.Content.ReadFromJsonAsync() ?? 54 | new ProblemDetails(); 55 | var traceId = details.Extensions["traceId"]?.ToString(); 56 | 57 | LogApiFailure(fullPath, (int) response.StatusCode, traceId ?? ""); 58 | 59 | //_logger.LogWarning( 60 | // "API failure: {fullPath} Response: {apiResponse}, Trace: {trace}, User: {user}", 61 | // fullPath, (int) response.StatusCode, traceId, userName); 62 | 63 | throw new Exception("API call failed!"); 64 | } 65 | 66 | Products = await response.Content.ReadFromJsonAsync>() ?? new List(); 67 | if (Products.Any()) 68 | { 69 | CategoryName = Products.First().Category.First().ToString().ToUpper() + 70 | Products.First().Category[1..]; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Carved Rock Fitness 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 50 | 51 | 52 | 57 | 58 | @RenderBody() 59 | 60 | 132 |
133 | 134 | @await RenderSectionAsync("Scripts", required: false) 135 | 136 | 137 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/Shared/_Layout.cshtml.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | a { 11 | color: #0077cc; 12 | } 13 | 14 | .btn-primary { 15 | color: #fff; 16 | background-color: #1b6ec2; 17 | border-color: #1861ac; 18 | } 19 | 20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 21 | color: #fff; 22 | background-color: #1b6ec2; 23 | border-color: #1861ac; 24 | } 25 | 26 | .border-top { 27 | border-top: 1px solid #e5e5e5; 28 | } 29 | .border-bottom { 30 | border-bottom: 1px solid #e5e5e5; 31 | } 32 | 33 | .box-shadow { 34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 35 | } 36 | 37 | button.accept-policy { 38 | font-size: 1rem; 39 | line-height: inherit; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | white-space: nowrap; 47 | line-height: 60px; 48 | } 49 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using CarvedRock.WebApp 2 | @namespace CarvedRock.WebApp.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using Microsoft.IdentityModel.Tokens; 3 | using CarvedRock.WebApp; 4 | // using NLog; 5 | // using NLog.Web; 6 | using Serilog; 7 | using Serilog.Exceptions; 8 | using Microsoft.Extensions.Diagnostics.HealthChecks; 9 | using Serilog.Enrichers.Span; 10 | using System.Diagnostics; 11 | using OpenTelemetry.Trace; 12 | using OpenTelemetry.Resources; 13 | 14 | var builder = WebApplication.CreateBuilder(args); 15 | builder.Logging.ClearProviders(); 16 | //builder.Logging.AddJsonConsole(); 17 | //builder.Services.AddApplicationInsightsTelemetry(); 18 | 19 | builder.Host.UseSerilog((context, loggerConfig) => { 20 | loggerConfig 21 | .ReadFrom.Configuration(context.Configuration) 22 | .WriteTo.Console() 23 | .Enrich.WithExceptionDetails() 24 | .Enrich.FromLogContext() 25 | .Enrich.With() 26 | .WriteTo.Seq("http://localhost:5341"); 27 | }); 28 | 29 | builder.Services.AddOpenTelemetryTracing(b => { 30 | b.SetResourceBuilder( 31 | ResourceBuilder.CreateDefault().AddService(builder.Environment.ApplicationName)) 32 | .AddAspNetCoreInstrumentation() 33 | .AddHttpClientInstrumentation() 34 | .AddOtlpExporter(opts => { opts.Endpoint = new Uri("http://localhost:4317"); }); 35 | }); 36 | 37 | //NLog.LogManager.Setup().LoadConfigurationFromFile(); 38 | //builder.Host.UseNLog(); 39 | 40 | JwtSecurityTokenHandler.DefaultMapInboundClaims = false; 41 | builder.Services.AddAuthentication(options => 42 | { 43 | options.DefaultScheme = "Cookies"; 44 | options.DefaultChallengeScheme = "oidc"; 45 | }) 46 | .AddCookie("Cookies") 47 | .AddOpenIdConnect("oidc", options => 48 | { 49 | options.Authority = "https://demo.duendesoftware.com"; 50 | options.ClientId = "interactive.confidential"; 51 | options.ClientSecret = "secret"; 52 | options.ResponseType = "code"; 53 | options.Scope.Add("openid"); 54 | options.Scope.Add("profile"); 55 | options.Scope.Add("email"); 56 | options.Scope.Add("api"); 57 | options.Scope.Add("offline_access"); 58 | options.GetClaimsFromUserInfoEndpoint = true; 59 | options.TokenValidationParameters = new TokenValidationParameters 60 | { 61 | NameClaimType = "email" 62 | }; 63 | options.SaveTokens = true; 64 | }); 65 | builder.Services.AddHttpContextAccessor(); 66 | builder.Services.AddHealthChecks() 67 | .AddIdentityServer(new Uri("https://demo.duendesoftware.com"), failureStatus: HealthStatus.Degraded); 68 | 69 | builder.Services.AddRazorPages(); 70 | builder.Services.AddHttpClient(); 71 | 72 | var app = builder.Build(); 73 | 74 | app.UseExceptionHandler("/Error"); 75 | 76 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 77 | //app.UseHsts(); 78 | //app.UseHttpsRedirection(); 79 | 80 | app.UseStaticFiles(); 81 | 82 | app.UseRouting(); 83 | app.UseAuthentication(); 84 | app.UseMiddleware(); 85 | app.UseAuthorization(); 86 | 87 | app.MapRazorPages().RequireAuthorization(); 88 | app.MapHealthChecks("health").AllowAnonymous(); 89 | 90 | app.Run(); 91 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:49282", 7 | "sslPort": 44374 8 | } 9 | }, 10 | "profiles": { 11 | "CarvedRock.WebApp": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "applicationUrl": "https://localhost:7224;http://localhost:5224", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "IIS Express": { 21 | "commandName": "IISExpress", 22 | "launchBrowser": true, 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/UserScopeMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace CarvedRock.WebApp; 4 | public class UserScopeMiddleware 5 | { 6 | private readonly RequestDelegate _next; 7 | private readonly ILogger _logger; 8 | 9 | public UserScopeMiddleware(RequestDelegate next, ILogger logger) 10 | { 11 | _next = next; 12 | _logger = logger; 13 | } 14 | 15 | public async Task InvokeAsync(HttpContext context) 16 | { 17 | if (context.User.Identity is { IsAuthenticated: true }) 18 | { 19 | var user = context.User; 20 | var pattern = @"(?<=[\w]{1})[\w-\._\+%]*(?=[\w]{1}@)"; 21 | var maskedUsername = Regex.Replace(user.Identity.Name??"", pattern, m => new string('*', m.Length)); 22 | 23 | var subjectId = user.Claims.First(c=> c.Type == "sub")?.Value; 24 | 25 | using (_logger.BeginScope("User:{user}, SubjectId:{subject}", maskedUsername, subjectId)) 26 | { 27 | await _next(context); 28 | } 29 | } 30 | else 31 | { 32 | await _next(context); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /CarvedRock.WebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApplicationInsights": { 3 | "ConnectionString": "InstrumentationKey=ec748731-430b-4f07-bd28-951e421fa034;IngestionEndpoint=https://westus2-2.in.applicationinsights.azure.com/" 4 | }, 5 | "Serilog": { 6 | "MinimumLevel": { 7 | "Default": "Information", 8 | "Override": { 9 | "Microsoft.AspNetCore": "Warning", 10 | "CarvedRock": "Debug", 11 | "System": "Warning", 12 | "Microsoft.Hosting.Diagnostics": "Warning" 13 | } 14 | } 15 | }, 16 | "Logging": { 17 | "LogLevel": { 18 | "Default": "Information", 19 | "Microsoft.AspNetCore": "Warning", 20 | "Microsoft.AspNetCore.HttpLogging": "Information", 21 | "CarvedRock": "Debug" 22 | }, 23 | "Console": { 24 | "FormatterName": "json", 25 | "FormatterOptions": { 26 | "SingleLine": true, 27 | "IncludeScopes": true, 28 | "TimestampFormat": "HH:mm:ss ", 29 | "UseUtcTimestamp": true, 30 | "JsonWriterOptions": { 31 | "Indented": true 32 | } 33 | } 34 | }, 35 | "ApplicationInsights": { 36 | "LogLevel": { 37 | "Default": "Information", 38 | "Microsoft.AspNetCore": "Warning", 39 | "CarvedRock": "Debug" 40 | } 41 | }, 42 | "Debug": { 43 | "LogLevel": { 44 | "Default": "Critical" 45 | } 46 | } 47 | }, 48 | "AllowedHosts": "*" 49 | } 50 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/nlog.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/wwwroot/css/custom.css: -------------------------------------------------------------------------------- 1 | .orange-border { 2 | border-color: #faa541; 3 | border-width: 5px; 4 | border-style: solid; 5 | } 6 | -------------------------------------------------------------------------------- /CarvedRock.WebApp/wwwroot/css/main.css: -------------------------------------------------------------------------------- 1 | /* HEADER */ 2 | .header-nav--nav {border: 0; border-bottom: 1px solid #a0a0a0; margin-bottom: 0;} 3 | 4 | .header-nav--nav .header-nav--main ul {margin: 10px 0 10px;} 5 | .header-nav--nav .header-nav--main ul li.nav-item {font-size: 14px; text-transform: uppercase; font-weight: 700; letter-spacing: 0.2px; padding-bottom: 10px;} 6 | .header-nav--nav .header-nav--main ul li.nav-item:after {content: ""; display: inline-block; width: 15px;} 7 | .header-nav--nav .header-nav--main ul li.nav-item:last-child:after {width: 0;} 8 | .header-nav--nav .header-nav--main ul li.nav-item a {padding: 0;} 9 | 10 | .header-nav--nav .header-nav--main .form-inline input.form-control {width: 330px; height: 30px; margin: 0; box-shadow: none;} 11 | .header-nav--nav .header-nav--main .form-inline .btn-default {background: #a0a0a0; position: relative; border: 0; border-radius: 0; font-weight: 300; width: 100px; height: 30px; min-width: auto; top: 0; left: -3px;} 12 | 13 | .header-nav--nav .navbar-brand {padding: 0; width: 100%;} 14 | .header-nav--nav .navbar-brand a {display: block; width: 170px;} 15 | 16 | .header-nav--nav .navbar-collapse {padding-right: 0;} 17 | 18 | .header-nav--nav .header-nav--utilities {margin-bottom: 15px;} 19 | .header-nav--nav .header-nav--utilities .account {text-align: left; font-size: 14px;} 20 | .header-nav--nav .header-nav--utilities .account a {font-size: 14px;} 21 | .header-nav--nav .header-nav--utilities .image {width: 60px; height: 60px; margin-right: 15px;} 22 | .header-nav--nav .header-nav--utilities .image img {border-radius: 100px;} 23 | 24 | .header-nav--search {background: #a0a0a0; padding: 5px 20px;} 25 | .header-nav--search input.form-control {margin-bottom: 0; width: 100%;} 26 | 27 | @media only screen and (min-width: 768px) { 28 | .header-nav--nav .navbar-collapse.collapse {flex: 1; display: flex !important;} 29 | .header-nav--nav .navbar-brand {padding: 0; width: auto; margin-right: 0;} 30 | .header-nav--nav .navbar-brand a {display: block; width: 120px;} 31 | .header-nav--nav .header-nav--main ul {margin-bottom: 0;} 32 | .header-nav--nav .header-nav--main ul li.nav-item {font-size: 13px; padding-bottom: 0;} 33 | .header-nav--nav .header-nav--main ul li.nav-item:after {width: 8px;} 34 | .header-nav--nav .header-nav--utilities {margin-left: auto; margin-bottom: 0;} 35 | .header-nav--nav .header-nav--utilities .account {font-size: 14px; text-align: right;} 36 | .header-nav--nav .header-nav--utilities .image {margin-left: 15px;} 37 | } 38 | 39 | @media only screen and (min-width: 992px) { 40 | .header-nav--nav .header-nav--main ul li.nav-item {font-size: 16px;} 41 | .header-nav--nav .navbar-brand {margin-right: 40px;} 42 | .header-nav--nav .navbar-brand a {display: block; width: 170px;} 43 | .header-nav--nav .header-nav--main ul li.nav-item:after {width: 15px;} 44 | .header-nav--nav .header-nav--utilities .account {font-size: 18px;} 45 | } 46 | 47 | /* HERO */ 48 | .crf-hero {background-image: url('https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/hero_bkgd_v1.jpg'); background-repeat: no-repeat; height: 400px; background-size: cover; background-position: center center; text-align: center;} 49 | .crf-hero h1, .crf-hero h2 {font-size: 63px; font-weight: 700; margin: 0; flex: inherit; line-height: 0.9;} 50 | .crf-hero h1 {left: -30px;} 51 | .crf-hero h2 {color: #faa541;} 52 | .crf-hero p.lead {letter-spacing: 2px; font-weight: 700;} 53 | 54 | @media only screen and (min-width: 1200px) { 55 | .crf-hero .container {max-width: 1170px;} 56 | } 57 | @media only screen and (max-width: 768px) { 58 | .crf-hero {margin-bottom: 0; padding-bottom: 0; background-position: 70% 0; background-size: 180%;} 59 | .crf-hero .container {margin-top: auto; background-color: #faa541; padding: 15px;} 60 | .crf-hero h1 {left: 0;} 61 | .crf-hero h2 , .crf-hero p.lead {color: white;} 62 | .crf-hero p.lead {margin-top: 5px; margin-bottom: 0;} 63 | } 64 | @media only screen and (max-width: 480px) { 65 | .crf-hero h1, .crf-hero h2 {font-size: 55px;} 66 | } 67 | 68 | /* CIGAR BANNER */ 69 | .crf-cigar-banner {margin-top: 55px; margin-bottom: 55px;} 70 | .crf-cigar-banner--container {width: 100%; border-top: 1px solid #a0a0a0; border-bottom: 1px solid #a0a0a0; padding: 25px 0; margin-left: 15px; margin-right: 15px; text-align: center; position: relative; overflow: hidden;} 71 | .crf-cigar-banner--container img {width: 250px;} 72 | .crf-cigar-banner--text > div {font-size: 30px; font-weight: 700; text-transform: uppercase; line-height: 1;} 73 | .crf-cigar-banner--text > div:last-child {font-size: 16px; font-weight: 400; margin-top: 5px;} 74 | 75 | @media only screen and (max-width: 1024px) { 76 | .crf-cigar-banner--text > div {font-size: 24px;} 77 | .crf-cigar-banner--container img {width: 220px;} 78 | } 79 | @media only screen and (max-width: 768px) { 80 | .crf-cigar-banner {margin-top: 0;} 81 | .crf-cigar-banner--container {flex-wrap: wrap; margin-left: 0; margin-right: 0; border-bottom-color: transparent;} 82 | .crf-cigar-banner--text > div:last-child {margin-top: 15px;} 83 | .crf-cigar-banner--container img:nth-of-type(1) {left:0; top: 0;} 84 | .crf-cigar-banner--container img:nth-of-type(2) {top: 0; right: 0;} 85 | .crf-cigar-banner--container img {width: 350px; position: absolute; mix-blend-mode: multiply;} 86 | .crf-cigar-banner--text {flex: 0 0 100%; margin-top: 250px;} 87 | } 88 | @media only screen and (max-width: 640px) { 89 | .crf-cigar-banner--container img:nth-of-type(1) {left: -15%;} 90 | .crf-cigar-banner--container img:nth-of-type(2) {right: -15%;} 91 | } 92 | @media only screen and (max-width: 480px) { 93 | .order-sm-0 {order: 0;} 94 | .order-sm-1 {order: 1;} 95 | .order-sm-2 {order: 2;} 96 | .crf-cigar-banner--container img {width: 270px;} 97 | .crf-cigar-banner--text {margin-top: 180px;} 98 | } 99 | 100 | /* STORIES */ 101 | .crf-story a > div {color: #364147;} 102 | .crf-story .crf-story--text {padding: 30px 0; text-transform: uppercase; text-align: center;} 103 | .crf-story .crf-story--text > div {font-size: 16px; font-weight: 200; color: #faa541;} 104 | .crf-story .crf-story--text h3 {font-size: 30px; font-weight: 700; line-height: 1; margin: 0;} 105 | .crf-story .crf-story--image img {width: 100%;} 106 | 107 | @media only screen and (max-width: 991px) { 108 | .crf-story .crf-story--image {height: 180px; overflow: hidden; position: relative;} 109 | .crf-story .crf-story--image img {position: relative; top: 50%; transform: translateY(-50%);} 110 | } 111 | 112 | /* FULL BANNER */ 113 | .crf-full-banner {margin-top: 50px; margin-bottom: 50px;} 114 | .crf-full-banner .row {position: relative;} 115 | .crf-full-banner .row:before {content: ""; position: absolute; border: 1px solid white; display: block; top: 30px; bottom: 30px; left: 50px; right: 50px; z-index: 1;} 116 | .crf-full-banner .crf-full-banner--image { 117 | flex: 0 0 58.333333%; 118 | background-image: url('https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/img-vistas.jpg'); 119 | background-repeat: no-repeat; 120 | background-size: cover; 121 | background-position: top center; 122 | min-height: 400px; 123 | } 124 | .crf-full-banner .crf-full-banner--text {flex: 0 0 41.666667%; background: #364147; text-transform: uppercase; padding: 30px; position: relative;} 125 | .crf-full-banner .crf-full-banner--text h4 {font-size: 40px; font-weight: 700; color: #637f94; font-style: italic; line-height: 1; margin: 0;} 126 | .crf-full-banner .crf-full-banner--text h4:after {content: ""; width: 100px; height: 1px; background: #faa541; margin: 20px auto; display: block;} 127 | .crf-full-banner .crf-full-banner--text > div {color: white;} 128 | .crf-full-banner .crf-full-banner--text > div:nth-of-type(1) {font-size: 60px; font-weight: 700; line-height: 1;} 129 | .crf-full-banner .crf-full-banner--text > div:nth-of-type(2) {font-size: 25px; font-weight: 200; letter-spacing: 3px;} 130 | .crf-full-banner .crf-full-banner--text .btn.btn-default {text-transform: none; margin-top: 50px; text-transform: uppercase; letter-spacing: 1px; z-index: 1;} 131 | 132 | @media only screen and (max-width: 991px) { 133 | .crf-full-banner .col {position: relative; flex-direction: column;} 134 | .crf-full-banner .crf-full-banner--text {padding: 30px 30px 50px;} 135 | .crf-full-banner .crf-full-banner--text h4 {display: none;} 136 | .crf-full-banner .crf-full-banner--text .btn.btn-default {margin-top: 20px;} 137 | .crf-full-banner .crf-full-banner--image {min-height: 300px;} 138 | .crf-full-banner .crf-full-banner--image h4 {font-size: 40px; font-weight: 700; color: #637f94; font-style: italic; line-height: 1; margin: 0; text-align: center; top: 50px; position: relative; text-transform: uppercase;} 139 | } 140 | @media only screen and (max-width: 768px) { 141 | .crf-full-banner .col {padding-left: 0; padding-right: 0;} 142 | .crf-full-banner .row:before {left: 30px; right: 30px;} 143 | } 144 | 145 | /* FOOTER */ 146 | footer {background: black; position: relative; z-index: 0; min-height: 330px;} 147 | footer:after {content: ""; background: #a0a0a0; width: 75%; position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: -1;} 148 | 149 | footer .crf-footer--category {flex: 0 0 75%; padding: 40px 15px;} 150 | footer .crf-footer--header {text-transform: uppercase; font-weight: 700; font-size: 16px; margin-bottom: 15px;} 151 | 152 | footer .crf-footer--links a {color: white; text-transform: uppercase; font-size: 12px; display: block; padding: 0 0 4px;} 153 | footer .crf-footer--links a:hover {text-decoration: underline;} 154 | footer .crf-footer--links.active .crf-footer--header {margin-bottom: 15px;} 155 | footer .crf-footer--links.active ul {display: block;} 156 | 157 | footer .crf-footer--newsletter .crf-footer--header {font-size: 12px; font-weight: 400; margin-bottom: 5px;} 158 | footer .crf-footer--newsletter .crf-footer--subheader {font-style: italic; font-size: 12px; font-weight: 400; margin-bottom: 15px;} 159 | footer .crf-footer--newsletter input {border: 1px solid #364147; width: 200px; margin-bottom: 10px;} 160 | footer .crf-footer--newsletter button {width: 150px; min-width: auto; text-transform: uppercase; letter-spacing: 1px;} 161 | 162 | footer .crf-footer-m--social {order: 4; position: relative;} 163 | footer .crf-footer--social ul li, footer .crf-footer-m--social ul li {display: inline-block;} 164 | footer .crf-footer--social ul li a, footer .crf-footer-m--social ul li a {display: block; width: 23px; height: 23px; background-size: 23px 23px; background-position: center;} 165 | footer .crf-footer--social .twitter, footer .crf-footer-m--social .twitter {background-image: url('https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/social_icons/Twitter_Icon.svg');} 166 | footer .crf-footer--social .facebook, footer .crf-footer-m--social .facebook {background-image: url('https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/social_icons/Facebook_Icon.svg');} 167 | footer .crf-footer--social .instagram, footer .crf-footer-m--social .instagram {background-image: url('https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/social_icons/Instagram_Icon.svg');} 168 | footer .crf-footer--social .pinterest, footer .crf-footer-m--social .pinterest {background-image: url('https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/social_icons/Pinterest_Icon.svg');} 169 | footer .crf-footer--social .googleplus, footer .crf-footer-m--social .googleplus {background-image: url('https://www.pluralsight.com/content/dam/pluralsight2/teach/author-tools/carved-rock-fitness/social_icons/GooglePlus_Icon.svg');} 170 | 171 | footer .crf-footer--logo {color: #e5e5e5; font-size: 16px; flex: 0 0 25%;} 172 | footer .crf-footer--logo img {max-width: 230px;} 173 | footer .crf-footer--logo .container {padding: 40px 0;} 174 | footer .crf-footer--logo .container > div {padding: 0 30px;} 175 | footer .crf-footer--logo .container > div:before {content: ""; display: block; width: 100%; height: 1px; margin: 30px 0; background: linear-gradient(to right,#f05a28 0,#e80a89 100%);} 176 | footer .crf-footer--copyright {text-align: center; font-size: 12px; flex: 0 0 75%; padding: 10px 0;} 177 | 178 | @media only screen and (min-width: 992px) { 179 | footer {flex-wrap: wrap;} 180 | } 181 | @media only screen and (max-width: 991px) and (min-width: 320px) { 182 | footer {flex-direction: column; background: transparent;} 183 | footer:after {width: 100%; background: transparent;} 184 | 185 | footer .crf-footer--category {background: #a0a0a0; padding-bottom: 0;} 186 | footer .crf-footer--logo {background: black; margin: 50px 20px;} 187 | 188 | footer .crf-footer--links:after {content: ""; display: block; height: 2px; background: white; margin: 10px -15px 10px;} 189 | footer .crf-footer--links:nth-of-type(1) {order: 1;} 190 | footer .crf-footer--links:nth-of-type(2) {order: 2;} 191 | footer .crf-footer--links:nth-of-type(3) {order: 3;} 192 | footer .crf-footer--links ul {display: none;} 193 | 194 | footer .crf-footer--header {margin-bottom: 10px; cursor: pointer; position: relative;} 195 | footer .crf-footer--header:after { 196 | content: "+"; 197 | display: block; 198 | font-weight: 200; 199 | font-size: 30px; 200 | line-height: 1; 201 | color: white; 202 | width: 30px; 203 | height: 30px; 204 | text-align: center; 205 | position: absolute; 206 | top: 50%; 207 | right: 0; 208 | transform: translateY(-50%); 209 | border: 1px solid white; 210 | border-radius: 100px; 211 | } 212 | 213 | footer .crf-footer--links.active .crf-footer--header:after {content: "";} 214 | footer .crf-footer--links.active .crf-footer--header:before {content: ""; width: 15px; height: 2px; background: white; transform: translate(-50%); position: absolute; top: 50%; right: 0;} 215 | 216 | footer .crf-footer--newsletter .crf-footer--header {cursor: default;} 217 | footer .crf-footer--newsletter .crf-footer--header:before, 218 | footer .crf-footer--newsletter .crf-footer--header:after, 219 | footer .crf-footer-m--social .crf-footer--header:before, 220 | footer .crf-footer-m--social .crf-footer--header:after {display: none;} 221 | 222 | footer .crf-footer-m--social .crf-footer--header {cursor: default;} 223 | footer .crf-footer-m--social ul {display: block; position: absolute; top: 0; right: 0;} 224 | footer .crf-footer-m--social:after {display: none;} 225 | 226 | footer .crf-footer--social {order: 0; padding-bottom: 10px;} 227 | footer .crf-footer--social:after {content: ""; display: block; height: 15px; background: white; margin: 40px -15px 0;} 228 | 229 | footer .crf-footer--logo .container {padding: 40px 10px;} 230 | } 231 | @media only screen and (max-width: 768px) { 232 | footer .crf-footer--category .row {flex-direction: column;} 233 | } 234 | 235 | /* MOBILE VIEW ORDER */ 236 | @media only screen and (max-width: 768px) { 237 | .crf {display: flex; flex-direction: column;} 238 | .crf-story {order: 6; margin-bottom: 50px;} 239 | .crf-full-banner {order: 5; margin-top: 0;} 240 | footer {order: 7;} 241 | } -------------------------------------------------------------------------------- /CarvedRock.WebApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahlsailrunner/logging-monitoring-aspnet6/a4f2c61e17d0d871fcae117fd0102d944a207ded/CarvedRock.WebApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /CarvedRock.WebApp/wwwroot/images/shutterstock_222721876.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahlsailrunner/logging-monitoring-aspnet6/a4f2c61e17d0d871fcae117fd0102d944a207ded/CarvedRock.WebApp/wwwroot/images/shutterstock_222721876.jpg -------------------------------------------------------------------------------- /CarvedRock.WebApp/wwwroot/images/shutterstock_475046062.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahlsailrunner/logging-monitoring-aspnet6/a4f2c61e17d0d871fcae117fd0102d944a207ded/CarvedRock.WebApp/wwwroot/images/shutterstock_475046062.jpg -------------------------------------------------------------------------------- /CarvedRock.WebApp/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /CarvedRock.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarvedRock.Api", "CarvedRock.Api\CarvedRock.Api.csproj", "{993233F6-F7B0-46E6-A0E1-65A1D9E77313}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarvedRock.Domain", "CarvedRock.Domain\CarvedRock.Domain.csproj", "{5245B8B2-5F50-4008-B094-525ECBA1504D}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarvedRock.Data", "CarvedRock.Data\CarvedRock.Data.csproj", "{C4342716-AFFF-46FC-9F46-14D5BE08EC80}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarvedRock.WebApp", "CarvedRock.WebApp\CarvedRock.WebApp.csproj", "{3E458087-1544-4D78-A1C2-90AF03EC93F5}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {993233F6-F7B0-46E6-A0E1-65A1D9E77313}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {993233F6-F7B0-46E6-A0E1-65A1D9E77313}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {993233F6-F7B0-46E6-A0E1-65A1D9E77313}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {993233F6-F7B0-46E6-A0E1-65A1D9E77313}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {5245B8B2-5F50-4008-B094-525ECBA1504D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {5245B8B2-5F50-4008-B094-525ECBA1504D}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {5245B8B2-5F50-4008-B094-525ECBA1504D}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {5245B8B2-5F50-4008-B094-525ECBA1504D}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {C4342716-AFFF-46FC-9F46-14D5BE08EC80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {C4342716-AFFF-46FC-9F46-14D5BE08EC80}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {C4342716-AFFF-46FC-9F46-14D5BE08EC80}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {C4342716-AFFF-46FC-9F46-14D5BE08EC80}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {3E458087-1544-4D78-A1C2-90AF03EC93F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {3E458087-1544-4D78-A1C2-90AF03EC93F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {3E458087-1544-4D78-A1C2-90AF03EC93F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {3E458087-1544-4D78-A1C2-90AF03EC93F5}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {FAE1B7C4-22B1-4A77-B281-DF647AE2900D} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Logging and Monitoring in ASP.NET Core 2 | 3 | > [!NOTE] 4 | > This repo has an [updated branch that targets .NET 8](https://github.com/dahlsailrunner/logging-monitoring-aspnet6/tree/aspnet8). 5 | 6 | This is the repository with the code associated with this Pluralsight course: [https://app.pluralsight.com/library/courses/logging-monitoring-aspdotnet-core-6](https://app.pluralsight.com/library/courses/logging-monitoring-aspdotnet-core-6) 7 | 8 | The course (and this repo) demonstrates logging and monitoring within ASP.NET Core 6, including the following 9 | features: 10 | 11 | * Injection and use of the `ILogger` interface 12 | * Default providers and configuration within ASP.NET Core 6 13 | * Log levels and their usage 14 | * Categories for log entries 15 | * Filtering log entries with both configuration and code 16 | * Exception Handling and logging 17 | * Scopes 18 | * Event Ids and how to use them 19 | * Techniques for hiding sensitive information 20 | * Semantic logging 21 | * Third party logging libraries (like Serilog and NLog) 22 | * Using third party libraries to write to various destinations (Splunk, Seq, Application Insights) 23 | * Using the `LoggerMessage` source generator 24 | * Health checks and things that monitor the health checks 25 | * Traceability with OpenTelemetry 26 | 27 | Repo URL: [https://github.com/dahlsailrunner/logging-monitoring-aspnet6](https://github.com/dahlsailrunner/logging-monitoring-aspnet6) 28 | 29 | ## VS Code Setup 30 | 31 | The `C#` extension is required to use this repo. I have some other settings that you may be curious about 32 | and they are described in my [VS Code gist](https://gist.github.com/dahlsailrunner/1765b807940e29951ea6bdfb36cd85dd). 33 | --------------------------------------------------------------------------------