Skip to content

Commit

Permalink
Standardize configuration & Update docs (#1)
Browse files Browse the repository at this point in the history
# Motivations
Standardized configuration

# Modifications
- Introduce `INuiBuilder` for use with `AddNuiServices` to configure an
internal static of "options"
- Simplify manual publishing CI
- Update readme
  • Loading branch information
Twinki14 authored Dec 28, 2023
1 parent 53fb085 commit 7e9f371
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 46 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ concurrency:
env:
is-default-branch: ${{ github.ref_name == github.event.repository.default_branch }}
is-tag: ${{ startsWith(github.ref, 'refs/tags/') }}
is-manual: ${{ github.event_name == 'workflow_dispatch' }}
do-manual-publish: ${{ github.event_name == 'workflow_dispatch' && inputs.publish == true }}

jobs:
build:
Expand Down Expand Up @@ -67,5 +67,5 @@ jobs:
run: dotnet nuget push **/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate

- name: Publish - GitHub
if: ${{ env.is-default-branch == 'true' || env.is-tag == 'true' || (env.is-manual == 'true' && inputs.publish == true) }}
if: ${{ env.is-default-branch == 'true' || env.is-tag == 'true' || env.do-manual-publish == 'true' }}
run: dotnet nuget push **/*.nupkg --skip-duplicate
26 changes: 17 additions & 9 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ An unofficial set of extensions for developing Nui interfaces with Blazor WASM i
[![Downloads](https://img.shields.io/nuget/dt/CitizenFX.Extensions.Blazor.WebAssembly?style=flat-square)](https://www.nuget.org/packages/CitizenFX.Extensions.Blazor.WebAssembly)
[![GitHub release](https://img.shields.io/github/v/release/Twinki14/CitizenFX.Extensions.Blazor.WebAssembly?style=flat-square)](https://github.com/Twinki14/CitizenFX.Extensions.Blazor.WebAssembly/releases)
[![Nuget](https://img.shields.io/nuget/v/CitizenFX.Extensions.Blazor.WebAssembly?style=flat-square)](https://www.nuget.org/packages/CitizenFX.Extensions.Blazor.WebAssembly)
[![cfx.re](https://img.shields.io/badge/cfx.re-link-orange)](https://forum.cfx.re/t/c-cfx-extensions-blazor-webassembly/5196202)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Twinki14/CitizenFX.Extensions.Blazor.WebAssembly/build-publish.yaml?style=flat-square)](https://github.com/Twinki14/CitizenFX.Extensions.Blazor.WebAssembly/actions/workflows/build-publish.yaml)

## Features
Expand All @@ -13,26 +14,33 @@ An unofficial set of extensions for developing Nui interfaces with Blazor WASM i
- Service-based Nui Callback triggering
- `@inject INuiCallbackService`
- `await NuiCallbackService.TriggerNuiCallbackAsync("getItemInfo", new { item = "phone" });`
- `System.Text.Json` for JSON Serialization & Deserialization

## Getting started
- For Nui Message handling, the [NuiMessageListener](src/CitizenFX.Extensions.Blazor.WebAssembly/NuiMessageListener.cs) must be injected as a root-component in index.html, and your component(s) must inherit [NuiComponent](src/CitizenFX.Extensions.Blazor.WebAssembly/NuiComponent.cs)
- Add `<template id="nui-message-listener"></template>` to your `index.html` in the `<body>`
- Add `builder.RootComponents.Add<NuiMessageListener>("#nui-message-listener");` in your `Program.cs`
- This adds some Javascript to your `<body>` that directs any Nui Messages for the resource to `NuiMessageListener`
- Add `@inherits NuiComponent` in your component
- Add `[NuiMessageHandler("<type-name>")]` to any static or instanced method in your component
- Add `@inherits NuiComponent` in your component/razor page
- Add `[NuiMessageHandler("<identifier>")]` to any static or instanced method in your component


- For triggering Nui Callbacks, [NuiCallbackService](src/CitizenFX.Extensions.Blazor.WebAssembly/Services/NuiCallbackService.cs) must be added to your service collection
- Add `builder.Services.AddNuiServices();` in your `Program.cs`
- Inject in your page with `@inject INuiCallbackService NuiCallbackService`

## Notes
- `NuiMessageListener` & `NuiMessageHandler` requires/uses a specific string field in the sending-message to determine which method to invoke
- Currently that field is `type`, but this may change in the future to be configurable
- When `NuiMessageListener` recieves an Nui message, `NuiMessageListener` will look for all `NuiMessageHandler` attributed methods constructed with the matching `type` name found in the recieved Nui message, and then attempt to deserialize any json fields specified in the `NuiMessageHandler` attributed method paramters
- Currently, you may only have one `NuiMessageListener` per type-string
- `NuiMessageListener` and `NuiCallbackService` uses System.Text.Json to serialize & deserialize json in the library
- You can configure the JsonSerializerOptions the message handler and the callback service uses in `AddNuiServices();`
- `builder.Services.AddNuiServices(options => { options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); });`
- The field can be configured in `builder.Services.AddNuiServices();`
- When `NuiMessageListener` receives an Nui message, it checks for `[NuiMessageHandler]` attributed methods with a matching identifier name in the received message
- Currently, you may only have one `NuiMessageListener` per identifier-string
- Configure the internal `JsonSerializerOptions` and the identifier-string in `builder.Services.AddNuiServices();`
```csharp
builder.Services.AddNuiServices(options =>
{
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
options.MessageHandlerIdentifierField = "id";
});
```

## Example - `NuiMessageHandler`
`client.lua`
Expand Down
6 changes: 6 additions & 0 deletions src/CitizenFX.Extensions.Blazor.WebAssembly.sln
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CitizenFX.Extensions.Blazor.WebAssembly", "CitizenFX.Extensions.Blazor.WebAssembly\CitizenFX.Extensions.Blazor.WebAssembly.csproj", "{6A15498C-C63B-416E-9D5B-E4205396EE3F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B9BD26B7-E5EE-45EA-A9F0-CEE2F41D1756}"
ProjectSection(SolutionItems) = preProject
..\readme.md = ..\readme.md
..\LICENSE = ..\LICENSE
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
16 changes: 16 additions & 0 deletions src/CitizenFX.Extensions.Blazor.WebAssembly/INuiBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text.Json;

namespace CitizenFX.Extensions.Blazor.WebAssembly;

public interface INuiBuilder
{
/// <summary>
/// The <see cref="System.Text.Json.JsonSerializerOptions"/> used by <see cref="NuiMessageHandler"/> and <see cref="Services.NuiCallbackService"/> for JSON serialization/deserialization
/// </summary>
public JsonSerializerOptions JsonSerializerOptions { get; }

/// <summary>
/// The identifier field for <see cref="NuiMessageHandler"/> to determine which <see cref="NuiMessageHandler"/> to trigger by <see cref="NuiMessageListener"/>
/// </summary>
public string MessageHandlerIdentifierField { get; set; }
}
6 changes: 6 additions & 0 deletions src/CitizenFX.Extensions.Blazor.WebAssembly/Internal/Nui.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace CitizenFX.Extensions.Blazor.WebAssembly.Internal;

internal sealed class Nui
{
internal static NuiBuilder Options = new();
}
12 changes: 12 additions & 0 deletions src/CitizenFX.Extensions.Blazor.WebAssembly/Internal/NuiBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json;

namespace CitizenFX.Extensions.Blazor.WebAssembly.Internal;

internal sealed class NuiBuilder : INuiBuilder
{
/// <inheritdoc />
public JsonSerializerOptions JsonSerializerOptions { get; } = new();

/// <inheritdoc />
public string MessageHandlerIdentifierField { get; set; } = "type";
}
4 changes: 2 additions & 2 deletions src/CitizenFX.Extensions.Blazor.WebAssembly/NuiComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ private static List<MessageHandlerMethod> FindMessageHandlerMethods()
.Select(m =>
{
var attribute = (NuiMessageHandler) Attribute.GetCustomAttribute(m, typeof(NuiMessageHandler))!;
return new MessageHandlerMethod(m, instance, attribute.Type);
return new MessageHandlerMethod(m, instance, attribute.Identifier);
})
.ToList();

Expand All @@ -83,7 +83,7 @@ private static List<MessageHandlerMethod> FindMessageHandlerMethods()
.Select(method =>
{
var attribute = (NuiMessageHandler) Attribute.GetCustomAttribute(method, typeof(NuiMessageHandler))!;
return new MessageHandlerMethod(method, null, attribute.Type);
return new MessageHandlerMethod(method, null, attribute.Identifier);
}))
.ToList();

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/// <summary>
/// Any method with this attribute will be considered when handling Nui Messages.
/// <see cref="NuiMessageListener"/> uses this attribute to determine which method the Nui Message belongs to, using the <see cref="Identifier"/> property added in the Nui Message.
/// <see cref="NuiMessageListener"/> uses this attribute to determine which method the Nui Message belongs to, using the <see cref="INuiBuilder.MessageHandlerIdentifierField"/> property added in the Nui Message.
/// </summary>
/// <example>
/// <code>
Expand All @@ -19,10 +19,9 @@
/// }
/// </code>
/// </example>
/// <param name="type">The type or identifier of a Nui Message, typically sent with SendNuiMessage, this is used to determine which method the Nui Message should go to. For example, type = "showui:message"</param>
/// <param name="identifier">The type or identifier of a Nui Message, typically sent with SendNuiMessage, this is used to determine which method the Nui Message should go to. For example, type = "showui:message"</param>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class NuiMessageHandler(string type) : Attribute
public class NuiMessageHandler(string identifier) : Attribute
{
public readonly string Type = type;
public const string Identifier = "type";
public readonly string Identifier = identifier;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using CitizenFX.Extensions.Blazor.WebAssembly.Internal;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -62,9 +63,9 @@ public static async Task OnNuiMessage(JsonDocument eventData)
{
var methods = await NuiComponent.GetMessageHandlerMethods();

if (!eventData.RootElement.TryGetProperty(NuiMessageHandler.Identifier, out var identifierValue))
if (!eventData.RootElement.TryGetProperty(Nui.Options.MessageHandlerIdentifierField, out var identifierValue))
{
// log debug here
_logger!.LogDebug("Identifier not found in the received message {IdentifierValue}", identifierValue);
return;
}

Expand Down Expand Up @@ -92,7 +93,7 @@ public static async Task OnNuiMessage(JsonDocument eventData)

if (eventData.RootElement.TryGetProperty(param.Name, out var element))
{
var deserialized = element.Deserialize(param.ParameterType, NuiJsonSerializerOptions.Options);
var deserialized = element.Deserialize(param.ParameterType, Nui.Options.JsonSerializerOptions);
if (deserialized is not null)
{
methodValues.Add(deserialized);
Expand All @@ -115,7 +116,6 @@ public static async Task OnNuiMessage(JsonDocument eventData)
}
}

// log debug here
_logger!.LogDebug("Attempting to invoke method {MethodName} with {NumberOfParameters} parameters", identifiedMethod.Info.Name, methodValues.Count);

await InvokeAsync(identifiedMethod.Info, identifiedMethod.Instance, methodValues.ToArray());
Expand All @@ -142,6 +142,6 @@ private static async ValueTask InvokeAsync(MethodInfo info, object? instance, ob
[LoggerMessage(1, LogLevel.Critical, "Critical exception in {caller} when attempting to bind to a handler method with a parameter name {parameterName} and parameter type {parameterType}", EventName = "Handler method parameter JSON binding")]
static partial void LogCriticalJsonBinding(ILogger logger, Exception ex, string? parameterName, Type parameterType, [CallerMemberName] string caller = nameof(NuiMessageListener));

[LoggerMessage(2, LogLevel.Critical, "Parameter not found in the handler when attempting to bind with a parameter name {parameterName} and parameter type {parameterType}", EventName = "Handler method parameter discovery")]
[LoggerMessage(2, LogLevel.Critical, "Parameter not found in {caller} when attempting to bind with a parameter name {parameterName} and parameter type {parameterType}", EventName = "Handler method parameter discovery")]
static partial void LogErrorPropertyNotFound(ILogger logger, string? parameterName, Type parameterType, [CallerMemberName] string caller = nameof(NuiMessageListener));
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Text.Json;
using CitizenFX.Extensions.Blazor.WebAssembly.Internal;
using CitizenFX.Extensions.Blazor.WebAssembly.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand All @@ -8,15 +8,26 @@ namespace CitizenFX.Extensions.Blazor.WebAssembly;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds Nui related services to the service collection.
/// Adds Nui related services to the collection
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configureNuiJsonSerializerOptions">Action to configure the <see cref="NuiJsonSerializerOptions"/> used in <see cref="NuiCallbackService"/> and <see cref="NuiMessageListener"/> for serializing & deserializing JSON</param>
public static IServiceCollection AddNuiServices(
this IServiceCollection services,
Action<JsonSerializerOptions>? configureNuiJsonSerializerOptions = null)
/// <param name="services">The extended service collection</param>
/// <returns>The service collection</returns>
public static IServiceCollection AddNuiServices(this IServiceCollection services)
{
configureNuiJsonSerializerOptions?.Invoke(NuiJsonSerializerOptions.Options);
services.TryAddSingleton<INuiCallbackService, NuiCallbackService>();

return services;
}

/// <summary>
/// Adds Nui related services to the collection
/// </summary>
/// <param name="services">The extended service collection</param>
/// <param name="configure">Action to configure Nui-specific options <see cref="INuiBuilder"/></param>
/// <returns>The service collection</returns>
public static IServiceCollection AddNuiServices(this IServiceCollection services, Action<INuiBuilder> configure)
{
configure(Nui.Options);

services.TryAddSingleton<INuiCallbackService, NuiCallbackService>();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http.Json;
using CitizenFX.Extensions.Blazor.WebAssembly.Internal;
using Microsoft.JSInterop;

namespace CitizenFX.Extensions.Blazor.WebAssembly.Services;
Expand All @@ -18,7 +19,7 @@ public async ValueTask<HttpResponseMessage> TriggerNuiCallbackAsync<T>(string ca
{
httpClient.BaseAddress = new Uri($"https://{resourceName}/");

return await httpClient.PostAsJsonAsync($"{callback}", value, NuiJsonSerializerOptions.Options, cancellationToken);
return await httpClient.PostAsJsonAsync($"{callback}", value, Nui.Options.JsonSerializerOptions, cancellationToken);
}
}

Expand Down

0 comments on commit 7e9f371

Please sign in to comment.