Skip to content

Commit

Permalink
Add project files.
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidThielen committed Oct 4, 2024
1 parent 816c20c commit 44fa48d
Show file tree
Hide file tree
Showing 11 changed files with 565 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy ASP.Net Core app to Azure Web App - louishowe

on:
push:
branches:
- master
- feature/*
workflow_dispatch:

jobs:
build:
runs-on: windows-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v4

- name: Set up .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.x'
include-prerelease: true

- name: Build
run: dotnet build .\TradeWindsBlazor\TradeWindsBlazor.csproj --configuration Release


21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Trade Winds Studios (David Thielen)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<img align="left" width="200" src="server_client.png"/>

# TradeWindsBlazor

These are some classes I created for my Blazor application.

I will add to this when I identify additional components in my apps that might be of general use.

> This is under the MIT license. If you find this useful I ask (not a requirement) that you consider reading my book [I DON’T KNOW WHAT I’M DOING!: How a Programmer Became a Successful Startup CEO](https://a.co/d/bEpDlJR).
>
> And if you like it, please review it on Amazon and/or GoodReads. The number of legitimate reviews helps a lot. Much appreciated.
## ExComponentBase

ExComponentBase.cs is included as an example, not to be used directly. That's why it is `internal` instead of `public`. I do recommend you copy this over to your application, extend it, and use it instead of `ComponentBase` for your components.

To use the ScoppedLoggerEx, you must have the following in your component:

```csharp
[Inject]
private ScopedLoggerFactoryEx ScopedLoggerFactoryEx { get; set; } = default!;

protected ScopedLoggerEx LoggerEx { get; set; } = default!;

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

LoggerEx = await ScopedLoggerFactoryEx.GetLogger(GetType());
}
```

## ExPageBase

I also recommend you create the class ExPageBase, that is a subclass of ExComponentBase. Use this as your base class for any pages. In this class, add the following:

```csharp
protected override async Task OnInitializedAsync()
{

await base.OnInitializedAsync();

if (LoggerEx.IsEnabled(LogLevel.Trace))
LoggerEx.LogTrace($"Entering {GetType().Name}.OnInitializedAsync(), IsPreRender={IsPreRender}");
}
```

## ILogger

After you have added the above to your `ComponentBase`, you now have the member variable `LoggerEx` that is an `ILogger` for your component. And it is scoped to the component adding username and aspNetId as scopes for all logging by the logger.

## Inject everything

I strongly recommend that you add an `[Inject]` for every service you use in any component to your `ExComponentBase`. And the same for every `[CascadingParameter]`. Saves you a lot of copy/paste to each new component.
25 changes: 25 additions & 0 deletions TradeWindsBlazor.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35209.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TradeWindsBlazor", "TradeWindsBlazor\TradeWindsBlazor.csproj", "{C93C0E3F-955E-4873-8694-BF1ADBC9669B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C93C0E3F-955E-4873-8694-BF1ADBC9669B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C93C0E3F-955E-4873-8694-BF1ADBC9669B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C93C0E3F-955E-4873-8694-BF1ADBC9669B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C93C0E3F-955E-4873-8694-BF1ADBC9669B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {193FE3C6-D807-4FDD-AD44-3B999395A0B7}
EndGlobalSection
EndGlobal
90 changes: 90 additions & 0 deletions TradeWindsBlazor/ExComponentBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@

// Copyright (c) 2024 Trade Winds Studios (David Thielen)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using System.Security.Claims;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using TradeWindsBlazor.Extensions;
using TradeWindsBlazor.Loggers;

namespace TradeWindsBlazor
{
/// <summary>
/// The base class for any component other than a simple component.
/// </summary>
internal class ExComponentBase : ComponentBase
{
[Inject]
private ScopedLoggerFactoryEx ScopedLoggerFactoryEx { get; set; } = default!;

/// <summary>
/// This logger is for use in the methods in this base class and not for the subclass.
/// </summary>
[Inject]
private ILogger<ExComponentBase> Logger { get; set; } = default!;

[CascadingParameter]
protected Task<AuthenticationState> AuthenticationStateTask { get; set; } = default!;

/// <summary>
/// THe HttpContext for the current session (circuit). Only valid in OnInitializedAsync,
/// </summary>
[CascadingParameter]
private HttpContext? HttpContext { get; set; }

/// <summary>
/// Built from AuthenticationStateTask - the logged in Principal (user).
/// </summary>
protected ClaimsPrincipal Principal { get; set; } = ClaimsPrincipalExtensions.Anonymous;

/// <summary>
/// The logger for the subclass. This is for use in the subclass and not in this base class.
/// </summary>
protected ScopedLoggerEx LoggerEx { get; set; } = default!;

/// <summary>
/// true if this is the pre-render call to OnInitializedAsync. Do <b>not</b> call this anywhere other
/// than inside OnInitializedAsync!
/// </summary>
protected bool IsPreRender => HttpContext is not null;

/// <summary>
/// Returns the User-Agent from the request. This is only valid in OnInitializedAsync in the PreRender pass. Will return
/// null for other calls.
/// </summary>
protected string? RequestUserAgent => HttpContext?.Request.Headers["User-Agent"];

/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

// get the Principal
Principal = (await AuthenticationStateTask).User;

// set up the logger for this component. Passes GetType() so it is a logger for
// this object, not for the base class.
LoggerEx = await ScopedLoggerFactoryEx.GetLogger(GetType());
}
}
}
72 changes: 72 additions & 0 deletions TradeWindsBlazor/Extensions/ClaimsPrincipalExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

// Copyright (c) 2024 Trade Winds Studios (David Thielen)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using System.Security.Claims;

namespace TradeWindsBlazor.Extensions
{
public static class ClaimsPrincipalExtensions
{
/// <summary>
/// The name used for the anonymous user.
/// </summary>
public static string AnonymousName { get; } = "Anonymous";

/// <summary>
/// Returns the IdentityUser.Id of the passed in principal.
/// </summary>
/// <param name="principal">Normally the logged in user.</param>
/// <returns>The IdentityUser.Id of the passed in principal. null if the user is not logged in (anonymous).</returns>
public static string? UserId (this ClaimsPrincipal principal)
{
var claim = principal.FindFirst(u => u.Type.Contains("nameidentifier"));
return claim?.Value;
}

public static List<Claim> AppClaims(this ClaimsPrincipal principal)
{
var listClaims = new List<Claim>();
foreach (var claim in principal.Claims)
{
if (claim.Type.StartsWith("http") || claim.Type.StartsWith("AspNet"))
continue;
listClaims.Add(claim);
}

return listClaims;
}

/// <summary>
/// Returns true if this Principal is an anonymous user.
/// </summary>
/// <param name="principal">Normally the logged in user.</param>
/// <returns>true if this Principal is an anonymous user.</returns>
public static bool IsAnonymous(this ClaimsPrincipal principal)
{
return principal.Identity == null || principal.Identity.IsAuthenticated == false;
}

/// <summary>
/// An anonymous user.
/// </summary>
public static ClaimsPrincipal Anonymous => new(new ClaimsIdentity());
}
}
67 changes: 67 additions & 0 deletions TradeWindsBlazor/Loggers/ScopedLoggerEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

// Copyright (c) 2024 Trade Winds Studios (David Thielen)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using Microsoft.Extensions.Logging;

namespace TradeWindsBlazor.Loggers
{

/// <summary>
/// An ILogger where each Log() call will be in a scope with the user information.
/// </summary>
/// <typeparam name="T">The class the logger is going to log in.</typeparam>
public class ScopedLoggerEx : ILogger
{

private readonly ILogger _logger;
private readonly string? _aspNetId;
private readonly string? _username;

public ScopedLoggerEx(ILogger logger, string? aspNetId, string? username)
{
_logger = logger;
_aspNetId = aspNetId;
_username = username;
}

/// <inheritdoc />
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func<TState, Exception?, string> formatter)
{
using (_logger.BeginScope("User:{username}, {aspNetId}", _username, _aspNetId))
{
_logger.Log(logLevel, eventId, state, exception, formatter);
}
}

/// <inheritdoc />
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
{
return _logger.BeginScope(state);
}

/// <inheritdoc />
public bool IsEnabled(LogLevel logLevel)
{
return _logger.IsEnabled(logLevel);
}
}
}
Loading

0 comments on commit 44fa48d

Please sign in to comment.