Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ISmsService and support multiple SMS Providers #14774

Merged
merged 16 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/OrchardCore.Build/Dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<PackageManagement Include="Markdig" Version="0.34.0" />
<PackageManagement Include="MessagePack" Version="2.2.60" />
<PackageManagement Include="Microsoft.Extensions.Azure" Version="1.7.1" />
<PackageManagement Include="Microsoft.Extensions.Http.Resilience" Version="8.1.0" />
<PackageManagement Include="Microsoft.Identity.Web" Version="2.16.1" />
<PackageManagement Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageManagement Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
Expand Down
12 changes: 4 additions & 8 deletions src/OrchardCore.Modules/OrchardCore.Sms/Activities/SmsTask.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using OrchardCore.Workflows.Abstractions.Models;
Expand All @@ -11,21 +10,18 @@ namespace OrchardCore.Sms.Activities;

public class SmsTask : TaskActivity<SmsTask>
{
private readonly ISmsProvider _smsProvider;
private readonly ISmsService _smsService;
private readonly IWorkflowExpressionEvaluator _expressionEvaluator;
private readonly HtmlEncoder _htmlEncoder;
protected readonly IStringLocalizer S;

public SmsTask(
ISmsProvider smsProvider,
ISmsService smsService,
IWorkflowExpressionEvaluator expressionEvaluator,
HtmlEncoder htmlEncoder,
IStringLocalizer<SmsTask> stringLocalizer
)
{
_smsProvider = smsProvider;
_smsService = smsService;
_expressionEvaluator = expressionEvaluator;
_htmlEncoder = htmlEncoder;
S = stringLocalizer;
}

Expand Down Expand Up @@ -58,7 +54,7 @@ public override async Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecuti
Body = await _expressionEvaluator.EvaluateAsync(Body, workflowContext, null),
};

var result = await _smsProvider.SendAsync(message);
var result = await _smsService.SendAsync(message);

workflowContext.LastResult = result;

Expand Down
9 changes: 9 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Sms/AdminMenu.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using OrchardCore.Mvc.Core.Utilities;
using OrchardCore.Navigation;
using OrchardCore.Sms.Controllers;

namespace OrchardCore.Sms;

Expand Down Expand Up @@ -31,6 +33,13 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder)
.Permission(SmsPermissions.ManageSmsSettings)
.LocalNav()
)
.Add(S["SMS Test"], S["SMS Test"].PrefixPosition(), sms => sms
.AddClass("smstest")
.Id("smstest")
.Action(nameof(AdminController.Test), typeof(AdminController).ControllerName(), new { area = "OrchardCore.Sms" })
.Permission(SmsPermissions.ManageSmsSettings)
.LocalNav()
)
)
);

Expand Down
115 changes: 115 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Sms/Controllers/AdminController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.Sms.ViewModels;

namespace OrchardCore.Sms.Controllers;

public class AdminController : Controller
{
private readonly SmsProviderOptions _smsProviderOptions;
private readonly IPhoneFormatValidator _phoneFormatValidator;
private readonly INotifier _notifier;
private readonly IAuthorizationService _authorizationService;
private readonly ISmsProviderResolver _smsProviderResolver;
private readonly ISmsService _smsService;

protected readonly IHtmlLocalizer H;
protected readonly IStringLocalizer S;

public AdminController(
IOptions<SmsProviderOptions> smsProviderOptions,
IPhoneFormatValidator phoneFormatValidator,
ISmsProviderResolver smsProviderResolver,
ISmsService smsService,
INotifier notifier,
IAuthorizationService authorizationService,
IHtmlLocalizer<AdminController> htmlLocalizer,
IStringLocalizer<AdminController> stringLocalizer)
{
_smsProviderOptions = smsProviderOptions.Value;
_phoneFormatValidator = phoneFormatValidator;
_smsProviderResolver = smsProviderResolver;
_smsService = smsService;
_notifier = notifier;
_authorizationService = authorizationService;
H = htmlLocalizer;
S = stringLocalizer;
}

public async Task<IActionResult> Test()
{
if (!await _authorizationService.AuthorizeAsync(User, SmsPermissions.ManageSmsSettings))
{
return Forbid();
}

var model = new SmsTestViewModel();

PopulateModel(model);

return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test(SmsTestViewModel model)
{
if (!await _authorizationService.AuthorizeAsync(User, SmsPermissions.ManageSmsSettings))
{
return Forbid();
}

if (ModelState.IsValid)
{
var provider = await _smsProviderResolver.GetAsync(model.Provider);

if (provider is null)
{
ModelState.AddModelError(nameof(model.PhoneNumber), S["Please select a valid provider."]);
}
else if (!_phoneFormatValidator.IsValid(model.PhoneNumber))
{
ModelState.AddModelError(nameof(model.PhoneNumber), S["Please provide a valid phone number."]);
}
else
{
var result = await _smsService.SendAsync(new SmsMessage()
{
To = model.PhoneNumber,
Body = S["This is a test SMS message."]
}, provider);

if (result.Succeeded)
{
await _notifier.SuccessAsync(H["The test SMS message has been successfully sent."]);

return RedirectToAction(nameof(Test));
}
else
{
await _notifier.ErrorAsync(H["The test SMS message failed to send."]);
}
}
}

PopulateModel(model);

return View(model);
}

private void PopulateModel(SmsTestViewModel model)
{
model.Providers = _smsProviderOptions.Providers
.Where(entry => entry.Value.IsEnabled)
.Select(entry => new SelectListItem(entry.Key, entry.Key))
.OrderBy(item => item.Text)
.ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.DisplayManagement.Entities;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Environment.Shell;
using OrchardCore.Environment.Shell.Builders;
using OrchardCore.Mvc.ModelBinding;
using OrchardCore.Settings;
using OrchardCore.Sms.ViewModels;

Expand All @@ -22,45 +22,42 @@ public class SmsSettingsDisplayDriver : SectionDisplayDriver<ISite, SmsSettings>
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly IShellHost _shellHost;
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly ShellSettings _shellSettings;

protected IStringLocalizer S;

private readonly SmsProviderOptions _smsProviderOptions;

public SmsSettingsDisplayDriver(
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService,
IOptions<SmsProviderOptions> smsProviderOptions,
IShellHost shellHost,
ILogger<SmsSettingsDisplayDriver> logger,
IServiceProvider serviceProvider,
ShellSettings shellSettings)
IOptions<SmsProviderOptions> smsProviders,
ShellSettings shellSettings,
IStringLocalizer<SmsSettingsDisplayDriver> stringLocalizer)
{
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
_shellHost = shellHost;
_logger = logger;
_serviceProvider = serviceProvider;
_smsProviderOptions = smsProviders.Value;
_shellSettings = shellSettings;
_smsProviderOptions = smsProviderOptions.Value;
S = stringLocalizer;
}

public override async Task<IDisplayResult> EditAsync(SmsSettings settings, BuildEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;

if (!await _authorizationService.AuthorizeAsync(user, SmsPermissions.ManageSmsSettings))
{
return null;
}

return Initialize<SmsSettingsViewModel>("SmsSettings_Edit", model =>
public override IDisplayResult Edit(SmsSettings settings)
=> Initialize<SmsSettingsViewModel>("SmsSettings_Edit", model =>
{
model.DefaultProvider = settings.DefaultProviderName;
model.Providers = GetProviders();
}).Location("Content:1")
model.Providers = _smsProviderOptions.Providers
.Where(entry => entry.Value.IsEnabled)
.Select(entry => new SelectListItem(entry.Key, entry.Key))
.OrderBy(item => item.Text)
.ToArray();

}).Location("Content:1#Providers")
.RenderWhen(() => _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext?.User, SmsPermissions.ManageSmsSettings))
.Prefix(Prefix)
.OnGroup(SmsSettings.GroupId);
}

public override async Task<IDisplayResult> UpdateAsync(SmsSettings settings, BuildEditorContext context)
{
Expand All @@ -76,44 +73,31 @@ public override async Task<IDisplayResult> UpdateAsync(SmsSettings settings, Bui

if (await context.Updater.TryUpdateModelAsync(model, Prefix))
{
if (settings.DefaultProviderName != model.DefaultProvider)
if (string.IsNullOrEmpty(model.DefaultProvider))
{
settings.DefaultProviderName = model.DefaultProvider;
context.Updater.ModelState.AddModelError(Prefix, nameof(model.DefaultProvider), S["You must select a default provider."]);
}
else
{
if (settings.DefaultProviderName != model.DefaultProvider)
{
settings.DefaultProviderName = model.DefaultProvider;

await _shellHost.ReleaseShellContextAsync(_shellSettings);
await _shellHost.ReleaseShellContextAsync(_shellSettings);
}
}
}

return await EditAsync(settings, context);
return Edit(settings);
}

private SelectListItem[] _providers;

private SelectListItem[] GetProviders()
protected override void BuildPrefix(ISite model, string htmlFieldPrefix)
{
if (_providers == null)
{
var items = new List<SelectListItem>();
Prefix = typeof(SmsSettings).Name;

foreach (var providerPair in _smsProviderOptions.Providers)
{
try
{
var provider = _serviceProvider.CreateInstance<ISmsProvider>(providerPair.Value);

items.Add(new SelectListItem(provider.Name, providerPair.Key));
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to resolve an SMS Provider with the technical name '{technicalName}'.", providerPair.Key);
}
}

_providers = items
.OrderBy(item => item.Text)
.ToArray();
if (!string.IsNullOrEmpty(htmlFieldPrefix))
{
Prefix = htmlFieldPrefix + "." + Prefix;
}

return _providers;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Localization;
Expand All @@ -17,16 +16,18 @@ public class SmsTaskDisplayDriver : ActivityDisplayDriver<SmsTask, SmsTaskViewMo
{
private readonly IPhoneFormatValidator _phoneFormatValidator;
private readonly ILiquidTemplateManager _liquidTemplateManager;

protected readonly IStringLocalizer S;

public SmsTaskDisplayDriver(
IPhoneFormatValidator phoneFormatValidator,
IStringLocalizer<SmsTaskDisplayDriver> stringLocalizer,
ILiquidTemplateManager liquidTemplateManager)
ILiquidTemplateManager liquidTemplateManager,
IStringLocalizer<SmsTaskDisplayDriver> stringLocalizer
)
{
_phoneFormatValidator = phoneFormatValidator;
S = stringLocalizer;
_liquidTemplateManager = liquidTemplateManager;
S = stringLocalizer;
}

protected override void EditActivity(SmsTask activity, SmsTaskViewModel model)
Expand Down
Loading