Skip to content

Commit

Permalink
feat: Add Atc.Blazor base package and added some extensions methods f…
Browse files Browse the repository at this point in the history
…or handling QueryString parameters / bindings
  • Loading branch information
davidkallesen committed Mar 31, 2022
1 parent 6a06b16 commit 9c7d2eb
Show file tree
Hide file tree
Showing 13 changed files with 391 additions and 6 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/post-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ jobs:
run: dotnet pack -c Release --no-restore -o ${GITHUB_WORKSPACE}/packages -p:RepositoryBranch=$BRANCH_NAME

- name: 📦 Push packages to GitHub Package Registry
run: dotnet nuget push ${GITHUB_WORKSPACE}/packages/'Atc.Blazor.ColorThemePreference.'${NBGV_NuGetPackageVersion}'.nupkg' -k ${{ secrets.GITHUB_TOKEN }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate
run: |
dotnet nuget push ${GITHUB_WORKSPACE}/packages/'Atc.Blazor.'${NBGV_NuGetPackageVersion}'.nupkg' -k ${{ secrets.GITHUB_TOKEN }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate
dotnet nuget push ${GITHUB_WORKSPACE}/packages/'Atc.Blazor.ColorThemePreference.'${NBGV_NuGetPackageVersion}'.nupkg' -k ${{ secrets.GITHUB_TOKEN }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ jobs:
run: dotnet pack -c Release --no-restore -o ${GITHUB_WORKSPACE}/packages -p:RepositoryBranch=$BRANCH_NAME /p:PublicRelease=true

- name: 📦 Push packages to NuGet
run: dotnet nuget push ${GITHUB_WORKSPACE}/packages/'Atc.Blazor.ColorThemePreference.'${NBGV_NuGetPackageVersion}'.nupkg' -k ${{ secrets.NUGET_KEY }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate --no-symbols
run: |
dotnet nuget push ${GITHUB_WORKSPACE}/packages/'Atc.Blazor.'${NBGV_NuGetPackageVersion}'.nupkg' -k ${{ secrets.NUGET_KEY }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate --no-symbols
dotnet nuget push ${GITHUB_WORKSPACE}/packages/'Atc.Blazor.ColorThemePreference.'${NBGV_NuGetPackageVersion}'.nupkg' -k ${{ secrets.NUGET_KEY }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate --no-symbols
24 changes: 24 additions & 0 deletions Atc.Blazor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{DCB86DDD-7
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atc.Blazor", "src\Atc.Blazor\Atc.Blazor.csproj", "{7CE1DD6F-7339-44E1-B037-1941A8387099}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{A0E0CD23-AD47-44BB-A056-A76EE3EA64BC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sample", "Sample", "{5FDCB20D-0C42-4CBA-AA3A-317A6B9E2760}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{283A2EC4-BDB8-45BC-81A0-362B59D7A0AE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atc.Blazor.Tests", "test\Atc.Blazor.Tests\Atc.Blazor.Tests.csproj", "{B9EE0DDA-761C-401E-AC1D-B641D772FFA3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -26,10 +36,24 @@ Global
{3875CEE1-DBED-4999-B1F6-F5D45E14BEF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3875CEE1-DBED-4999-B1F6-F5D45E14BEF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3875CEE1-DBED-4999-B1F6-F5D45E14BEF6}.Release|Any CPU.Build.0 = Release|Any CPU
{7CE1DD6F-7339-44E1-B037-1941A8387099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CE1DD6F-7339-44E1-B037-1941A8387099}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CE1DD6F-7339-44E1-B037-1941A8387099}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CE1DD6F-7339-44E1-B037-1941A8387099}.Release|Any CPU.Build.0 = Release|Any CPU
{B9EE0DDA-761C-401E-AC1D-B641D772FFA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9EE0DDA-761C-401E-AC1D-B641D772FFA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9EE0DDA-761C-401E-AC1D-B641D772FFA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9EE0DDA-761C-401E-AC1D-B641D772FFA3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{97E12D4B-3492-4336-B851-22202BDD327B} = {A0E0CD23-AD47-44BB-A056-A76EE3EA64BC}
{3875CEE1-DBED-4999-B1F6-F5D45E14BEF6} = {5FDCB20D-0C42-4CBA-AA3A-317A6B9E2760}
{7CE1DD6F-7339-44E1-B037-1941A8387099} = {A0E0CD23-AD47-44BB-A056-A76EE3EA64BC}
{B9EE0DDA-761C-401E-AC1D-B641D772FFA3} = {283A2EC4-BDB8-45BC-81A0-362B59D7A0AE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A21A02DE-E75C-4D9F-9B76-012E6FFC5EF0}
EndGlobalSection
Expand Down
85 changes: 81 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,90 @@ This repository contains packages with components for Blazor application:
|---|---|
| Atc.Blazor.ColorThemePreference | A library for detecting the user preferred color theme |

## Get started Atc.Blazor.ColorThemePreference

### Requirements
## Requirements

* [.NET 6 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)

### Installation
## Get started Atc.Blazor

### Installation for Atc.Blazor

```powershell
Install-Package Atc.Blazor
```

### How to Use `NavigationManager.TryGetQueryString`

```csharp
int myInt = 0;

NavigationManager.TryGetQueryString<int>("myKey", out var myInt)
```

### How to Use `QueryStringParameterAttribute` and `SetPropertiesWithDecoratedQueryStringParameterFromQueryString`

```csharp
@page "/"
@inject NavigationManager NavigationManager

<div>My age is: @Age</div>

@code
{
[QueryStringParameter]
public int Age { get; set; }

public override Task SetParametersAsync(ParameterView parameters) // Overload from Blazor components lifecycle
{
this.SetPropertiesWithDecoratedQueryStringParameterFromQueryString(NavigationManager); // Bind from url-qyery-parameter 'age' to property 'Age'
return base.SetParametersAsync(parameters);
}
}
```

```csharp
@page "/"
@inject NavigationManager NavigationManager

<div>My age is: @Age</div>

@code
{
[QueryStringParameter("myAge")]
public int Age { get; set; }

public override Task SetParametersAsync(ParameterView parameters) // Overload from Blazor components lifecycle
{
this.SetPropertiesWithDecoratedQueryStringParameterFromQueryString(NavigationManager); // Bind from url-qyery-parameter 'myAge' to property 'Age'
return base.SetParametersAsync(parameters);
}
}
```

### How to Use `QueryStringParameterAttribute` and `UpdateQueryStringFromPropertiesWithDecoratedQueryStringParameter`

```csharp
@page "/"
@inject NavigationManager NavigationManager

<button type="button" @onclick="UpdateQueryStringWithAge(21)">Update url</button>

@code
{
[QueryStringParameter]
public int Age { get; set; }

public void UpdateQueryStringWithAge(int age)
{
this.Age = age;
this.UpdateQueryString(NavigationManager);
}
}
```

## Get started Atc.Blazor.ColorThemePreference

### Installation for Atc.Blazor.ColorThemePreference

```powershell
Install-Package Atc.Blazor.ColorThemePreference
Expand Down
16 changes: 16 additions & 0 deletions src/Atc.Blazor/Atc.Blazor.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<PackageId>Atc.Blazor</PackageId>
<PackageTags>blazor</PackageTags>
<Description>Atc.Blazor is a collection of classes and extension methods for common functionality.</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Atc" Version="2.0.108" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="6.0.2" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
</ItemGroup>

</Project>
21 changes: 21 additions & 0 deletions src/Atc.Blazor/Attributes/QueryStringParameterAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// ReSharper disable once CheckNamespace
namespace Atc;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class QueryStringParameterAttribute : Attribute
{
public QueryStringParameterAttribute()
{
Name = string.Empty;
}

public QueryStringParameterAttribute(string name)
{
Name = name;
}

/// <summary>
/// Name of the query string parameter. It uses the property name by default.
/// </summary>
public string Name { get; }
}
96 changes: 96 additions & 0 deletions src/Atc.Blazor/Extensions/ComponentBaseExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// ReSharper disable once CheckNamespace
// ReSharper disable ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
// ReSharper disable SuggestBaseTypeForParameter
namespace Atc;

public static class ComponentBaseExtensions
{
public static void SetPropertiesWithDecoratedQueryStringParameterFromQueryString<T>(this T component, NavigationManager navigationManager)
where T : ComponentBase
{
ArgumentNullException.ThrowIfNull(navigationManager);

if (!Uri.TryCreate(navigationManager.Uri, UriKind.RelativeOrAbsolute, out var uri))
{
throw new InvalidOperationException("The current url is not a valid URI. Url: " + navigationManager.Uri);
}

var queryString = QueryHelpers.ParseQuery(uri.Query);
foreach (var property in GetPublicAndNonPublicProperties<T>())
{
var parameterName = GetQueryStringParameterName(property);
if (parameterName is null)
{
continue;
}

if (!queryString.TryGetValue(parameterName, out var value))
{
continue;
}

var convertedValue = Convert.ChangeType(value[0], property.PropertyType, CultureInfo.InvariantCulture);
property.SetValue(component, convertedValue);
}
}

public static void UpdateQueryStringFromPropertiesWithDecoratedQueryStringParameter<T>(this T component, NavigationManager navigationManager)
where T : ComponentBase
{
ArgumentNullException.ThrowIfNull(navigationManager);

if (!Uri.TryCreate(navigationManager.Uri, UriKind.RelativeOrAbsolute, out var uri))
{
throw new InvalidOperationException("The current url is not a valid URI. Url: " + navigationManager.Uri);
}

var parameters = QueryHelpers.ParseQuery(uri.Query);
foreach (var property in GetPublicAndNonPublicProperties<T>())
{
var parameterName = GetQueryStringParameterName(property);
if (parameterName is null)
{
continue;
}

var value = property.GetValue(component);
if (value is null)
{
parameters.Remove(parameterName);
}
else
{
var convertedValue = Convert.ToString(value, CultureInfo.InvariantCulture);
parameters[parameterName] = convertedValue;
}
}

var newUri = uri.GetComponents(UriComponents.Scheme | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
foreach (var (key, stringValues) in parameters)
{
foreach (var value in stringValues)
{
newUri = QueryHelpers.AddQueryString(newUri, key, value);
}
}

navigationManager.NavigateTo(newUri);
}

[SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "OK - By design.")]
private static IEnumerable<PropertyInfo> GetPublicAndNonPublicProperties<T>()
=> typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

private static string? GetQueryStringParameterName(PropertyInfo propertyInfo)
{
var attribute = propertyInfo.GetCustomAttribute<QueryStringParameterAttribute>();
if (attribute is null)
{
return null;
}

return !string.IsNullOrEmpty(attribute.Name)
? attribute.Name
: propertyInfo.Name;
}
}
59 changes: 59 additions & 0 deletions src/Atc.Blazor/Extensions/NavigationManagerExtendedExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// ReSharper disable once CheckNamespace
// ReSharper disable ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
namespace Atc;

public static class NavigationManagerExtendedExtensions
{
public static bool TryGetQueryString<T>(this NavigationManager navigationManager, string key, out T value)
{
ArgumentNullException.ThrowIfNull(navigationManager);

var uri = navigationManager.ToAbsoluteUri(navigationManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue(key, out var valueFromQueryString))
{
if (typeof(T) == typeof(bool) &&
bool.TryParse(valueFromQueryString, out var valueAsBool))
{
value = (T)(object)valueAsBool;
return true;
}

if (typeof(T) == typeof(decimal) &&
decimal.TryParse(valueFromQueryString, NumberStyles.Any, CultureInfo.InvariantCulture, out var valueAsDecimal))
{
value = (T)(object)valueAsDecimal;
return true;
}

if (typeof(T) == typeof(double) &&
double.TryParse(valueFromQueryString, NumberStyles.Any, CultureInfo.InvariantCulture, out var valueAsDouble))
{
value = (T)(object)valueAsDouble;
return true;
}

if (typeof(T) == typeof(int) &&
int.TryParse(valueFromQueryString, NumberStyles.Any, CultureInfo.InvariantCulture, out var valueAsInt))
{
value = (T)(object)valueAsInt;
return true;
}

if (typeof(T) == typeof(Guid) &&
Guid.TryParse(valueFromQueryString, out var valueAsGuid))
{
value = (T)(object)valueAsGuid;
return true;
}

if (typeof(T) == typeof(string))
{
value = (T)(object)valueFromQueryString.ToString();
return true;
}
}

value = default!;
return false;
}
}
5 changes: 5 additions & 0 deletions src/Atc.Blazor/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
global using System.Diagnostics.CodeAnalysis;
global using System.Globalization;
global using System.Reflection;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.WebUtilities;
28 changes: 28 additions & 0 deletions test/Atc.Blazor.Tests/Atc.Blazor.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Atc.Blazor\Atc.Blazor.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit 9c7d2eb

Please sign in to comment.