Browse Source

Add SignalR Action (#743)

* Add SignalR Azure management package

* Add Action SignalR

* Update DataTypeAttribute

* Update Enum Uppercase

* Add space

* Update using Dispose

* Move initialisation list Users/Groups in CreateJobAsync

* Add Header

* Test Markdown Text

* Remove commande Users/Groups, dynamic by separator \n

* clean code

* ActionType rename to Action, technical reservation

* Add contrôle List empty

* Delete markdown Description

* Reduce code and parameters

Co-authored-by: Julien Mougneau <julien.mougneau@ydeho.fr>
pull/747/head
Julien M 4 years ago
committed by GitHub
parent
commit
34a7b4cb1b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 76
      backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRAction.cs
  2. 119
      backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRActionHandler.cs
  3. 21
      backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRPlugin.cs
  4. 1
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

76
backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRAction.cs

@ -0,0 +1,76 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure.Validation;
namespace Squidex.Extensions.Actions.SignalR
{
[RuleAction(
Title = "Azure SignalR",
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><path d='M.011 16L0 6.248l12-1.63V16zM14 4.328L29.996 2v14H14zM30 18l-.004 14L14 29.75V18zM12 29.495L.01 27.851.009 18H12z'/></svg>",
IconColor = "#1566BF",
Display = "Send to Azure SignalR",
Description = "Send an message to azure SignalR.",
ReadMore = "https://azure.microsoft.com/fr-fr/services/signalr-service/")]
public sealed record SignalRAction : RuleAction
{
[LocalizedRequired]
[Display(Name = "Connection", Description = "The connection string to the Signal R Azure.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string ConnectionString { get; set; }
[LocalizedRequired]
[Display(Name = "Hub Name", Description = "The name of the hub.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string HubName { get; set; }
[LocalizedRequired]
[Display(Name = "Action", Description = "Broadcast = send to all User, User = send to specific user(s) specified in the 'Target' field, Group = send to specific group(s) specified in the 'Target' field")]
public ActionTypeEnum Action { get; set; }
[Display(Name = "Methode Name", Description = "Set the Name of the hub method received by the customer, default value 'push.")]
[Editor(RuleFieldEditor.Text)]
public string MethodName { get; set; }
[Display(Name = "Target (Optional)", Description = "Defines a user, group or target list by an id or name. For a list, define one value per line. Not necessary with the 'Broadcast' action")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Target { get; set; }
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Payload { get; set; }
protected override IEnumerable<ValidationError> CustomValidate()
{
if (!string.IsNullOrWhiteSpace(HubName) && !Regex.IsMatch(HubName, "^[a-z][a-z0-9]{2,}(\\-[a-z0-9]+)*$"))
{
yield return new ValidationError("Hub must be valid azure hub name.", nameof(HubName));
}
if ((Action == ActionTypeEnum.User || Action == ActionTypeEnum.Group) && string.IsNullOrWhiteSpace(Target))
{
yield return new ValidationError("Target must be specified with 'User' or 'Group' Action.", nameof(HubName));
}
}
}
public enum ActionTypeEnum
{
Broadcast,
User,
Group
}
}

119
backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRActionHandler.cs

@ -0,0 +1,119 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Azure.SignalR.Management;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
namespace Squidex.Extensions.Actions.SignalR
{
public sealed class SignalRActionHandler : RuleActionHandler<SignalRAction, SignalRJob>
{
private readonly ClientPool<(string ConnectionString, string HubName), IServiceManager> clients;
public SignalRActionHandler(RuleEventFormatter formatter)
: base(formatter)
{
clients = new ClientPool<(string ConnectionString, string HubName), IServiceManager>(key =>
{
var serviceManager = new ServiceManagerBuilder()
.WithOptions(option =>
{
option.ConnectionString = key.ConnectionString;
option.ServiceTransportType = ServiceTransportType.Transient;
})
.Build();
return serviceManager;
});
}
protected override async Task<(string Description, SignalRJob Data)> CreateJobAsync(EnrichedEvent @event, SignalRAction action)
{
var hubName = await FormatAsync(action.HubName, @event);
string requestBody;
if (!string.IsNullOrWhiteSpace(action.Payload))
{
requestBody = await FormatAsync(action.Payload, @event);
}
else
{
requestBody = ToEnvelopeJson(@event);
}
string[] targetArray = new string[0];
string target = string.Empty;
target = await FormatAsync(action.Target, @event);
if (!string.IsNullOrEmpty(target))
{
targetArray = target.Split('\n');
}
var ruleDescription = $"Send SignalRJob to signalR hub '{hubName}'";
var ruleJob = new SignalRJob
{
ConnectionString = action.ConnectionString,
HubName = hubName,
Action = action.Action,
MethodName = action.MethodName,
Target = target,
TargetArray = targetArray,
Payload = requestBody
};
return (ruleDescription, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(SignalRJob job, CancellationToken ct = default)
{
var signalR = await clients.GetClientAsync((job.ConnectionString, job.HubName));
await using (var signalRContext = await signalR.CreateHubContextAsync(job.HubName))
{
var methodeName = !string.IsNullOrWhiteSpace(job.MethodName) ? job.MethodName : "push";
switch (job.Action)
{
case ActionTypeEnum.Broadcast:
await signalRContext.Clients.All.SendAsync(methodeName, job.Payload);
break;
case ActionTypeEnum.User:
await signalRContext.Clients.Users(job.TargetArray).SendAsync(methodeName, job.Payload);
break;
case ActionTypeEnum.Group:
await signalRContext.Clients.Groups(job.TargetArray).SendAsync(methodeName, job.Payload);
break;
}
}
return Result.Complete();
}
}
public sealed class SignalRJob
{
public string ConnectionString { get; set; }
public string HubName { get; set; }
public ActionTypeEnum Action { get; set; }
public string MethodName { get; set; }
public string Target { get; set; }
public string[] TargetArray { get; set; }
public string Payload { get; set; }
}
}

21
backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRPlugin.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure.Plugins;
namespace Squidex.Extensions.Actions.SignalR
{
public sealed class SignalRPlugin : IPlugin
{
public void ConfigureServices(IServiceCollection services, IConfiguration config)
{
services.AddRuleAction<SignalRAction, SignalRActionHandler>();
}
}
}

1
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -16,6 +16,7 @@
<PackageReference Include="Datadog.Trace" Version="1.27.1" /> <PackageReference Include="Datadog.Trace" Version="1.27.1" />
<PackageReference Include="Elasticsearch.Net" Version="7.13.2" /> <PackageReference Include="Elasticsearch.Net" Version="7.13.2" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.17.0" /> <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.17.0" />
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.9.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.9.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.9.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />

Loading…
Cancel
Save